Thursday, April 5, 2012

iOS Photos App Map Pin Clustering

Screenshot of Sample App
While working on a new application for iOS, I wanted to replicate the way the Photos app does their map pin clustering when you look at where you've taken pictures.  I did a bunch of searching around and there is a nice solution at http://revolver.be/blog/mapkit-clustering-with-ios/  but it wasn't exactly what I wanted to do.  After putting together code from everything I've found, I was able to get it working well enough that I thought I'd share it.

I've created a project on GitHub - https://github.com/kazmiekr/MapPinClustering where you can download the code for the sample project.  The sample project shows a map of a handful of annotations in the Boston area, you can zoom in and out and watch the pins animate to their locations.  There are a bunch of pins in the downtown Boston area as well.

The core of the code is in the ClusteringMapView subclass of the MKMapView class that handles the logic of clustering the pins.  Basically it divides the map into squares using the 'distance' property and while looping over the annotations, it only shows one pin per square.  If there is already a pin displayed for that square, it sets that pin's cluster point to the shown pin.  Each annotation is a custom MKAnnotation class that contains it's actual location and it's clustered point so we can animate the property as we show and hide the pins.

There is a lot you could do to improve this.

  1. You could try to do fancy measurement logic using meters/miles instead of random boxes to determine what to show, but at a cost to performance.
  2. You could get really fancy with a k-means clustering algorithm
  3. You could come up with weighted points to ensure that cluster points with a higher amount of points ensure visibility over others.
All of that is cool, but this works for now.  Let me know what you think and I hope it's something you can use.




Tuesday, November 1, 2011

Working with Custom Fonts in an iOS App

Adding a custom font to your objective-c app is pretty easy.  I stumbled on the difference between when to use the filename versus the font name.  I thought I'd provide some tips on getting everything working.

General Steps

1. Obtain the font you want to use
2. Add the font filename to the *-Info.plist file for your application under 'Fonts provided by application'.  You must specify the exact filename matching the case of the name.
3. Obtain the name of the font to use in the application.  Sometimes the filename isn't the name that the font is registered by.  You can do this a number of ways.
   a) On a Mac, you can right click and show info on the font file, you'll see a property named 'Full Name'
   b) On a Mac, you could open the file with 'Font Book', and the name will be on the top.










   c) You could output the registered font names in code with
NSLog(@"Registered Fonts: %@", [UIFont familyNames]);
4. Use the font in your app with
[UIFont fontWithName:@"YOUR FONT NAME" size:30]

Thursday, October 27, 2011

Airplane Mode and Game Center

Update - I've recently learned this is a new feature of iOS5, where it caches scores and achievements if offline and resends the next time they connect.  They mentioned this feature in the Introduction to Game Center WWDC video.

While working on my iPhone game, MadBombz, I implemented leaderboards using Apple's Game Center.  Using the sample code provided by Apple in their Game Kit Programming Guide, it's a simple call using the GKScore object and reportScoreWithCompletionHandler.  If that call fails, it will provide a reference to the NSError associated with it.

GKScore *scoreReporter = 
 [[[GKScore alloc] initWithCategory:@"Your_Category"] autorelease];
scoreReporter.value = score;
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
 if (error != nil)
 {
  // You might think code in here would fire when in
  // Airplane Mode, but it doesn't.  It's magical.
 }
}];


Well I implemented a bunch of code to handle these score post failures so that they would repost the next time the user logs into Game Center only to find a really hard time recreating this situation.  My first thought was to enable 'Airplane Mode' while playing the game, but much to my surprise the user still authenticates and the score still claims to post even though I have no network connection.

After playing around with it a bit, I've determined that if you post a score while in Airplane Mode, the next time you sign in to the game, it automatically posts the score for you.  I'm not sure if this is the same case if you are posting achievements or not, but I'd expect it to be similar.

One other note is that there may be a difference in how Game Center authentication works between iOS4 and iOS5.  It seems that in iOS5, it re-authenticates the user when the app is shown ( either fresh launch or already running ) even if you have the authentication code in viewDidLoad, something causes your authenticateWithCompletionHandler to fire again.  I think this is due to the singleton nature of GKLocalPlayer, but I did not see this behavior in iOS4.  It seems the score gets posted during this re-authentication.

I couldn't find any exact reference to this behavior, but found a bunch of forum posts where other users had similar behavior.