Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2016-02-25

NSOutlineView example in Swift

I encountered some strange effects when trying to use the NSOutlineView with pure Swift types. So here is an example on how I got it working using Foundation types.

A sample of the view I wanted to achieve:


This is the base type that I wanted to display in the outline view:

// The Domains Tab

class Domain {
    var name: String = "domain-name.extension"
    var wwwIncluded: Bool = true
    var root: String = "root-folder"
    var forewardUrl: String = ""
}

Which I had to replace with:

// The Domains Tab

class Domain {
    var name: NSString = "domain-name.extension"
    var wwwIncluded: NSNumber = NSNumber(bool: true)
    var root: NSString = "root-folder"
    var forewardUrl: NSString = ""

}


It was not the Bool that caused the problems, but the String. But before I talk about that, let me first show the implementation of the NSOutlineViewDataSource delegate:

var domains = Array<Domain>(arrayLiteral: Domain(), Domain())
@IBOutlet weak var domainNameColumn: NSTableColumn!
@IBOutlet weak var domainValueColumn: NSTableColumn!

extension ConsoleWindowViewController: NSOutlineViewDataSource {
    
    func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
        if item == nil { return domains.count }
        return 3
    }
    
    func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
        return (domains as NSArray).containsObject(item)
    }
    
    func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
        if item == nil { return domains[index] }
        for d in domains {
            if item === d {
                switch index {
                case 0: return d.wwwIncluded
                case 1: return d.root
                case 2: return d.forewardUrl
                default:
                    log.atLevelError(id: 0, source: "ConsoleWindowViewController.outlineView-child-ofItem", message: "Index out of range: \(index)")
                }
            }
        }
        return "Error"
    }
    
    // Using "Cell Based" content mode
    func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
        if tableColumn === domainNameColumn {
            for d in domains {
                if item === d { return d.name }
                else if item === d.wwwIncluded { return "Include 'www' prefix" }
                else if item === d.root  { return "Root directory" }
                else if item === d.forewardUrl { return "Foreward to URL" }
            }
        } else if tableColumn === domainValueColumn {
            for d in domains {
                if item === d { return nil }
                else if item === d.wwwIncluded { return (d.wwwIncluded.boolValue ? "yes" : "no") }
                else if item === d.root { return d.root }
                else if item === d.forewardUrl { return (d.forewardUrl.length > 0 ? d.forewardUrl : "-") }
            }
        }
        return nil
    }
}

As you can see, at some point we need to compare the item to the members of the Domain class just to know if the item is referenced. When root is defined as String then this comparison:

item === (d.root as AnyObject

would crash the App at runtime. Strangely enough defining the wwwIncluded as Bool and comparing it as

item === (d.wwwIncluded as AnyObject)

would work fine.

Only by converting the String to a NSString would the App run correctly.

While I do not know what the cause is for this, I don't mind defining the Domain class with Foundation class types as it makes the code a lot more readable. The number of casts is reduced considerably.

Oh, and one more thing: The NSOutlineView in InterfaceBuilder is preset to be "View Based" instead of "Cell Based". If at runtime the view shows you the annoying  "Table View Cell" content, you have probably forgotten to change the Table View - Content Mode from "View Based" to "Cell based":



If you want to use the "View Based" table then do not use

    func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {

but use the NSOutlineViewDelegate operation:

    func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {

instead.

Note: While it does work, it turned out that the above code has some not-so-nice properties. I posted an improved example here: Using NSOutlineView in Swift, an example.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment