iOS Region Monitoring after iPhone Restart

I have been working with CLLocationManager a bunch, and in my well cell covered area of Naperville, region monitoring (or “geofencing”) on iOS 5 works great. I can define a circular area and quit my app, and when I enter or exit that region, BAM, my app gets relaunched in the background and I get notified. Works well.

However, it didn’t work so well after you rebooted the iPhone. I expected it to continue to work exactly as if I had launched the application and then exited it. My app does not go in the background, and I verified that it terminated when I pressed the home button (well, it it technically goes in the background for a few seconds and then terminates, but whatever). I did some intense logging, and drove around my neighborhood a bunch of times until I figured it out. There is 3 pieces that I was missing:

1. After iOS is rebooted, my app was launched in the background with :

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

with the UIApplicationLaunchOptionsLocationKey key. Prior to rebooting, if I just terminated the app by pressing the home button, I get relaunched in the background with that key when a boundary is crossed and as soon as I hook up my delegate:

lm=[[CLLocationManager alloc] init];
[lm setDelegate:self];

I get the region notifications. I was not getting those notifications after reboot.
The problem was this: I was rebooting my phone and then driving around the block immediately. Indeterminately, I was getting launched with the UIApplicationLaunchOptionsLocationKey but did not get any notifications when I hooked up the delegate. This seemed to contradict what the docs said and what happened when the app was launched and then quit. However, after banging my head against it for a long while, I realized that the app is launched with the UIApplicationLaunchOptionsLocationKey almost exactly at 3 mins after booting up without any boundary crossing. So that notification I was expecting was never coming. I suspect that launchd is set up to either launch my app when location services are ready, or after a timed delay. Either way, I stopped looking for reasons that I wasn’t getting notified and just waited for 3 minutes. I think of it as a “preheat” period.
The app also doesn’t get launched until the phone is unlocked the first time. I never got launched until the phone was unlocked. Lots of moving parts.

2. You have to include the following keys and values in the info.plist:

UIBackgroundModes :{location}
UIRequiredDeviceCapabilities: {location-services}
I didn’t think I needed the “UIBackgroundModes” since I wasn’t running in the background and the docs seemed to imply that the UIBackgroundModes is only needed when running continuously in the background . My app was getting launched 3 minutes after reboot in the background, but never given any boundary crossing locations when I drove around the block. As soon as I added in the location key for UIBackgroundModes, I started to get notified when a boundary was crossed. I thought that since I was getting launched after reboot that I didn’t need the UIBackgroundModes: {location}. I was wrong. If you don’t have it, you get launched after reboot, but no notifications are sent when you cross a boundary until you launch the app at least once.
3. I put my delegate for CLLocationManager in a view controller. However, the view controller was loaded by the XIB and XIB was not loaded when my app was in the background. I refactored the code so that I have a new object that handles all the region boundaries, and instantiate it from the app delegate. This works well. The docs make this pretty clear, but the sample app “Regions” from Apple does this same thing.
I understand that the documentation was try to make boundary crossing simple (you just register regions and you get notified), but without understanding how each piece works, it was hard to figure out why it never worked (and why some times parts of it did).
I just rebooted my phone, waited 3 minutes, and then drove around the block. 1/2 way around, I got a notification that a boundary was crossed. Hurah. Hopefully this will save someone a bit of time.