addEmployee(sender: NSButton)

Because the book does not include an explanation of how this method works, I thought we could all put our heads together and come up with an explanation.

As far as I can tell, a lot of the code in the addEmployee() method is for “making things pretty”. The following is a bare bones implementation of the method, which seems to work:

[code] @IBAction func addEmployee(sender: NSButton) {

    let employee = Employee()
    arrayController.addObject(employee)  //Adds to end of Array Controller's content array, 
                                         //which automatically updates the model, i.e. the employees array
    
    tableView.editColumn(0,
        row: employees.count - 1,  //Get the index of the last employee in the array.
        withEvent: nil,
        select: true
    )
    
}[/code]

One thing that the book fails to mention is: when you connect the Add Employee button to the File’s Owner(Document), that destroys the connection we previously established between the Add Employee button and the ArrayController (see p. 164 for how that connection was previously setup). Previously, clicking the Add Employee button called the ArrayController’s add() action, but now we have to add the new Employee by hand inside our addEmployee() action.

What about the rest of the code?

        let windowController = windowControllers[0] as! NSWindowController
        let window = windowController.window!
        
        let textFieldWillGiveUpFirstResponderStatus =
            window.makeFirstResponder(window)
        
        println(textFieldWillGiveUpFirstResponderStatus)
        
        if !textFieldWillGiveUpFirstResponderStatus {
            println("Please finishing editing the current Employee before trying to add a new Employee.")
            return
        }

First, I would read p. 305-306 which explains what “first responder” means.

I don’t see how the code above applies to our app. Initially, I thought that if the user were in the middle of editing an Employee, say the name, then the name TextField would not give up its first responder status because the user was not done with the edits. In order to signal that the user was done editing the TextField and that the TextField was willing to give up its first responder status, I thought the user needed to hit Return. In that case, you could prevent the user from adding a new Employee while the user was editing another Employee by testing if you could make another object be the first responder, e.g. the window. If window.makeFirstResponder(window) returned true, that would indicate that the user was not in the middle of editing a TextField. However, adding the code above does not prevent the user from adding a new Employee while they are editing another Employee.

My app seems to work the same with or without the first responder code, so I don’t know what it does. Anyone have any ideas?

My next idea was that the first responder code had to do with multiple documents being open at the same time, but I can’t figure out how the code applies to that situation either.

I printed out the first responder at the top of the addEmployee method, and when you click the Add Employee button the first time, the first responder is the TableView. Then if you begin editing the name TextField for that Employee, and you click on the Add Employee button again, then some object of type NSTextView becomes the first responder. After hacking around, I used the following code to print out the text of the NSTextView (something called an NSTextView has to have text, right!):

        let textView = window.firstResponder as? NSTextView
        if let text = textView?.textStorage?.string {
            println(text)
        }

…which prints the edits to the name of the Employee that you were editing. So, that NSTextView object is somehow connected to the name NSTextField in the TableView. In any case, the output puzzled me because I thought the Add Employee button would be the first responder after you clicked on the button, however I guess an NSButton must refuse the first responder status (which, by the way, is the default for all NSResponder objects).

The following info from the Apple docs is interesting:

[quote]Determining First-Responder Status

Usually an NSResponder object [e.g. any control] can always determine if it’s currently the first responder by asking its window (or itself, if it’s an NSWindow object) for the first responder and then comparing itself to that object. You ask an NSWindow object for the first responder by sending it a firstResponder message [i.e. by calling firstResponder() on the window].


A complication of this simple scenario occurs with text fields. When a text field has input focus, it is not the first responder. Instead, [something called] the field editor for the window is the first responder; if you send firstResponder to the NSWindow object, an NSTextView object (the field editor) is what is returned. To determine if a given NSTextField is currently active, retrieve the first responder from the window and find out it is an NSTextView object and if its delegate is equal to the NSTextField object.[/quote]

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

Apparently, a window has one “field editor”, which is an NSTextView object, and it handles the editing for all the TextField’s in the window; and if you are editing a TextField, that NSTextView object becomes the firstResponder:

developer.apple.com/library/mac … 009459-CH8

Therefore, if you want to prevent the user from adding a new Employee while they are still editing another Employee, which the example code seems to be trying to accomplish, you can check if the first responder is an NSTextView–if it is, that means the user is currently editing one of the TextField’s. Here’s the code:

        let textView = window.firstResponder as? NSTextView  //If the firstResponder is not an NSTextView, 
                                                             //the cast fails, returning nil
        
        if let validTextView = textView {  //Check to see if the textView variable is non-nil by 
                                           //using an "if let" to unwrap it

            println("Please hit Return to finish editing the employee before clicking on Add Employee!")
            return   //Terminate the addEmployee method before adding a new Employee.
        }
        else {
            println("I will add a new Employee for you!")
        }
  1. Next is the code:
        let undo: NSUndoManager = undoManager!

        if undo.groupingLevel > 0 {
            undo.endUndoGrouping()
            undo.beginUndoGrouping()
        }

Back on p. 192, the book says:

I think that means that all the changes that your action method makes will be grouped together under one undo in the Undo menu. So, for example, if your action method calls ten other functions which all make changes, then all those changes will be grouped into one undo.

But the only change our addEmployee() action method makes in response to clicking on the Add Employee button is to add a new Employee to the ArrayController. So why do we have to add the defensive code above to close any previous group of changes and open a new group? One possibility: some other code somewhere that we might add in the future might decide to call our addEmployee() method directly–but if that is the case, why shouldn’t that future code get to decide if it wants the changes made by our addEmployee() method to be in the same group of changes it is making or be in a separate group?

Okay, I figured out one effect of the undo code. If the user is in the middle of editing an Employee’s name and they click on the Add Employee button, then the change the user made to the first Employee’s name will be included in the same undo group as “Undo Add Employee” for the second Employee. In other words, if the user clicks on the menu item Undo Add Employee, the second Employee that they added will be removed AND the change they made to the first Employee’s name will also be undone. The undo code above prevents that from happening: when the user clicks on the menu item “Undo Add Employee”, the undo machinations will only remove the second Employee.

The book has a comment for the undo code that says, “Has an edit occurred already in this event?”, which as far as I can tell is incorrect. The undo code actually handles edits left over from a previous event. The previous event was double_clicking_the_Employee/hitting_Return to begin editing the Employee, and this event is clicking the Add Employee button.

  1. Next up is this code:

arrayController.rearrangeObjects() let sortedEmployees = arrayController.arrangedObjects as! [Employee] let row = find(sortedEmployees, employee)!

Let’s say that the user enters two Employee’s with the names “Ollie” and “Ann”:

Ollie              5%
Ann                5%

Then the user clicks on the name column header a couple of times to sort alphabetically (remember to change your sort key for the name column from name.length back to name and change the selector from compare: back to caseInsensitiveCompare: in the Attributes Inspector):

Ann                5%
Ollie              5%

If the user then clicks on the Add Employee button, by default the name of the new Employee is “New Employee”, which starts with an “N”, so with the code above the new Employee will get inserted between Ann and Ollie:

Ann                5%
New Employee       5% 
Ollie              5%

That seems like a pretty useless feature to me. The user is going to change the name of the new Employee right away anyway, and if the user changes the name to “Zack”, then the alphabetical order will be wrong, and the user will have to click on the column header again to sort the names.

By the way, optionals allow you to do error handling, for instance:

if let x = someOptionalType { //do something if x has a non-nil value } else { //take alternative actions if x is nil }

But the book has opted to ignore the error handling, and instead the book just uses the unsafe ! when unwrapping optionals or casting, which I interpret to mean: “We want the app to fail immediately if the value of the optional is nil or the cast cannot be done.”