Problem with key paths and key value observing

  • I am getting the following errors with anything bound to a key path of
    selectedObjects.categoryList.name

    2004-08-24 01:29:49.582 iOrganizer[11421] [<NSCFArray 0x3816a0>
    addObserver:forKeyPath:options:context:] is not supported.  Key path:
    name

    and a duplicate one for removeObserver (if the program doesn't crash
    in the process)

    My object arrangement consists of two NSMutableArrays in the file's
    owner app controller.
    One is dataViews the other is categories.

    dataViews is an array of DataView objects with NSMutableString *name
    and NSMutableArray *categoryList. categories is an array of Category
    objects with NSMutableString *name.
    I have two actions that set categoryList based on selected categories.
    (If you click add with several categories selected, they are added to
    categoryList and the new NSMutableArray is set via setCategoryList for
    that DataView object.)

    Categories are Category objects which is a class that consists of
    NSMutableString *name.

    All classes have key value coded accessors and the to-many
    relationships have indexed accessors.

    I am trying to show the current selected categories that exist in a
    selected DataView's categoryList. (The arrangement is very similar to
    the key path examples in the array operators documentation on apple's
    site.) Two NSArrayControllers in IB are bound to categories and
    dataViews.

    Whenever I bind the tableColumn to the dataViews controller with
    selectedObjects.categoryList.name I get the above error. I've also
    tried creating a third NSArrayController bound to the dataViews
    controller with selectedObjects.categoryList and then binding the
    table column to that controller with arrangedObjects.name which also
    doesn't work. The thing that I found interesting while I was testing
    is that binding the table column's value to the dataView controller
    with selectedObjects.categoryList produces different results from
    binding to the third controller with just arrangedObjects. The first
    way produces a liste of categoryLists equal to the number of DataView
    objects but they are all identical. (I.E. they are all the
    categoryList for the current DataView selected.) the second way shows
    just the categoryList for the selected DataView. (but I still can't
    access the name. It's just a list of Category: <memory address>,
    Category: <memory address> .... in the first row of the table.)

    Here are the relevant files instance variable headers:

    iOrganizer.h (the document controller):

    @interface iOrganizer : NSDocument {
    NSMutableArray *repository;
    NSMutableArray *categories;
    NSMutableArray *dataViews;

    SKIndexRef contentIndex;
    NSMutableData *indexData;
    NSString *indexName;

    IBOutlet NSTableView *categoryListTable;
    IBOutlet NSArrayController *dataViewsController;
    IBOutlet NSArrayController *categoriesController;
    /*
    IBOutlet NSArrayController *repositoryController;
    IBOutlet NSArrayController *linkedItemsController;
    //IBOutlet NSArrayController *viewDataController;
    */
    }

    DataView class:

    @interface DataView : NSObject {
    NSMutableString *name;
    NSMutableArray *categoryList;
    }

    Category Class:

    @interface Category : NSObject {
    NSMutableString *name;
    }

    P.S. -- I've tried this programatically as well (hence some of the
    currently unnecessary IBOutlets). Since I haven't created addObserver
    and removeObserver methods I can't bind to my NSMutableArray instances
    directly (If I understand how that works. I may not. Feel free to
    educate me) I was hoping to avoid that throught he use of
    NSArrayControllers. Would I need to create those functions for my
    class objects even though I'm using NSArrayControllers? Or have I
    missed the point?

    --Derrek
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • On 24. Aug 2004, at 17:34, Derrek Leute wrote:

    > I am getting the following errors with anything bound to a key path of
    > selectedObjects.categoryList.name
    >
    > 2004-08-24 01:29:49.582 iOrganizer[11421] [<NSCFArray 0x3816a0>
    > addObserver:forKeyPath:options:context:] is not supported.  Key path:
    > name

    That's because the array has no such key. What you want to bind to is
    really selectedObjects.categoryList[*].name -- where [*] means any
    element of the array (though this notation doesn't exist, the closest
    is addObserver:toObjectsAtIndexes:forKeyPath:options:context:).

    Since you are having a key path with two arrays then you need two array
    controllers, the latter one has the "contentArray" bound to
    "selectedObjects.categoryList" (of the first one), and the element
    which currently binds to the key path you quoted, should bind to the
    second array controller using "arrangedObjects.name".

    I am almost certain that an example of this exists on mmalcolm's page!
    --
        http://macromates.com/
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • Thanks for the response!

    I have tried this, but now the tableview isn't updating. I'm sending
    willChangeValueForKey:@"categoryList" (and
    didChangeValueForKey:@"categoryList") to the DataView object from my
    IBAction method. I've also tried @"categoryList.name". (Though, that
    shouldn't be necessary if I'm using appropriate get/set accessors?) I
    am using setCategoryList and getCategoryList to get and set the array
    as well. I suspect I just need to trigger something that tells the
    table to update.

    Any Ideas?

    The appropriate action method is included. I use an intermediate
    NSMutableSet to hold the information so I only have one reference to
    each category. (yeah, there's probably a better way to do that, but
    I'm more concerned with making it work. I also needed indexed
    accessors which I didn't know how to use if my array object was a
    set.)

    - (IBAction)addSelectedCategoriesToSelectedViews:(id)sender {
    NSMutableArray *selectedCategories = [[self getSelectedCategories] retain];
    if ([selectedCategories count] > 0) {
      NSMutableArray *selectedViews = [[self getSelectedViews] retain];
      if ([selectedViews count] == 1) {
      DataView *aView = [[selectedViews lastObject] retain];
      NSMutableSet *newListSet = [[NSMutableSet alloc] init];
      [newListSet addObjectsFromArray:[aView getCategoryList]];
      [newListSet addObjectsFromArray:selectedCategories];
      NSMutableArray *finalList = [[NSMutableArray alloc]
    initWithArray:(NSMutableArray *)[newListSet allObjects]];
      [aView willChangeValueForKey:@"categoryList"];
      [aView setCategoryList:(NSMutableArray *)finalList];
      [aView didChangeValueForKey:@"categoryList"];
      [newListSet release];
      [finalList release];
      [aView release];
      }
      [selectedViews release];
    }
    [selectedCategories release];
    }

    I feel like I'm very close to *really* grasping KVB/KVC/KVO.

    --Derrek

    On Tue, 24 Aug 2004 20:09:55 +0200, Allan Odgaard <ml...> wrote:
    > On 24. Aug 2004, at 17:34, Derrek Leute wrote:
    >
    >> I am getting the following errors with anything bound to a key path of
    >> selectedObjects.categoryList.name
    >>
    >> 2004-08-24 01:29:49.582 iOrganizer[11421] [<NSCFArray 0x3816a0>
    >> addObserver:forKeyPath:options:context:] is not supported.  Key path:
    >> name
    >
    > That's because the array has no such key. What you want to bind to is
    > really selectedObjects.categoryList[*].name -- where [*] means any
    > element of the array (though this notation doesn't exist, the closest
    > is addObserver:toObjectsAtIndexes:forKeyPath:options:context:).
    >
    > Since you are having a key path with two arrays then you need two array
    > controllers, the latter one has the "contentArray" bound to
    > "selectedObjects.categoryList" (of the first one), and the element
    > which currently binds to the key path you quoted, should bind to the
    > second array controller using "arrangedObjects.name".
    >
    > I am almost certain that an example of this exists on mmalcolm's page!
    > --
    > http://macromates.com/
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • On 24. Aug 2004, at 21:17, Derrek Leute wrote:

    > - (IBAction)addSelectedCategoriesToSelectedViews:(id)sender {
    > [...]

    I am really missing the context here and what all this needs to
    actually do, but, instead of this:

    > NSMutableArray *selectedCategories = [[self getSelectedCategories]
    > retain];

    Use: [self mutableArrayValueForKey:@"selectedCategories"];

    This will return a proxy which will generate the proper key/value
    notifications, when you insert/remove objects from the array.

    And you don't need the retain as far as I can tell.
    --
        http://macromates.com/
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • On Aug 24, 2004, at 12:17 PM, Derrek Leute wrote:
    > I have tried this, but now the tableview isn't updating. I'm sending
    > willChangeValueForKey:@"categoryList" (and
    > didChangeValueForKey:@"categoryList") to the DataView object from my
    > IBAction method.
    >
    Umm, why?  {will,did}ChangeValueForKey is typically something an object
    should send to itself so that observers can be informed of a change to
    its stage.

    > - (IBAction)addSelectedCategoriesToSelectedViews:(id)sender {
    > NSMutableArray *selectedCategories = [[self getSelectedCategories]
    > retain];
    > if ([selectedCategories count] > 0) {
    > NSMutableArray *selectedViews = [[self getSelectedViews] retain];
    > if ([selectedViews count] == 1) {
    > DataView *aView = [[selectedViews lastObject] retain];
    > NSMutableSet *newListSet = [[NSMutableSet alloc] init];
    > [newListSet addObjectsFromArray:[aView getCategoryList]];
    > [newListSet addObjectsFromArray:selectedCategories];
    > NSMutableArray *finalList = [[NSMutableArray alloc]
    > initWithArray:(NSMutableArray *)[newListSet allObjects]];
    > [aView willChangeValueForKey:@"categoryList"];
    > [aView setCategoryList:(NSMutableArray *)finalList];
    > [aView didChangeValueForKey:@"categoryList"];
    > [newListSet release];
    > [finalList release];
    > [aView release];
    > }
    > [selectedViews release];
    > }
    > [selectedCategories release];
    > }
    >
    In what class is this method implemented?

    There is probably no need for the additional retain/release pairs
    within this block, and no need for the casts.

    Assuming you have not disables automatic KVO notifications, there
    should be no need to call {will,did}ChangeValueForKey -- if these are
    called they should be called by the object that is changing, not the
    object making the change.

    You should only need two array controllers; one manages the data views,
    the other the category list.  The category list's 'contentArray' value
    should probably be bound to:

    [DataViewArrayController].selection.categoryList

    mmalc
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • This method is implemented in the iOrganizer.m file which is the main
    app controller for a document based project. It seemed the best place
    to put it. Would the more appropriate place be in DataView?

    The casts were left over from when I was trying to use an NSMutableSet
    directly for the categoryList instance variable. I eventually just
    switched to an NSMutableArray entirely. (but I still expect
    categoryList to contain only one of any Category. hence the copying
    objects into an NSMutableSet before setting a new array)

    I have created that arrangement that both you and Allan have
    suggested. Now when I push the button associated with add, it doesn't
    crash but nothing shows up in the table view connected.

    Just figured it out. It needed a clean all for the project. It's
    working exactly as it should now. :)

    Thanks for your help!

    (By the way, your examples have been incredibly useful. Particularly
    manual bindings and groups.)

    Do you, by any chance, have a working example of using target
    arguments bindings for buttons? I feel like that should do what I'm
    looking for without writing any IBActions. (though I could be
    mistaken.) I haven't been able to get that to work without crashing in
    any situation (But those problems could easily have been connected to
    these as well.)

    --Derrek

    On Tue, 24 Aug 2004 15:25:58 -0700, mmalcolm crawford
    <mmalc_lists...> wrote:
    >
    > On Aug 24, 2004, at 12:17 PM, Derrek Leute wrote:
    >> I have tried this, but now the tableview isn't updating. I'm sending
    >> willChangeValueForKey:@"categoryList" (and
    >> didChangeValueForKey:@"categoryList") to the DataView object from my
    >> IBAction method.
    >>
    > Umm, why?  {will,did}ChangeValueForKey is typically something an object
    > should send to itself so that observers can be informed of a change to
    > its stage.
    >
    >> - (IBAction)addSelectedCategoriesToSelectedViews:(id)sender {
    >> NSMutableArray *selectedCategories = [[self getSelectedCategories]
    >> retain];
    >> if ([selectedCategories count] > 0) {
    >> NSMutableArray *selectedViews = [[self getSelectedViews] retain];
    >> if ([selectedViews count] == 1) {
    >> DataView *aView = [[selectedViews lastObject] retain];
    >> NSMutableSet *newListSet = [[NSMutableSet alloc] init];
    >> [newListSet addObjectsFromArray:[aView getCategoryList]];
    >> [newListSet addObjectsFromArray:selectedCategories];
    >> NSMutableArray *finalList = [[NSMutableArray alloc]
    >> initWithArray:(NSMutableArray *)[newListSet allObjects]];
    >> [aView willChangeValueForKey:@"categoryList"];
    >> [aView setCategoryList:(NSMutableArray *)finalList];
    >> [aView didChangeValueForKey:@"categoryList"];
    >> [newListSet release];
    >> [finalList release];
    >> [aView release];
    >> }
    >> [selectedViews release];
    >> }
    >> [selectedCategories release];
    >> }
    >>
    > In what class is this method implemented?
    >
    > There is probably no need for the additional retain/release pairs
    > within this block, and no need for the casts.
    >
    > Assuming you have not disables automatic KVO notifications, there
    > should be no need to call {will,did}ChangeValueForKey -- if these are
    > called they should be called by the object that is changing, not the
    > object making the change.
    >
    > You should only need two array controllers; one manages the data views,
    > the other the category list.  The category list's 'contentArray' value
    > should probably be bound to:
    >
    > [DataViewArrayController].selection.categoryList
    >
    > mmalc
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • On Aug 24, 2004, at 5:32 PM, Derrek Leute wrote:

    > This method is implemented in the iOrganizer.m file which is the main
    > app controller for a document based project. It seemed the best place
    > to put it. Would the more appropriate place be in DataView?
    >
    I'd be tempted to split the functionality, and implement something like
    an 'addToCategoryListIfAbsent:(NSArray *)' method to DataView since
    this is likely to be more generally useful.  It would then post its own
    change notification if the list is indeed changed.  Something like
    (typed in Mail -- I may have overlooked something):

    - (void) addToCategoryListIfAbsent:(NSArray *)additions
    {
    NSMutableSet *additionsSet = [[NSMutableSet alloc]
    initWithArray:additions];
    NSSet *currentSet = [[NSSet alloc] initWithArray:categoryList];

    [additionsSet minusSet:currentSet];
    [currentSet release];

    unsigned int additionsSetCount = [additionsSet count];
    if (additionsSetCount > 0)
    {
      NSRange range = NSMakeRange([categoryList count], additionsSetCount);
      NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:range];

      [self willChange:NSKeyValueChangeInsertion
      valuesAtIndexes:indexes forKey:@"categoryList"];
      [categoryList addObjectsFromArray:[additionsSet allObjects]];
      [self didChange:NSKeyValueChangeInsertion
      valuesAtIndexes:indexes forKey:@"categoryList"];
    }
    [additionsSet release];
    }

    You would then reimplement 'addSelectedCategoriesToSelectedViews:' to
    call that method with the appropriate argument.

    > (By the way, your examples have been incredibly useful. Particularly
    > manual bindings and groups.)
    >
    Thank you...

    > Do you, by any chance, have a working example of using target
    > arguments bindings for buttons? I feel like that should do what I'm
    > looking for without writing any IBActions. (though I could be
    > mistaken.) I haven't been able to get that to work without crashing in
    > any situation (But those problems could easily have been connected to
    > these as well.)
    >
    ... unfortunately not that I can distribute publicly.  I'll see if I
    can submit it as sample code.

    mmalc
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • On Aug 24, 2004, at 10:02 PM, mmalc wrote:

    >
    > - (void) addToCategoryListIfAbsent:(NSArray *)additions
    > {
    ...
    }

    Just want to say, this is a nice handy routine.. after I figured out
    the IndexSet stuff. I'll keep a note of it.
    I just had one question. In this line:

    [categoryList addObjectsFromArray:[additionsSet allObjects]];

    Any reason not to do:

    [categoryList addObjectsFromArray: additions];

    ?

    Just curious.

    Thanks.

    jt
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • On Aug 24, 2004, at 9:55 PM, John Tsombakos wrote:
    > On Aug 24, 2004, at 10:02 PM, mmalc wrote:
    >> - (void) addToCategoryListIfAbsent:(NSArray *)additions
    >> {
    > ...
    > }
    > Just want to say, this is a nice handy routine..
    >
    Cheers, but please note that, as stated, it was (rather
    uncharacteristically) typed into Mail.  It was invented on the spur of
    the moment, and should be tested!  And there may well be better
    implementations.

    > [categoryList addObjectsFromArray:[additionsSet allObjects]];
    > Any reason not to do:
    > [categoryList addObjectsFromArray: additions];
    >
    Yes: any objects already in the category list have been removed from
    additionsSet -- this way no object is added to the array more than
    once.  (Assuming my logic is correct.)

    mmalc
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • > From: mmalcolm crawford <mmalc_lists...>
    > Date: 2004/08/25 Wed AM 01:43:32 EDT
    > To: John Tsombakos <johnts...>
    > CC: <cocoa-dev...>
    > Subject: Re: Problem with key paths and key value observing
    >
    >
    > On Aug 24, 2004, at 9:55 PM, John Tsombakos wrote:
    >> On Aug 24, 2004, at 10:02 PM, mmalc wrote:
    >>> - (void) addToCategoryListIfAbsent:(NSArray *)additions
    >>> {
    >> ...
    >> }
    >> Just want to say, this is a nice handy routine..
    >>
    > Cheers, but please note that, as stated, it was (rather
    > uncharacteristically) typed into Mail.  It was invented on the spur of
    > the moment, and should be tested!  And there may well be better
    > implementations.
    >

    Point taken and understood :)
    >> [categoryList addObjectsFromArray:[additionsSet allObjects]];
    >> Any reason not to do:
    >> [categoryList addObjectsFromArray: additions];
    >>
    > Yes: any objects already in the category list have been removed from
    > additionsSet -- this way no object is added to the array more than
    > once.  (Assuming my logic is correct.)

    Ah. Of course. Thanks for clarifying.

    jt
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
  • This brings up the question of where these methods should be placed.
    In my data model I will have another object that also contains a
    categoryList that will be added to in the same way (and removed from.
    I have a reverse method that does the exact same thing in reverse). I
    had planned to make my IBActions responsible for working with the
    appropriate class instance and then doing the work. If I split the
    functionality up, should I just duplicate the code between the
    DataView object and whatever other object I'm using with the same
    functionality?

    --Derrek

    > I'd be tempted to split the functionality, and implement something like
    > an 'addToCategoryListIfAbsent:(NSArray *)' method to DataView since
    > this is likely to be more generally useful.  It would then post its own
    > change notification if the list is indeed changed.  Something like
    > (typed in Mail -- I may have overlooked something):
    >
    > - (void) addToCategoryListIfAbsent:(NSArray *)additions
    > {
    > NSMutableSet *additionsSet = [[NSMutableSet alloc]
    > initWithArray:additions];
    > NSSet *currentSet = [[NSSet alloc] initWithArray:categoryList];
    >
    > [additionsSet minusSet:currentSet];
    > [currentSet release];
    >
    > unsigned int additionsSetCount = [additionsSet count];
    > if (additionsSetCount > 0)
    > {
    > NSRange range = NSMakeRange([categoryList count], additionsSetCount);
    > NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:range];
    >
    > [self willChange:NSKeyValueChangeInsertion
    > valuesAtIndexes:indexes forKey:@"categoryList"];
    > [categoryList addObjectsFromArray:[additionsSet allObjects]];
    > [self didChange:NSKeyValueChangeInsertion
    > valuesAtIndexes:indexes forKey:@"categoryList"];
    > }
    > [additionsSet release];
    > }
    >
    > You would then reimplement 'addSelectedCategoriesToSelectedViews:' to
    > call that method with the appropriate argument.
    >
    >> (By the way, your examples have been incredibly useful. Particularly
    >> manual bindings and groups.)
    >>
    > Thank you...
    >
    >> Do you, by any chance, have a working example of using target
    >> arguments bindings for buttons? I feel like that should do what I'm
    >> looking for without writing any IBActions. (though I could be
    >> mistaken.) I haven't been able to get that to work without crashing in
    >> any situation (But those problems could easily have been connected to
    >> these as well.)
    >>
    > .... unfortunately not that I can distribute publicly.  I'll see if I
    > can submit it as sample code.
    >
    > mmalc
    _______________________________________________
    cocoa-dev mailing list | <cocoa-dev...>
    Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
    Do not post admin requests to the list. They will be ignored.
previous month august 2004 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 29
30 31          
Go to today