Replacing model objects using an NSArrayController

  • Hello everyone,

    I'm a bit of a newbie here, but hopefully this question won't be too annoying. :-)

    My application contains several records displayed to the user in an NSTableView bound to an NSArrayController. Because these records are complicated (containing around thirty fields), I have implemented an Edit option where the user can select one of rows in the table, click the Edit button, and have a new window appear displaying all of the fields, radio buttons, numeric values, etc. for changing.

    I have implemented NSCopying on my data objects. When I go to display the edit window, I get a reference to the selected object (myController arrangedObjects / selectionIndexes / objectAtIndex), copy the data into a new object for editing ([myObject copy]) and then pass that off to the NSWindowController that manages the editing process. I do this so that a Cancel from the editor window will not affect the original object.

    All of that seems to be straightforward enough.

    My question is how to get the new, edited object back into the array controller.

    I have implemented all of the KVC methods in the Manager class that supplies the data to the NSArrayController, and they are being called for Add and Delete. I've also coded a replaceObjectInMyArrayAtIndex:withObject: which I would like to be called by the array controller when it swaps in the new object for the old one. Although my current code does the same thing with separate remove and insert calls (see below), that seems to lead to two undoable actions, rather than the one I have coded in replaceObjectInMyArrayAtIndex:withObject:

    fSrcbook = [selArray objectAtIndex: selItem];
    fEditbook = [fSrcbook copy];

    // display editing window, etc.

    if (returnCode == NSOKButton) {
      NSArray * bka = [fArrayController arrangedObjects];

      int row = [bka indexOfObjectIdenticalTo: fSrcItem];
      [fArrayController removeObject: fSrcItem];
      [fArrayController insertObject: fEditItem atArrangedObjectIndex: row];
    }

    (I suppose I could put the remove/insert in an undo edit group, but I'd like to keep my undo code out of this method, if possible. No particular reason, except that it feels cleaner to me.)

    I can't see anything in the NSArrayController class that will help me do the replace directly, and nothing else relevant has popped up in my searching for a solution. I admit, however, that I am sometimes blind to the obvious, and I also may have used inadequate search terms. Still, I highly doubt I'm the first person to want to do this, and it just seems like it should be possible to do a replace directly.

    And I am also wondering if this is the usual way to implement editing a complicated object like I am attempting to do.

    Any suggestions or clarifications would be greatly appreciated.

    Thank you,
    Bill
  • On Feb 11, 2010, at 20:33, William Peters wrote:

    > My application contains several records displayed to the user in an NSTableView bound to an NSArrayController. Because these records are complicated (containing around thirty fields), I have implemented an Edit option where the user can select one of rows in the table, click the Edit button, and have a new window appear displaying all of the fields, radio buttons, numeric values, etc. for changing.
    >
    > I have implemented NSCopying on my data objects. When I go to display the edit window, I get a reference to the selected object (myController arrangedObjects / selectionIndexes / objectAtIndex), copy the data into a new object for editing ([myObject copy]) and then pass that off to the NSWindowController that manages the editing process. I do this so that a Cancel from the editor window will not affect the original object.
    >
    > All of that seems to be straightforward enough.
    >
    > My question is how to get the new, edited object back into the array controller.
    >
    > I have implemented all of the KVC methods in the Manager class that supplies the data to the NSArrayController, and they are being called for Add and Delete. I've also coded a replaceObjectInMyArrayAtIndex:withObject: which I would like to be called by the array controller when it swaps in the new object for the old one. Although my current code does the same thing with separate remove and insert calls (see below), that seems to lead to two undoable actions, rather than the one I have coded in replaceObjectInMyArrayAtIndex:withObject:
    >
    > fSrcbook = [selArray objectAtIndex: selItem];
    > fEditbook = [fSrcbook copy];
    >
    > // display editing window, etc.
    >
    > if (returnCode == NSOKButton) {
    > NSArray * bka = [fArrayController arrangedObjects];
    >
    > int row = [bka indexOfObjectIdenticalTo: fSrcItem];
    > [fArrayController removeObject: fSrcItem];
    > [fArrayController insertObject: fEditItem atArrangedObjectIndex: row];
    > }
    >
    > (I suppose I could put the remove/insert in an undo edit group, but I'd like to keep my undo code out of this method, if possible. No particular reason, except that it feels cleaner to me.)

    There isn't a "replace" method for an array controller. If you must do it as a single operation, then solution would be to fetch the original object at the end of editing, update its properties to match the (edited) properties of the copy, then discard the now-temporary object copy.

    However, your description doesn't completely make sense. There are no objects "in" an array controller. Instead, think of the array controller as a sorted, filtered perspective on your underlying data. Normally, the array controller is monitoring the underlying data via KVO, so any underlying changes are noticed and it adjusts itself accordingly without your writing any code.

    You seem to be saying that you've already updated the underlying data model ("I have implemented all of the KVC methods in the Manager class ..."), in which case you *don't* want to mess with the array controller directly. Basically, there are two approaches to adding, deleting and replacing, when an array controller is involved:

    1. Update the data model directly and KVO compliantly, and let the array controller notice the changes via KVO.

    2. Do not update the data model directly, but use the array controller methods instead (removeObject:, insertObject:atArrangedObjectIndex:, etc). These methods internally cause the data model to be updated.

    Pick one. Not both.

    Method #1 is the cleanest, because it doesn't introduce the array controller (which is really part of your UI glue code) into the data model update, but you *must* ensure that the data model change is made KVO compliantly. Typically, that means making the changes using a mutableArrayValueForKey: proxy object.

    Method #2 is a convenience when you've not built (or have not been able to build) a clean MVC design into your app, or when you must work with the "arrangedObjects" order for some reason.

    Others will likely disagree with me, but I think that there's something a bit "smelly" about code referring to array controllers. It often indicates a defect in the MVC design.
  • On Feb 11, 2010, at 10:33 PM, Quincey Morris wrote:

    > There isn't a "replace" method for an array controller. If you must do it as a single operation, then solution would be to fetch the original object at the end of editing, update its properties to match the (edited) properties of the copy, then discard the now-temporary object copy.
    >
    > However, your description doesn't completely make sense. There are no objects "in" an array controller. Instead, think of the array controller as a sorted, filtered perspective on your underlying data. Normally, the array controller is monitoring the underlying data via KVO, so any underlying changes are noticed and it adjusts itself accordingly without your writing any code.
    >
    > You seem to be saying that you've already updated the underlying data model ("I have implemented all of the KVC methods in the Manager class ..."), in which case you *don't* want to mess with the array controller directly. Basically, there are two approaches to adding, deleting and replacing, when an array controller is involved:
    >
    > 1. Update the data model directly and KVO compliantly, and let the array controller notice the changes via KVO.
    >
    > 2. Do not update the data model directly, but use the array controller methods instead (removeObject:, insertObject:atArrangedObjectIndex:, etc). These methods internally cause the data model to be updated.
    >
    > Pick one. Not both.
    >
    > Method #1 is the cleanest, because it doesn't introduce the array controller (which is really part of your UI glue code) into the data model update, but you *must* ensure that the data model change is made KVO compliantly. Typically, that means making the changes using a mutableArrayValueForKey: proxy object.
    >
    > Method #2 is a convenience when you've not built (or have not been able to build) a clean MVC design into your app, or when you must work with the "arrangedObjects" order for some reason.
    >
    > Others will likely disagree with me, but I think that there's something a bit "smelly" about code referring to array controllers. It often indicates a defect in the MVC design.
    >

    Quincey,

    Your explanation took a little while to make sense to me, but I finally did have the "aha" moment. You were right that I was confusing my MVC design, trying to use a controller object to tell another controller object to change the model. I wasn't aware that I could just change the model directly and that KVO would affect the other controller for me. It still seems rather magical how it does that, but I've reworked my code to directly call my replaceObjectInMyArrayAtIndex:withObject: and verified that the changes are now flowing through to the UI just how I wanted.

    Thank you for helping me through this learning experience; I'm sure I will draw from it again and again.

    Bill
previous month february 2010 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
Go to today