Sorting Core Data data

  • Hello all,

    Core Data seems to be a great thing, so I'm working on moving my old
    FileMaker DB's.

    With one of the tables, the order of the entities is essential. Let's
    say, I have a table of cities visited, together with time stamps of
    the visit, and I want to calulate the travel distance. Since I can't
    guarantee in which order those visits are added to the database, I
    have to sort them by day when or after fetching, right?

    Having found a few quite good tutorials on the net, storing data and
    viewing it via an NSArrayController works fine already, but none of
    these tutorials seems to bother with sorting.

    How should I approach sorting without loosing too much of Core Data's
    advantages?

    Thanks,
    Markus

    - - - - - - - - - - - - - - - - - - -
    Dipl. Ing. Markus Hitter
    http://www.jump-ing.de/
  • On May 30, 2005, at 3:19 AM, Markus Hitter wrote:

    > With one of the tables, the order of the entities is essential.
    > Let's say, I have a table of cities visited, together with time
    > stamps of the visit, and I want to calulate the travel distance.
    > Since I can't guarantee in which order those visits are added to
    > the database, I have to sort them by day when or after fetching,
    > right?
    > Having found a few quite good tutorials on the net, storing data
    > and viewing it via an NSArrayController works fine already, but
    > none of these tutorials seems to bother with sorting.
    > How should I approach sorting without loosing too much of Core
    > Data's advantages?
    >
    You mean, how do you sort the itinerary in the table view?  Click on
    the table column header.

    How do you sort the cities in memory?  You can, as you imply, add a
    sort descriptor to your fetch request -- see the tutorial that does
    bother with sorting:

    <http://developer.apple.com/documentation/Cocoa/Conceptual/
    CoreDataUtilityTutorial/07_Fetch/chapter_8_section_2.html
    >

    "Create and Issue the Fetch Request
    The first step is to create the fetch request. You want to fetch
    instances of the Run entity and order the results by recency."

    Otherwise, if you want to sort in memory, again you can do so using
    the array of cities and a sort descriptor:
    - (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors

    or (if your array is mutable):

    - (void)sortUsingDescriptors:(NSArray *)sortDescriptors

    mmalc
  • Thanks for the help, mmalcolm

    Am 30.05.2005 um 13:05 schrieb mmalcolm crawford:

    > On May 30, 2005, at 3:19 AM, Markus Hitter wrote:
    >
    >
    >> With one of the tables, the order of the entities is essential.
    >> Let's say, I have a table of cities visited, together with time
    >> stamps of the visit, and I want to calulate the travel distance.
    >> Since I can't guarantee in which order those visits are added to
    >> the database, I have to sort them by day when or after fetching,
    >> right?
    >> Having found a few quite good tutorials on the net, storing data
    >> and viewing it via an NSArrayController works fine already, but
    >> none of these tutorials seems to bother with sorting.
    >> How should I approach sorting without loosing too much of Core
    >> Data's advantages?
    >>
    >>
    >>
    > You mean, how do you sort the itinerary in the table view?  Click
    > on the table column header.
    >

    While this works for the view, it doesn't change anything in the
    NSArrayController's contents array.

    > How do you sort the cities in memory?  You can, as you imply, add a
    > sort descriptor to your fetch request -- see the tutorial that does
    > bother with sorting:
    >
    > <http://developer.apple.com/documentation/Cocoa/Conceptual/
    > CoreDataUtilityTutorial/07_Fetch/chapter_8_section_2.html>
    >

    To my understanding, I'd have to do all fetches manually, then.
    Loosing automatic fetching through the binding mechanism?

    > Otherwise, if you want to sort in memory, again you can do so using
    > the array of cities and a sort descriptor:
    > - (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors
    >

    I'd have two arrays, then. One created by the automatic fetch and
    used by the view, another one used for calculations. How would I map
    changes back to the db?

    > or (if your array is mutable):
    >
    > - (void)sortUsingDescriptors:(NSArray *)sortDescriptors
    >

    This was quite useful already. Stuff is sorted in the array, and
    after a view update, in the view as well. Only problem left is, the
    sorting has to be triggered after each edit.

    Learning as I'm reading and discussing, I've found the
    NSArrayController accepts sortDesriptors as well. Not being an
    bindings expert, I applied them this way:

    (I write this for the records and in the hope _not_ to blame
    mmalcolm, his hints were very helpful)

    1) In the class XxxAppDelegate (you get one when using the CoreData
    template), add a key-value-coding-compliant property:

        @interface FahrtenbuchAppDelegate : NSObject {
        ...
        NSArray *sortDescriptors;
        }
        - (NSArray *)sortDescriptors;
        ...

    2) Implement this property. As an example:

        @implementation FahrtenbuchAppDelegate

        - (NSArray *)sortDescriptors {

            if (sortDescriptors) {
                return sortDescriptors;
            }

            sortDescriptors = [NSArray arrayWithObject:
    [[NSSortDescriptor alloc]
                initWithKey:@"abfahrtszeit" ascending:YES]];

            return sortDescriptors;
        }
        ...

    3) In Interface Builder, bind this property to the NSArrayController,
    right above the binding to the managedObjectContext:4) Works like a charme, without any glue code fiddling.

    Thanks again and have fun,
    Markus

    - - - - - - - - - - - - - - - - - - -
    Dipl. Ing. Markus Hitter
    http://www.jump-ing.de/
  • On May 30, 2005, at 11:49 AM, Markus Hitter wrote:
    >>> How should I approach sorting without loosing too much of Core
    >>> Data's advantages?
    >> You mean, how do you sort the itinerary in the table view?  Click
    >> on the table column header.
    > While this works for the view, it doesn't change anything in the
    > NSArrayController's contents array.
    >> How do you sort the cities in memory?  You can, as you imply, add
    >> a sort descriptor to your fetch request -- see the tutorial that
    >> does bother with sorting: [...]
    > To my understanding, I'd have to do all fetches manually, then.
    > Loosing automatic fetching through the binding mechanism?
    >
    These two comments seem inconsistent?  Do you want the *display*
    sorted, or the *in-memory* array sorted?

    >> Otherwise, if you want to sort in memory, again you can do so
    >> using the array of cities and a sort descriptor:
    >> - (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors
    > I'd have two arrays, then. One created by the automatic fetch and
    > used by the view, another one used for calculations. How would I
    > map changes back to the db?
    >
    I'm not sure what the problem is here? -- the managed objects are the
    same in both arrays.

    > Learning as I'm reading and discussing, I've found the
    > NSArrayController accepts sortDesriptors as well. Not being an
    > bindings expert, I applied them this way:
    >
    This is, in effect, what happens when you click on a table column
    header... :-)
    Do you want to ensure the user doesn't re-order the data?

    mmalc
  • Am 30.05.2005 um 21:38 schrieb mmalcolm crawford:

    >
    > On May 30, 2005, at 11:49 AM, Markus Hitter wrote:
    >
    >>>> How should I approach sorting without loosing too much of Core
    >>>> Data's advantages?
    >>>>
    >>> You mean, how do you sort the itinerary in the table view?  Click
    >>> on the table column header.
    >>>
    >> While this works for the view, it doesn't change anything in the
    >> NSArrayController's contents array.
    >>
    >>> How do you sort the cities in memory?  You can, as you imply, add
    >>> a sort descriptor to your fetch request -- see the tutorial that
    >>> does bother with sorting: [...]
    >>>
    >> To my understanding, I'd have to do all fetches manually, then.
    >> Loosing automatic fetching through the binding mechanism?
    >>
    > These two comments seem inconsistent?  Do you want the *display*
    > sorted, or the *in-memory* array sorted?

    Both.

    Even more, I want to make sure the data existing, but not viewed, is
    sorted as well.

    ... and I don't know how to get the binding mechanism to use a
    specialized fetch description.

    >>> Otherwise, if you want to sort in memory, again you can do so
    >>> using the array of cities and a sort descriptor:
    >>> - (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors
    >>>
    >> I'd have two arrays, then. One created by the automatic fetch and
    >> used by the view, another one used for calculations. How would I
    >> map changes back to the db?
    >>
    > I'm not sure what the problem is here?

    Since the order of the objects is part of the information they
    represent ("how many km/miles were it to drive to Berlin?" - it
    depends on where the car drove previously), one of the arrays would
    be wrong. I'd have to replace the unsorted array with the sorted one.
    Sorting and replacing again each time something changes.

    >> Learning as I'm reading and discussing, I've found the
    >> NSArrayController accepts sortDesriptors as well. Not being an
    >> bindings expert, I applied them this way:

    > This is, in effect, what happens when you click on a table column
    > header... :-)

    Are you sure about this? I looked at the data with:

        for (i=0; i<[[arrayController content] count]; i++) {
            NSLog(@"%@", [[[arrayController content] objectAtIndex:i]
    valueForKey:@"abfahrtszeit"]);
        }

    Clicking in the table column header didn't change anything for the in-
    memory-array. Binding the sortDescriptor did. And since the array
    with the sort descriptor is correctly sorted from the beginning,
    there is no need to duplicate the array after fetching it or to
    display the view twice.

    The sorting provided by the table view seems to be a view-internal
    thing. This is sufficient for a lot of applications, I must admit.
    "in effect" depends on the POV :-)

    > Do you want to ensure the user doesn't re-order the data?

    Yes.

    Cheers,
    Markus

    - - - - - - - - - - - - - - - - - - -
    Dipl. Ing. Markus Hitter
    http://www.jump-ing.de/
  • On May 30, 2005, at 1:54 PM, Markus Hitter wrote:
    > Am 30.05.2005 um 21:38 schrieb mmalcolm crawford:
    >> On May 30, 2005, at 11:49 AM, Markus Hitter wrote:
    >> [...] Do you want the *display* sorted, or the *in-memory* array
    >> sorted?
    > Both.
    > Even more, I want to make sure the data existing, but not viewed,
    > is sorted as well.
    >
    OK.
    The view (table view bound to an array controller) will manage the
    view sorting for you.  It's up to you to keep the in-memory array
    sorted.  But ... (*)

    > ... and I don't know how to get the binding mechanism to use a
    > specialized fetch description.
    >
    In the way I suspect you intend, you can't.

    (*)  What you can do is bind the contentArray of the array controller
    to an array in your "app controller" (if you're using a document-
    based application, this would be your NSDocument subclass -- in your
    case it looks like it would be in FahrtenbuchAppDelegate).  You can
    then populate this array however you wish (including using a custom
    fetch request).  If you modify this array in a KVO-compliant manner,
    then your table view will be kept in sync.

    For example, in the Bookmarks example at:
        <http://homepage.mac.com/mmalc/CocoaExamples/controllers.html>
    you could implement (in MyDocument.m):

    - (IBAction)swapFirstAndLast:sender
    {
        int count1 = [self countOfBookmarksArray] - 1;
        id first = [[[self objectInBookmarksArrayAtIndex:0] retain]
    autorelease];
        id last = [[[self objectInBookmarksArrayAtIndex:count1] retain]
    autorelease];

        [self removeObjectFromBookmarksArrayAtIndex:count1];
        [self removeObjectFromBookmarksArrayAtIndex:0];

        [self insertObject:last inBookmarksArrayAtIndex:0];
        [self insertObject:first inBookmarksArrayAtIndex:count1];
    }

    >>> I'd have two arrays, then. One created by the automatic fetch and
    >>> used by the view, another one used for calculations. How would I
    >>> map changes back to the db?
    >> I'm not sure what the problem is here?
    > Since the order of the objects is part of the information they
    > represent ("how many km/miles were it to drive to Berlin?" - it
    > depends on where the car drove previously), one of the arrays would
    > be wrong. I'd have to replace the unsorted array with the sorted
    > one. Sorting and replacing again each time something changes.
    >
    I'm not sure how this related to "mapping changes back to the db"?
    The persistent store does not store ordered elements.

    >>> Learning as I'm reading and discussing, I've found the
    >>> NSArrayController accepts sortDesriptors as well. Not being an
    >>> bindings expert, I applied them this way:
    >> This is, in effect, what happens when you click on a table column
    >> header... :-)
    > Are you sure about this?

    Umm, yes...

    > I looked at the data with:
    > for (i=0; i<[[arrayController content] count]; i++) {
    > NSLog(@"%@", [[[arrayController content] objectAtIndex:i]
    > valueForKey:@"abfahrtszeit"]);
    > }
    >
    Try:
            NSLog(@"%@", [[[arrayController arrangedObjects]
    objectAtIndex:i] valueForKey:@"abfahrtszeit"]);

    Also try creating a subclass of NSArrayController and overriding
    setSortDescriptors:
    - (void)setSortDescriptors:(NSArray *)sortDescriptors

    {
        NSLog(@"sort descriptors: %@", sortDescriptors);
        [super setSortDescriptors:sortDescriptors];
    }

    > The sorting provided by the table view seems to be a view-internal
    > thing.  This is sufficient for a lot of applications, I must admit.
    > "in effect" depends on the POV :-)
    >
    Not really (POV) -- both set the sort descriptors for the array
    controller.

    >> Do you want to ensure the user doesn't re-order the data?
    >> Yes.
    >
    See "Disabling sorting in a tableview" at    <http://
    homepage.mac.com/mmalc/CocoaExamples/controllers.html>
    -- this is "in effect" (or in this case "in fact") what you've done
    by binding the sortDescriptors (as opposed to leaving it unbound).

    mmalc
  • Am 31.05.2005 um 00:19 schrieb mmalcolm crawford:

    > On May 30, 2005, at 1:54 PM, Markus Hitter wrote:
    >
    >> Am 30.05.2005 um 21:38 schrieb mmalcolm crawford:
    >>
    >>> On May 30, 2005, at 11:49 AM, Markus Hitter wrote:
    >>> [...] Do you want the *display* sorted, or the *in-memory* array
    >>> sorted?
    >>>
    >> Both.
    >> Even more, I want to make sure the data existing, but not viewed,
    >> is sorted as well.
    >
    > OK.
    > The view (table view bound to an array controller) will manage the
    > view sorting for you.  It's up to you to keep the in-memory array
    > sorted.  But ... (*)

    With your earlier idea to use -[arrayController arrangedObjects] I'm
    now positive I can do my object-relative calculations without
    bothering with the array provided by -content. -arrangedObjects seem
    to be always in sync with what's actually displayed in the table view.

    > See "Disabling sorting in a tableview" at    <http://
    > homepage.mac.com/mmalc/CocoaExamples/controllers.html>

    Binding the array controller to the whole table view instead of it's
    columns? I tried, including some variations, but got an empty table
    view always.

    Instead, binding single array controller properties to single table
    columns (like before) and unchecking "Creates Sort Descriptor" in the
    column's binding panel had the effect to disable sorting triggered by
    clicking in the column header.

    Thanks to your help, mmalcolm, (and my patience) I think I have the
    sorting stuff working now.

    As described earlier, I bound a sortDescriptor to the
    arrayController. Then, in a convenient place (-
    [FahrtenbuchAppDelegate managedObjectContext]) I registered for -
    isEditing:

        [arrayController addObserver:self forKeyPath:@"isEditing"
    options:0 context:nil];

    The method triggered by the observer looks like:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)
    object change:(NSDictionary *)change context:(void *)context {
        if ([keyPath isEqual:@"isEditing"]) {
            // isEditing == 0 means editing did end.
            if ([arrayController isEditing] == 0) {
                // calling [arrayController rearrangeObjects] directly
    would mean to
                // re-enter NSArrayController which is asking for trouble.
                [[NSRunLoop currentRunLoop] performSelector:@selector
    (rearrangeObjects) target:arrayController argument:nil order:0 modes:
    [NSArray arrayWithObject:NSDefaultRunLoopMode]];
            }
        }
    }

    Now I can edit the table to my hearts contents. Sorting is updated
    after each edit and I have a hook to trigger my (re-)calculations.

    Cheers,
    Markus

    - - - - - - - - - - - - - - - - - - -
    Dipl. Ing. Markus Hitter
    http://www.jump-ing.de/
  • On Jun 1, 2005, at 9:31 AM, Markus Hitter wrote:
    > With your earlier idea to use -[arrayController arrangedObjects]
    > I'm now positive I can do my object-relative calculations without
    > bothering with the array provided by -content. -arrangedObjects
    > seem to be always in sync with what's actually displayed in the
    > table view.
    >
    Umm, yes, that's the definition of 'arrangedObjects' (I assume that
    table columns are bound to arrangedObjects.keyPath).

    >> See "Disabling sorting in a tableview" at    <http://
    >> homepage.mac.com/mmalc/CocoaExamples/controllers.html>
    >>
    > Binding the array controller to the whole table view instead of
    > it's columns?

    No, you still have to bind the table columns -- the example shows
    that there are additional bindings for the table view that are
    usually established for you automatically, but which you can override
    (well, you can override sortDescriptors -- overriding the others
    doesn't really make sense).

    > As described earlier, I bound a sortDescriptor to the
    > arrayController. Then, in a convenient place (-
    > [FahrtenbuchAppDelegate managedObjectContext]) I registered for -
    > isEditing:
    > [arrayController addObserver:self forKeyPath:@"isEditing"
    > options:0 context:nil];
    >
    It may be better to do this in awakeFromNib, since there's always a
    chance you may change your code such that the managedObjectContext is
    required before the nib file is loaded.

    > The method triggered by the observer looks like:
    > - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)
    > object change:(NSDictionary *)change context:(void *)context {
    > if ([keyPath isEqual:@"isEditing"]) {
    > // isEditing == 0 means editing did end.
    > if ([arrayController isEditing] == 0) {
    > // calling [arrayController rearrangeObjects] directly
    > would mean to
    > // re-enter NSArrayController which is asking for trouble.
    > [[NSRunLoop currentRunLoop] performSelector:@selector
    > (rearrangeObjects) target:arrayController argument:nil order:0
    > modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
    > }
    > }
    > }
    >
    In general you should use the context argument (that you set when you
    invoke addObserver:...) to determine what observation notification
    you're receiving.  The context should uniquely identify the source,
    whereas the keyPath may not.

    mmalc
previous month may 2005 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