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

2015-11-30

Swift Example: Binding a NSTableView to an Array

After the introduction to Cocoa bindings in the previous post, todays example is a little more complicated. Unfortunately Apple is lacking in easily accessible documentation: I had to spend a couple of days in frustration before I finally got the tableview bindings working. Though there are some blogs on this subject I found that none worked for the version of xcode that I am using here (7.1).

So here goes:

This is how the app will look:



Each row will contain a label, a textfield and a button. The content for the label and the textfield will be in a table, and the button will be linked to the object that is shown in the table. As you see above, the button will be disabled if the textfield is empty.

All the code that is necessary (in AppDelegate.swift) looks as follows:

class ParameterTableRow: NSObject {
    var name: String = "Parameter Value:"
    var value: String?
    func setButtonClicked() {
        print("Set button clicked")
    }
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    dynamic var parameterTable: Array<ParameterTableRow> = []
    
    func applicationDidFinishLaunching(aNotification: NSNotification) {
        // Insert code here to initialize your application
    }

    func applicationWillTerminate(aNotification: NSNotification) {
        // Insert code here to tear down your application
    }

}

In the AppDelegate class itself, I only added the definition of the parameterTable. Notice that this property must be made dynamic. Otherwise the runtime cannot intercept the messages to and from the property.
The parameterTable contains items of the class ParameterTableRow that for convenience was included in the same file (AppDelegate.swift).
It is advisable that the ParameterTableRow class inherits from NSObject  It must implement the KVO and KVC protocols and inheriting from NSObject is the easiest way to achieve this.

The name and value properties are displayed in the table, and the button in the table-row will call the method setButtonClicked.

Now, the part that is difficult to show: the bindings.

In Xcode/IB create a window with a table. Also create a row-like NSView that is populated with a label, a textfield and a button. In the table remove the content of the Table Cell View and replace it with the row view. The end result should look like this:


Note that I also added two buttons "add(+)" and "remove(-)" at the end.

Next, drag an Array Controller:


into the objects of this xib file. The result should be this:


The Shared User defaults Controller appears automagically, don't worry about it. We will not touch it.

The Array Controller must be bound to the parameterTable in our delegate. Select the Array Controller and open up the bindings inspector:


Under "Controller Content" open "Content Array" and bind to the delegate like this:


Since we will be using a Add(+) button to create new items in the table, we also need to tell the Array Controller which kind of items to create. Do this in the attributes inspector of the Array Controller:


Fill in the Class Name and use the fully qualified name: This starts with the project name and follows the path from there. In our case it is simply "TableBindingsExample.ParameterTableRow".

That is all for the Array Controller.

Next, lets bind the Table View to the Array Controller. We need to make two bindings, one for the content and one for the selection. Select the Table View in the object navigator of the xib file and open its bindings inspector. Create the bindings as shown below:


Forgetting to create the selection indexes bindings will cause problems later when table rows are deleted. (When the remove button only removes the last entry, you will know that you forgot this binding)

While we are at the Table View, we can also enable multiple selection in the attributes inspector:


Note the "Selection" checkbox "Multiple" is selected here to enable multiple row selection.

Next bind the Parameter Value Textfield (not the cell with the same name!!). Select the Parameter Value and open up the bindings inspector. Create the Value binding as follows:


Note that the value is not bound to the array controller but to the Table Cell View. When the runtime fills in the column, it sets the "objectValue" in the Table Cell View to the corresponding ParameterTableRow object. Hence we need to bind to the objectValue and specify the property "name" of our table row object.

Do the same for the Text Field of our table row as follows:


For the button we do not bind the content, but the target. And possible the arguments, however our target has no arguments. Bind the target of the button as follows:


Clicking the button will call the setButtonClicked method, but we want to disable the button if the textfield is empty. There is a binding for that... :



One final thing: connect the Add(+) and Remove(-) buttons to the Array Controller and connect them to the corresponding actions. Use the simple ctrl-drag for this, drag from the + button to the Array Controller object and select the "add" action. Do the same for the - button and the "remove" action. The Connections inspector should show this for the add button:


That is all, now the project should work as intended. Compile and run.
I have tried to show all the steps, if I missed something, please let me know.

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