Challenge: Begin Editing on Add

I have got a working solution implementing le Document with the @IBAction of the NSButton(Add) but I don’t like the behavior of the undo grouping.
The undo first delete the edit and after delete the new entry
I think the problem is in the call arrayController.addObject(car) that close the group before returning to the addCar.
There is a way to reopen the last undo group ?

A second question: There is a way to write the code into the CarArrayController ?
I try it inside the ‘override func addObject(object: AnyObject)’ but the problem is that I haven’t the windowController[0] that is in Document.
Perhaps I need an outlet of the File’s Owner ?

Hi,

I have a problem with de type Type. How Typed as a Car Type ?
It’s possible for you to share your code ?

Thx

I didn’t have any trouble with the undo groupings. My solution was overriding the AddObject(_:slight_smile: method on the ArrayController. There was a hint on the challenge that you will need to add an outlet to the ArrayController. I added an outlet for the tableview and put the following code in AddObject:

super.addObject(object) if let row = arrangedObjects.indexOfObjectIdenticalTo?(object) { tableView.editColumn(0, row, withEvent: nil, select: true) }

Then to ensure there was validation on whatever field the user was currently editing when clicking the add button, I moved the add button action to an IBAction in the model. There I call window.makeFirstResponder(nil) to do the current field validation, and if successful, then call the add selector on the ArrayController. This required another outlet on the document for the ArrayController.

This Code doesn’t work… why not? I get a host of errors but can’t figure where I’m off track. Thanks!

[code]class CarArrayController: NSArrayController {

@IBOutlet weak var tableView: NSTableView!

override func newObject() -> AnyObject {
    let newObj = super.newObject() as! NSObject
    let now = NSDate()
    newObj.setValue(now, forKey: "datePurchased")
           return newObj

}
override func add(sender: AnyObject?){
super.add(self)
let row = self.arrangedObjects.count
println("(row)")
tableView.editColumn(0, row: row, withEvent: nil, select: true)
}[/code]

yes my solution is similar but I was thinking about Model-View-Controller so I would like to put all the code referencing the View into the CarArrayController without using a separate @IBAction. I have the override func addObject(object: AnyObject) that intercept the Add !
The problem was in getting the reference to windowController (let windowController = windowControllers[0] as! NSWindowController). But the arrayController knows all about the table and the window !

The other little problem is into the undo. The Add Action insert a new row and after select the row and open the edit. These are two sections for the undo manager and if you choose to undo, the first time you undo the edit and the second you undo the add row. How to open one only section ?

Perhaps I have to wait when I will know more about the OS X functionality.
Thank You

here my code:

import Cocoa

class Document: NSPersistentDocument {
    
    @IBOutlet weak var arrayController: CarArrayController!    
    @IBOutlet weak var tableView: NSTableView!
    
    override init() {
        super.init()
        // Add your subclass-specific initialization here.
    }

    override func windowControllerDidLoadNib(aController: NSWindowController) {
        super.windowControllerDidLoadNib(aController)
        // Add any code here that needs to be executed once the windowController has loaded the document's window.
    }

    override class func autosavesInPlace() -> Bool {
        return true
    }

    override var windowNibName: String? {
        // Returns the nib file name of the document
        // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this property and override -makeWindowControllers instead.
        return "Document"
    }
    
    // MARK: - Actions
    
   @IBAction func addCar(sender: NSButton){
        let windowController = windowControllers[0] as! NSWindowController
        let window = windowController.window!
        let endedEditing = window.makeFirstResponder(window)
        if !endedEditing {
            println("incapace di terminare l'editing")
        return
        }
    let undo: NSUndoManager = undoManager!
    if undo.groupingLevel > 0 {
        undo.endUndoGrouping()
        undo.beginUndoGrouping()
    }
    let car = arrayController.newObject() as! NSObject
    arrayController.addObject(car)
    arrayController.rearrangeObjects()
    let sortedCars = arrayController.arrangedObjects as! [NSObject]
    let row = find(sortedCars, car)!
    tableView.editColumn(0, row: row, withEvent: nil, select: true)
    }
}
....
 override func addObject(object: AnyObject) {
        super.addObject(object)
    }

I went for a simple solution that seems to work. I made an outlet to the tableview and just found the row the new object was in and set that row to be edited.Undo/redo seems to work fine.

class CarArrayController: NSArrayController {

@IBOutlet weak var tableView: NSTableView!

override func newObject() -> AnyObject {
let newObj = super.newObject() as! NSObject
let now = NSDate()
newObj.setValue(now, forKey: “datePurchased”)

return newObj

}

override func addObject(object: AnyObject) {
super.addObject(object)

// Get the sorted array
let sortedObjects = self.arrangedObjects as! [NSObject]
let newObj = object as! NSObject

// Find the object just added
let row = find(sortedObjects, newObj)
if let r = row {
  tableView.editColumn(0, row: r, withEvent: nil, select: true)
}   

}
}

LOL!

I was pleased with my solution until I saw the solution offered by Feanor. His solution bypasses a problem on which I spent time fumbling.
Manoss asks what type is Car. One cannot simply say

or

The compiler chokes. The compiler is happy with

To find this out, select CarLotDocument.xcdatamodeld in the Project navigator pane. Select the Data Model Inspector (the third tab) in the Utilities pane. You will see an entity named Car of class NSManagedObject.

By overriding addObject(object: AnyObject) and calling super.addObject(object), Feanor bypasses the above and has an object that can then be cast as an NSObject without complaint.

My solution:

[code]class CarArrayController: NSArrayController {

@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var carLotDocument: CarLotDocument!

override func newObject() -> AnyObject {
    let newObj = super.newObject() as! NSObject
    let now = NSDate()
    newObj.setValue(now, forKey: "datePurchased")
    return newObj
}

@IBAction func addCar(sender: NSButton) {
    let window = tableView.window!
    
    let endedEditing = window.makeFirstResponder(window)
    if !endedEditing {
        println("Unable to end editing")
        return
    }
    
    let undo = carLotDocument.undoManager!
    if undo.groupingLevel > 0 {
        // close the last group
        undo.endUndoGrouping()
        // open a new group
        undo.beginUndoGrouping()
    }
    
    // create the new car object
    let car = self.newObject() as! NSManagedObject
    
    // add the new object to the array controller's content array
    self.addObject(car)
    
    // re-sort (in case the user has sorted a column)
    self.rearrangeObjects()
    
    // get thje sorted array
    let sortedCars = self.arrangedObjects as! [NSManagedObject]
    
    // find the object just added
    let row = find(sortedCars, car)!
    
    // begin edit in first column
    // println("starting edit of \(car) in row \(row)")
    tableView.editColumn(0,
        row: row,
        withEvent: nil,
        select: true)
}

}[/code]

Undo works for me. A further challenge would be to update the Undo item in the Edit menu to reflect the step being undone. Maybe after finishing the rest of the book :wink:

Well there is something I don’t understand. In Feanor’s solution he calls the superclass with super.addObject. Tombernard makes up his own method (addCar) and just says self.addObject. If one tries to override the addObject method without calling the superclass but just say self.addObject, there is a EXC_BAD_ACCESS error. It seems like there is some form of memory leak where the addObject method gets called and called and called until the whole thing goes up in smoke. Pretty annoying. Any ideas why this is so? I understand that it’s important to call the superclass in overridden methods, but I want to understand why this is so in this particular case with subclassing NSArrayController. I don’t understand why the addObject method gets called repeatedly…

I hadn’t considered adding or overriding a target-action. I tried monitoring the array of objects from the Document subclass.

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        guard context == &KVOContext else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            return
        }
        guard let keyPath = keyPath, object = object, change = change else {
            return
        }

        guard keyPath == "arrangedObjects" && object === carsController else {
            print("Document got wrong object/attribute to watch.")
            return
        }
        guard let rawChangeKind = (change[NSKeyValueChangeKindKey] as? NSNumber)?.intValue, changeKind = NSKeyValueChange(rawValue: UInt(rawChangeKind)) else {
            print("Couldn't find a (convertible) change.")
            return
        }
        switch changeKind {
        case .Insertion:
            guard let insertedIndices = change[NSKeyValueChangeIndexesKey] as? NSIndexSet else {
                print("Couldn't find the indices inserted.")
                return
            }
            let insertedIndex = insertedIndices.firstIndex
            guard insertedIndex != NSNotFound else {
                print("Couldn't find an index for an inserted item")
                return
            }
            tableView.editColumn(0, row: insertedIndex, withEvent: nil, select: true)
        default:
            print("Didn't have the right kind of change. (Wanted '\(NSKeyValueChange.Insertion.rawValue)', got '\(changeKind.rawValue)'.)")
            return
        }
    }

I put an observation on my car-array controller in the window-NIB-loaded method and remove the observation when the window closes (adding the needed delegate method). I initially was watching the “contentArray” property, but that did nothing. My insertion-watch didn’t work because the code uses .Setting instead! The bindings code splats an updated array in a single shot instead of finely manipulating single cells. I guess I have to do it the way all of you are…

(unless there’s a way to force updates to the arranged-objects’ array to be piecemeal instead of all-at-once?)

[quote=“CTMacUser”]I hadn’t considered adding or overriding a target-action. I tried monitoring the array of objects from the Document subclass.

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        guard context == &KVOContext else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            return
        }
        guard let keyPath = keyPath, object = object, change = change else {
            return
        }

        guard keyPath == "arrangedObjects" && object === carsController else {
            print("Document got wrong object/attribute to watch.")
            return
        }
        guard let rawChangeKind = (change[NSKeyValueChangeKindKey] as? NSNumber)?.intValue, changeKind = NSKeyValueChange(rawValue: UInt(rawChangeKind)) else {
            print("Couldn't find a (convertible) change.")
            return
        }
        switch changeKind {
        case .Insertion:
            guard let insertedIndices = change[NSKeyValueChangeIndexesKey] as? NSIndexSet else {
                print("Couldn't find the indices inserted.")
                return
            }
            let insertedIndex = insertedIndices.firstIndex
            guard insertedIndex != NSNotFound else {
                print("Couldn't find an index for an inserted item")
                return
            }
            tableView.editColumn(0, row: insertedIndex, withEvent: nil, select: true)
        default:
            print("Didn't have the right kind of change. (Wanted '\(NSKeyValueChange.Insertion.rawValue)', got '\(changeKind.rawValue)'.)")
            return
        }
    }

I put an observation on my car-array controller in the window-NIB-loaded method and remove the observation when the window closes (adding the needed delegate method). I initially was watching the “contentArray” property, but that did nothing. My insertion-watch didn’t work because the code uses .Setting instead! The bindings code splats an updated array in a single shot instead of finely manipulating single cells. I guess I have to do it the way all of you are…

(unless there’s a way to force updates to the arranged-objects’ array to be piecemeal instead of all-at-once?)[/quote]

Well, I start off by adding a property on CarArrayController that’s a set. I changed the newObject method to add its new objects to that set.

Then I change the observer method in the document subclass. I took out the change-kind stuff, and after the check for the car-array controller’s arranged-objects, I go through the controller’s set of new objects, I select new object’s row in the table for editing, then remove the object from the set.