Archive for August, 2006

23rd Aug 2006

Core Data, Bindings, and Sorting in an NSTableView: The Easy Way

So I’m working on a little Core Data based application that you will hear more about in the future. In it, I have an NSTableView whose coulmn headers are bound to an NSArrayController, which in turn is bound to the managedObjectContext to get all my spiffy data.

Problem #1: Core Data doesn’t do sorting. Every time the app is launched, the data in the table appears randomly. Having it appear sorted on launch was actually really simple. Here’s the solution:

1. Implement a KVC NSArray object in your controller or delegate object, and have it return an NSArray with the default sortDescriptor (here I am sorting by a ‘date’ property):

- (NSArray *)sortDescriptor
{
	if(sortDescriptor == nil){
		sortDescriptor = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO]];
		}

	return sortDescriptor;
}

- (void)setSortDescriptor:(NSArray *)newSortDescriptor
{
	sortDescriptor = newSortDescriptor;
}

2. In Interface Builder, bind the sortDescriptors Controller Content Parameter to your controller or delegate object, and set the Model Key Path to whatever you called your NSArray of sort descriptors (in my case, sortDescriptor).

That’s it! It wasn’t as intuitive as I originally thought, but it certainly seems pretty clean to me.

BUT WAIT! We have a new problem now.

Problem #2: Objects that are inserted or changed in the table aren’t sorted properly. Fixing this problem was a little trickier, but I found a solution to it at CocoaDev’s RearrangeObjects page. Here’s my little implementation of it (although original credit should go to JediKnil):

1. Register to hear the NSManagedObjectContextObjectsDidChangeNotification, preferably somewhere in your app controller’s awakeFromNib method.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectsDidChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]];

2. Next, we implement the objectsDidChange: method:

- (void)objectsDidChange:(NSNotification *)note
{
       [myArrayController rearrangeObjects];
}

This forces the table to check it’s arrangement every time any object is changed, added, or deleted. Depending on your purposes, you may want to use JediKnil’s method which checks for certain properties changes, and avoids rearranging if unneeded.

That’s it! It took me a while to figure this out and I saw others were having the same problems else where and never really got answers, so hopefully this is useful to someone. Good luck!

Posted by Posted by patrick under Filed under bindings, code, coredata, how-to, sorting Comments 1 Comment »

15th Aug 2006

Hacking the Nike+iPod

I recently got a Nike+iPod sport kit and shoes. They are really damn cool. It does what it is meant to do quite well (tracking your run and giving you all kinds of fun motivating statistics) but I had always wondered how it recorded the information, and if there was anything else fun I could get it to do.

Luckily for everyone, Apple and nike chose to record this data as clean XML. Here’s what a workout file looks like:

<?xml version="1.0" encoding="UTF-8"?>
<sportsData><vers>1</vers>
<runSummary><workoutName>Basic</workoutName>
<time>2006-08-13T00:54:49-04:00</time>
<duration>1716812</duration>
<durationString>28:36</durationString>
<distance unit="km">4.3527</distance>
<distanceString>4.35 km</distanceString>
<pace>6:34 min/km</pace>
<calories>355</calories>
<battery></battery>
<playlistList><playlist><playlistName>Running</playlistName>
</playlist>
</playlistList>
<stepCounts><walkBegin>124</walkBegin>
<walkEnd>1316</walkEnd>
<runBegin>98</runBegin>
<runEnd>3028</runEnd>
</stepCounts>
</runSummary>
<template><templateID>8D495DCE</templateID>
<templateName>Basic</templateName>
</template>
<goal type="" value="" unit=""></goal>
<userInfo><empedID>5C6284WFVSX</empedID>
<weight>79.4</weight>
<device>iPod</device>
<calibration>000000004170000001fe00230000000041f0000004a1000000002b0000000000</calibration>
</userInfo>
<startTime>2006-08-13T00:54:49-04:00</startTime>
<snapShotList snapShotType="userClick"><snapShot event="onDemandVP"><duration>313248</duration>
<distance>0.92</distance>
</snapShot>
<snapShot event="onDemandVP"><duration>528706</duration>
<distance>1.548</distance>
</snapShot>
<snapShot event="onDemandVP"><duration>755721</duration>
<distance>2.222</distance>
</snapShot>
<snapShot event="onDemandVP"><duration>790638</duration>
<distance>2.284</distance>
</snapShot>
<snapShot event="onDemandVP"><duration>1066793</duration>
<distance>2.714</distance>
</snapShot>
<snapShot event="onDemandVP"><duration>1277388</duration>
<distance>3.341</distance>
</snapShot>
<snapShot event="onDemandVP"><duration>1410509</duration>
<distance>3.552</distance>
</snapShot>
<snapShot event="powerSong"><duration>1412404</duration>
<distance>3.554</distance>
</snapShot>
<snapShot event="stop"><duration>1716780</duration>
<distance>4.352</distance>
</snapShot>
</snapShotList>
<snapShotList snapShotType="kmSplit"><snapShot><duration>339859</duration>
<distance>1.0</distance>
</snapShot>
<snapShot><duration>677423</duration>
<distance>2.001</distance>
</snapShot>
<snapShot><duration>1168834</duration>
<distance>3.0</distance>
</snapShot>
<snapShot><duration>1558960</duration>
<distance>4.0</distance>
</snapShot>
</snapShotList>
<snapShotList snapShotType="mileSplit"><snapShot><duration>549808</duration>
<distance>1.611</distance>
</snapShot>
<snapShot><duration>1236915</duration>
<distance>3.219</distance>
</snapShot>
</snapShotList>
<extendedDataList><extendedData dataType="distance" intervalType="time" intervalUnit="s" intervalValue="10">0.0, 0.0201, 0.0331, 0.0608, 0.098, 0.13, 0.1586, 0.1899, 0.2219, 0.2542, 0.2858, 0.3174, 0.3447, 0.3745, 0.4045, 0.4323, 0.467, 0.4948, 0.526, 0.5579, 0.586, 0.6159, 0.6486, 0.6789, 0.706, 0.7344, 0.7678, 0.795, 0.8255, 0.8592, 0.8852, 0.9146, 0.9445, 0.9711, 1.0004, 1.0296, 1.0563, 1.0882, 1.1148, 1.1447, 1.1753, 1.2036, 1.2338, 1.2632, 1.2934, 1.3219, 1.3515, 1.3811, 1.4105, 1.4401, 1.4656, 1.4948, 1.5246, 1.5509, 1.5808, 1.6115, 1.6395, 1.6729, 1.7034, 1.7304, 1.7604, 1.7922, 1.8245, 1.8536, 1.8847, 1.9164, 1.9476, 1.9793, 2.0107, 2.0425, 2.0697, 2.1003, 2.1317, 2.1616, 2.1865, 2.2137, 2.2369, 2.2516, 2.2664, 2.2846, 2.2988, 2.3148, 2.3308, 2.3471, 2.3633, 2.3775, 2.3936, 2.41, 2.4263, 2.4407, 2.4581, 2.4716, 2.4872, 2.5027, 2.5192, 2.5334, 2.5487, 2.5662, 2.5804, 2.5962, 2.6108, 2.6263, 2.6424, 2.6584, 2.6744, 2.6902, 2.704, 2.7188, 2.7341, 2.75, 2.7794, 2.8115, 2.8469, 2.8768, 2.9089, 2.9377, 2.9728, 3.0067, 3.0369, 3.0706, 3.102, 3.1347, 3.1682, 3.2005, 3.2293, 3.2613, 3.2906, 3.3215, 3.3471, 3.3636, 3.3782, 3.3931, 3.411, 3.427, 3.4416, 3.4579, 3.4742, 3.4888, 3.5074, 3.5213, 3.5383, 3.5518, 3.5634, 3.5822, 3.5978, 3.6106, 3.6486, 3.6933, 3.7372, 3.7769, 3.8201, 3.8626, 3.9049, 3.9381, 3.9691, 3.986, 4.0022, 4.0185, 4.0331, 4.0489, 4.0654, 4.0831, 4.098, 4.1143, 4.1303, 4.1463, 4.176, 4.2112, 4.2456, 4.2796, 4.3102, 4.3404</extendedData>
</extendedDataList>
</sportsData>

As you can see, the extendedData tag holds the real meat of the workout: the distance ran (in kilometers, since that’s how I set mine up) recorded every 10 seconds. We also get interesting snapShot items, that tell us the exact moment, to 1/1000 of a second, when we hit certain kilometer or mile milestones, or when the user hit the Voice Feedback button (the “onDemandVP” event).

You can also see, closer to the top, basic workout info such as how many calories burned duration, distance, average pace, etc.

Some things of interest include the stepCounts tag, which I guess counts steps, but isn’t used at all in the Nike+iPod interface.

It’s cool that this data is totally open and free to be hacked upon, although I’m not quite sure what I would use if for just yet, as the Nike+iPod web app seems to handle pretty much all my needs thus far. It’s nice to know that should the Nike+ service ever die, writing your own desktop app (that is much more precise and feature-ful) would not be difficult.

Hopefully this will be of use to someone or even just the curious like me.

Posted by Posted by patrick under Filed under apple, hacking, nike+ipod Comments 3 Comments »