Re: NSSortDescriptor not working with array of custom classes

  • > On Oct 19, 2007, at 2:26 PM, Jim Correia wrote:
    >
    >> Have you been able to isolate the behavior down to a standalone
    >> sample?
    >>
    >> I find that to be useful because it shows me my bug, or gives me
    >> something I can dump directly into radar. If you do that, send me
    >> the sample too, because I'm curious what is going on here :-)
    >>

    Well I ran into this problem again with another class and after
    several hours of head scratching,  it appears that when initializing
    sort descriptors you *must* use the actual name of any property you
    want to sort on. You can't use accessors to those properties.

    For example: I have a custom page view class whose datasource is a
    reference to a page model class and the model class stores page data
    in an internal NSMutableDictionary. In testing, all the accessor
    methods worked but when used to initialize a sort descriptor, they
    all return null

    // next line shows that "pages" is indeed an NSArray
    NSLog(@"pages class: %@", [pages class]);

    // next line shows that the first page is a valid "PMPageView" instance
    NSLog(@"first page: %@", [pages objectAtIndex: 0]);

    // next 4 lines all fetch the page number correctly
    NSLog(@"direct call page view accessor: %i", [[pages objectAtIndex:
    0] pageNumber]);
    NSLog(@"direct call model accessor: %@", [[[pages objectAtIndex: 0]
    model] pageNumber]);
    NSLog(@"direct call model raw accessor: %@", [[[[pages objectAtIndex:
    0] model] valueForKeyPath: @"pageRecord"] objectForKey:
    @"page_number"]);
    NSLog(@"direct call model raw accessor 2: %@", [[[pages
    objectAtIndex: 0] model] valueForKeyPath: @"pageRecord.page_number"]);

    // these all return null
    NSLog(@"page view accessor: %@", [[pages objectAtIndex: 0]
    valueForKeyPath: @"pageNumber"]);
    NSLog(@"model accessor: %@", [[pages objectAtIndex: 0]
    valueForKeyPath: @"model.pageNumber"]);
    NSLog(@"model raw accessor: %@", [[pages objectAtIndex: 0]
    valueForKeyPath: @"model.pageRecord.page_number"]);

    // all of the following sort descriptors fail
    [desc addObject: [[NSSortDescriptor alloc] initWithKey: @"pageNumber"
    ascending: YES]];
    [desc addObject: [[NSSortDescriptor alloc] initWithKey:
    @"model.pageNumber" ascending: YES]];
    [desc addObject: [[NSSortDescriptor alloc] initWithKey:
    @"model.pageRecord.page_number" ascending: YES]];


    // next line fails to sort the page array
    NSArray                *sortedPages        = [pages sortedArrayUsingDescriptors: desc];

    // If I define a valueForKeyPath function for the "PMPageView" class
    like so
    - (id) valueForKeyPath:(id) inKey
    {
    if ([inKey isEqualToString: @"model.pageNumber"])
      return [model pageNumber];
    else
      return [super valueForKeyPath: inKey];
    }

    // the following works, but having to write such an ugly hacked
    "valueForKeyPath" method shouldn't be necessary
    [desc addObject: [[NSSortDescriptor alloc] initWithKey:
    @"model.pageNumber" ascending: YES]];
    NSArray                *sortedPages        = [pages sortedArrayUsingDescriptors: desc];

    I must be confused about how to correctly define sort descriptors or
    perhaps KVC, but I would think that using accessor names in a key
    path should work. Is that not the case?

    Any help appreciated.

    Ken
  • On Nov 4, 2007, at 2:50 PM, Ken Tozier wrote:

    >
    >> On Oct 19, 2007, at 2:26 PM, Jim Correia wrote:
    >>
    >>> Have you been able to isolate the behavior down to a standalone
    >>> sample?
    >>>
    >>> I find that to be useful because it shows me my bug, or gives me
    >>> something I can dump directly into radar. If you do that, send me
    >>> the sample too, because I'm curious what is going on here :-)
    >>>
    >
    > Well I ran into this problem again with another class and after
    > several hours of head scratching,  it appears that when initializing
    > sort descriptors you *must* use the actual name of any property you
    > want to sort on. You can't use accessors to those properties.

    Can you post a complete code sample that shows the problem you are
    reporting. We are having to guess at what your class(es) involved
    really look like. You can simplify it down to some thing that shows
    the issue you having.

    Anyway you are doing things that I shouldn't have to do...

    -Shawn
  • On Nov 4, 2007, at 6:05 PM, Shawn Erickson wrote:

    > Can you post a complete code sample that shows the problem you are
    > reporting. We are having to guess at what your class(es) involved
    > really look like. You can simplify it down to some thing that shows
    > the issue you having.

    That last post was pretty much where the problem is, but here's a
    couple of other methods to flesh it in

    // page view class is defined like so:
    @interface PMPageView : NSView
    {
    PMPageModel    *model;

    BOOL    isEven,
          active,
          selected;

    KIntegerField  *pageNumberField;

    NSColor    *fillColor;

    int      pageNumber,
          clickCount;

    unsigned int  selectionModifiers;

    NSRect    intFrame;
    }

    /* if uncommented, sorting works but it just seems silly to have to
    do this
    - (id) valueForKeyPath:(id) inKey
    {
    if ([inKey isEqualToString: @"model.pageNumber"])
      return [model pageNumber];
    else
      return [super valueForKeyPath: inKey];
    }
    */

    // page model class is defined like so:
    @interface PMPageModel : NSObject
    {
    PMPublicationModel  *publication;
    NSMutableDictionary  *pageRecord;
    PHPInvocation  *invocation;
    NSString    *color;
    }

    // User types in an NSTextFiled to change the number of a page.
    Custom page matrix superview receives a notification in it's
    "observeValueForKeyPath" method

    - (void) observeValueForKeyPath:(NSString *) inKeyPath
      ofObject:(id) inObject
      change:(NSDictionary *) inChange
      context:(void *) inContext
    {
    if ([inKeyPath isEqualToString: @"pageRecord.page_number"])
    {
      [self handlePageNumberChange];
    }
    }

    // observeValueForKeyPath calls handler for the page number change
    - (void) handlePageNumberChange
    {
    [self sortPageViews];

    // update the records mod date (for observers)
    [self setRecordsModified: recordsModified + 1];
    }

    // handlePageNumberChange calls "sortPageViews" method (Note:
    sortPageViews is used by other methods so it's been factored into its
    own method)

    - (void) sortPageViews
    {
    NSLog(@"pages before sortine: %@, class: %@", pages, [pages class]);
    NSMutableArray  *desc = [[NSMutableArray alloc] init];

    // these all print valid values to the run log
    NSLog(@"first page: %@", [pages objectAtIndex: 0]);
    NSLog(@"direct call page view accessor: %i", [[pages objectAtIndex:
    0] pageNumber]);
    NSLog(@"direct call model accessor: %@", [[[pages objectAtIndex: 0]
    model] pageNumber]);
    NSLog(@"direct call model raw accessor: %@", [[[[pages
    objectAtIndex: 0] model] valueForKeyPath: @"pageRecord"]
    objectForKey: @"page_number"]);
    NSLog(@"direct call model raw accessor 2: %@", [[[pages
    objectAtIndex: 0] model] valueForKeyPath: @"pageRecord.page_number"]);

    // these all print null
    NSLog(@"page view accessor: %@", [[pages objectAtIndex: 0]
    valueForKeyPath: @"pageNumber"]);
    NSLog(@"model accessor: %@", [[pages objectAtIndex: 0]
    valueForKeyPath: @"model.pageNumber"]);
    NSLog(@"model raw accessor: %@", [[pages objectAtIndex: 0]
    valueForKeyPath: @"model.pageRecord.page_number"]);

    // all of these fail unless I define "
    [desc addObject: [[NSSortDescriptor alloc] initWithKey:
    @"pageNumber" ascending: YES]];
    //[desc addObject: [[NSSortDescriptor alloc] initWithKey:
    @"model.pageNumber" ascending: YES]];
    //[desc addObject: [[NSSortDescriptor alloc] initWithKey:
    @"model.pageRecord.page_number" ascending: YES]];
    //[desc addObject: [[NSSortDescriptor alloc] initWithKey:
    @"model.pageNumber" ascending: YES]];

    NSArray    *sortedCells  = [pages sortedArrayUsingDescriptors: desc];

    NSLog(@"pages after sortine: %@", sortedCells);

    [pages release];
    pages    = [[sortedCells mutableCopy] retain];

    [self updateCellFramesWithWidth: [self frame].size.width
      forceUpdate: YES];
    }

    Ken
  • On Nov 4, 2007, at 3:25 PM, Ken Tozier wrote:

    >
    > On Nov 4, 2007, at 6:05 PM, Shawn Erickson wrote:
    >
    >> Can you post a complete code sample that shows the problem you are
    >> reporting. We are having to guess at what your class(es) involved
    >> really look like. You can simplify it down to some thing that shows
    >> the issue you having.
    >
    > That last post was pretty much where the problem is, but here's a
    > couple of other methods to flesh it in
    >
    > - (void) sortPageViews
    > {
    > NSLog(@"pages before sortine: %@, class: %@", pages, [pages class]);

    Which class is sortPageViews in? What does pages contain?

    > NSArray                *sortedCells        = [pages sortedArrayUsingDescriptors: desc];

    > [pages release];
    > pages                = [[sortedCells mutableCopy] retain];

    Why not use -[NSMutableArray sortUsingDescriptors] instead of the
    above? Also in the above you copy and retain sortedCells so you are
    ending up with an extra retain that isn't balanced by the one release
    in the above.

    -Shawn
previous month november 2007 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    
Go to today