Data source challenge - making table view editable

I can’t crack this one. I am able to make the text in the table view editable but I cannot extract that new text and update my data model (a simple array). All of the delegate methods that would easily allow this seem to be for tables that are constructed with cells rather than the views we are using. I have found controlTextDidEndEditing(obj: NSNotification) which is called if I make the Table View Cell’s TextField’s delegate the File’s Owner but with no luck. Any pointers would be appreciated.

I changed the NSTableView’s “Content Mode” from “View Based” to “Cell Based”. Then you can double click on a row to edit it.

Yes, but I’d really like to maintain the View based table structure especially as cell based tables have been deprecated. I can make the cells clickable and edit the text by setting the ‘Editable’ attribute but this doesn’t update the underlying model layer.

As you identified, you can’t use a delegate method for this challenge when using a view-based table view.

Instead, you will want to add an action method itemTextFieldUpdated(_:slight_smile: to MainWindowController. You’ll then configure the NSTextField embedded within the NSTableCellView (within the table) to have the window controller as its target and itemTextFieldUpdated: as its action.

When you set up the target/action, you should see a warning in the issue navigator (if you have live issues enabled) informing you that a table view’s action must be sent to its delegate. Fix this by setting the window controller to be the table view’s delegate.

In the implementation of itemTextFieldUpdated(_:slight_smile:, you’ll take advantage of NSTableView’s method rowForView(_:slight_smile: to determine what row the text field which triggered the method is in.

I actually just did this with much less changes:

  • Change the “Behaviour” of the NSTextField from “None” to “Editable”
  • Implement tableView(tableView:, setObjectValue:, forTableColumn:, row:)

Nate - Thanks but wow, that is quite some challenge :slight_smile: I’d better gird my loins for the rest of the book. I’ll give it a go today and let you know how I get on.

St3fan - Yes, I saw that method but, again, it only relates to the deprecated cell based tables.

Nate - Thanks. Worked just as you described.

My 2¢:

also works.

I noticed, that when my data model is an array of objects, and not just strings,
the data model is updated every time i edit a tableCellView.

Obviously, this has to do with the fact that when binding to objects, the binding
succeeds in updating the model, because objects are reference types.

When my data model is just made of an array of strings, i have to manually,
update the model, using the itemTextFieldUpdated action, mentioned above.
This has to do with the string being a value type.

Is my thought correct on this matter? Has anyone else tried something similar?
Have you noticed the same results with me?

Thanks a lot anyway!!!

If you hunted through the docs long enough looking for a protocol method that would work, you would have eventually found this one:

[code]NSTableViewDataSource Protocol Reference

Setting Values

  • tableView:setObjectValue:forTableColumn:row:

Swift:
optional func tableView(_ aTableView: NSTableView,
setObjectValue anObject: AnyObject?,
forTableColumn aTableColumn: NSTableColumn?,
row rowIndex: Int)

Objective-C:

  • (void)tableView:(NSTableView *)aTableView
    setObjectValue:(id)anObject
    forTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex

[/code][quote]
Discussion:
This method is intended for use with cell-based table views, it must not be used with view-based table views. In view-based tables, use target/action to set each item in the view cell.
[/quote]
The last line of the discussion is the key. And after that, you needed to figure out that an NSTextField can trigger an action–just like a button can–and the action is: done editing the NSTextField, which is convenient. :slight_smile:

Like tombernard, I also used tableView.selectedRow to get the row that was just edited.

[quote]I noticed, that when my data model is an array of objects, and not just strings,
the data model is updated every time i edit a tableCellView.[/quote]
I created a model called Item(don’t forget to inherit from NSObject!) with one property called text, and I changed the binding of the NSTextField to objectValue.text. In the Add button’s action method, I created an Item object and set its text property to whatever was entered in the NSTextField, then I added the Item object to the array. Next, inside the action method that is triggered when the NSTextField is edited, I commented out all the code. When I run my app, I see the same results as you.

According to the book, a value in the array is assigned to the cell’s objectValue property. Then the NSTextfield retrieves the value from the objectValue property. According to that description, the NSTextField does not set any values–it only gets a value. But what you are seeing belies that state of affairs.

If Cocoa Bindings takes the Model key path: objectValue in one case and objectValue.text in the other case, and automatically assigns the edited value of the NSTextField to those variables, then your explanation seems spot on.

With an array of Strings, if Cocoa Bindings assigns the edited String to objectValue, that won’t affect the String in the array. Because a String is a value type, i.e. the String is copied when it is assigned to a new variable, a copy of the String in the array was assigned to objectValue. When Cocoa Bindings assigns the edited String to objectValue, the copy of the String in the array is discarded. None of that affects the original String in the array, so the array will still contain the original String. As a result, the original String will be displayed in the TableView.

With an array of Item objects, if Cocoa Bindings assigns the edited String to objectValue.text, then that will change the array. An array of Item objects is really an array of pointers to Item objects. And when a pointer in the array is assigned to objectValue, then both objectValue and the array will have a pointer to the same Item object. So when CocoaBindings assigns the edited String to objectValue.text, that changes the object that the array points to. As a result, the Item object in the array will contain the edited String, and the TableView will display the edited String.

Sorry for asking what it seems to be a silly question.

I just noticed, that when working with a tableView inside a Document.xib window,
when i change a cell’s behavior to editable, the cell’s text appears in light gray, where as,
all other non-editable cells, show text in black.

Does anyone has a clue?

I’ve been struggling with this for hours today. I came across the information that you had to use target-action for view-based tables, but unlike you, I never figured out that the action would be “done editing.” Is this documented anywhere? I’ve been wasting time looking for a protocol function that does it.

An NSTextField’s action is mentioned at the start of the NSTextField Class Reference:

developer.apple.com/library/mac … eld_Class/

And more detail is provided here:

developer.apple.com/library/mac … Views.html

Of course you can use .selectedRow
most of the time, but what if you bring the cell into edit mode programmatically and not by double-clicking it? Guess what happens then…

@Nate
Thx for the great hints, works perfect.

My code of the IBAction method from the Cell-TextField:

@IBAction func doneEditing(sender: NSTextField) { let overriddenTodo = sender.stringValue let itemIndex = tableView.rowForView(sender) todoList[itemIndex] = overriddenTodo tableView.reloadData() }

[quote=“NateChandler”]As you identified, you can’t use a delegate method for this challenge when using a view-based table view.

Instead, you will want to add an action method itemTextFieldUpdated(_:slight_smile: to MainWindowController. You’ll then configure the NSTextField embedded within the NSTableCellView (within the table) to have the window controller as its target and itemTextFieldUpdated: as its action.

When you set up the target/action, you should see a warning in the issue navigator (if you have live issues enabled) informing you that a table view’s action must be sent to its delegate. Fix this by setting the window controller to be the table view’s delegate.

In the implementation of itemTextFieldUpdated(_:slight_smile:, you’ll take advantage of NSTableView’s method rowForView(_:slight_smile: to determine what row the text field which triggered the method is in.[/quote]

So I tried all this and I still don’t get it. How do I get a double-click on the row to edit the text field and run this method?

I had the same problem, I added the code… and I could select the row but not edit anything. Make sure that you have done the following (spelling out what Nate and others have already stated, but in perhaps a little more detail):

[ul]
[li]In interface builder, for the NSTextField control labeled “Table View Cell”, change Behavior – in the middle of the 3rd ‘Text Field’ section on the Attributes Inspector, directly under Action – from ‘None’ to ‘Editable’. (This was the main one I missed! For the life of me, I couldn’t find this editable setting until other’s in this thread pointed out the ‘Behavior’ field.)

[/li]
[li]Set the IBAction from NSTextField control labeled “Table View Cell” to File’s Owner -> itemTextFieldUpdated.

[/li]
[li]Make sure that MainWindowController declares the NSTableViewDelegate protocol (in addition to NSTableViewDataSource). I didn’t even declare any of the methods from the protocol, so I didn’t have it listed initially… but I think you need it for the following step.

[/li]
[li]As Nate notes, make sure that NSTableView has delegate pointed to File’s Owner (MainWindowController). If you don’t set the delegate the itemTextFieldUpdated action won’t work even though you can double-click on a row and edit the text in the table. You will also get a warning stating the following: “[color=#BF0040]Action ‘itemTextFieldUpdated:’ sent by ‘Table View Cell’ is connected to ‘File’s Owner,’ an invalid target (Objects inside view based table views may only be connected to the table view’s delegate.)[/color]”[/li][/ul]
If you still have problems, below is the code I used. I used ‘AnyObject’ as the sender for the addItem action… as I used it as the target from both the ‘Add’ NSButton as well as the standalone NSTextField at the top. That way, it would add items when I just hit the ‘Return’ key after typing into the textField as well as by clicking on the ‘Add’ button. I was going to try and figure out how to delete a row by selecting it and hitting the ‘Delete’ key, but I didn’t yet. However, I do have itemTextFieldUpdated written so that it will delete the row if you delete the entire string once you select the text itself.

    @IBAction func addItem(sender: AnyObject) {
        if textField.stringValue != "" {
            items.append(textField.stringValue)
            textField.stringValue = ""
        }
        tableView.reloadData()
        print(items)
    }
    
    @IBAction func itemTextFieldUpdated(sender: NSTextField) {
        let row = tableView.rowForView(sender)
        if sender.stringValue == "" {
            items.removeAtIndex(row)
        } else {
            items[row] = sender.stringValue
        }
        tableView.reloadData()
        print(items)
    }

Thanks! It work great