Category Archives: GPS

How to create, remove and manage geofence reminders in iOS programmatically with Xcode

I’ve been a fan of geofencing since way back in the days of Where’s Tim. I spoke at the University of Kansas back in 2006 about how exciting it would be to one day have location aware checklists.  Now, with mobile devices running iOS 6, we can take advantage of the reminders API to add and remove location-based items. This saves us from writing a ton of code to manage our own geofencing framework.

I use this feature in Christian Radio Locator to allow users to set alerts when they enter into listening range of a particular station.

To add a reminder, we make sure the device is running iOS 6 or greater, then ask the user for permission to access their reminders. We create a new reminder and set the title and location. Then the distance range of when to alert the user. This will be specific to the situation. For a reminder triggered by Target, you’d want it to be only a 100 meters or so to avoid drive-by triggering. For a “call my wife when I’m getting close to home” situation, the range would be 2000 or 3000 meters. If everything is configured correctly, the API will add the reminder to the system and hand back an eventID, which should be store internally in the application to allow the user to remove the reminder and to avoid entering duplicates.

To remove a reminder, pass in the eventID and tell the API to remove it from the system. There seems to be a five to ten second delay, which makes debugging difficult. We can also query the reminders to display them in our app and to make sure the user hasn’t checked any off from Reminders.app.

Connecting to Event store

Here is the code to get the Event store. The store object will process all the add and remove messages. As the code indicates, the store object is cached locally once is has been successfully retrieved. There is also an unknown delay between requesting the store and the system asking the user for permission. That is why the providedAccess ivar is set via a block.

- (EKEventStore*) getEventStore{

    if (eventStore) {

        return eventStore;

    }

    

    EKEventStore *store = [[EKEventStore alloc] init];

    __block BOOL providedAccess = YES;

    BOOL atLeastIOS6 = [[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0;

    if (!atLeastIOS6) {

        providedStoreAccess = NO;

        return nil;

    }

    [store requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {

        providedAccess = granted;

    }];

    [self setProvidedStoreAccess:providedAccess];

    [self setEventStore:store];

    return store;

}

Adding location-based reminder to store

Now that we have the store, we can add a location-based reminder that will use the built-in geofencing. This will also display the outlined GPS symbol on the users Status Bar so they know they have a location-based reminder. Note that we are storing the eventID in NSUserDefaults so we can retrieve the details about it later.

- (BOOL) addReminder{

    //make sure there is a store to get to

    EKEventStore *store = [self getEventStore];

    if (!store) {

        return NO;

    }

    

    //make sure the user provided access to the store

    if (!providedStoreAccess) {

        return NO;

    }

    EKReminder *reminder = [EKReminder reminderWithEventStore:store];

    [reminder setTitle:[NSString stringWithFormat:@"Listen to %@ (%@)",station.ownedBy,station.frequency]];

    [reminder setNotes:[NSString stringWithFormat:@"%@ %i mile range",station.city, (int)[station.range doubleValue]]];

    //create the geofence alarm

    EKAlarm *enterAlarm = [[EKAlarm alloc] init];

    [enterAlarm setProximity:EKAlarmProximityEnter];

    EKStructuredLocation *enterLocation = [EKStructuredLocation locationWithTitle:[NSString stringWithFormat:@"%@ (%@)",station.ownedBy,station.frequency]];

    CLLocationDegrees lat = [station.lat doubleValue];

    CLLocationDegrees lng = [station.lng doubleValue];

    CLLocation *radioLocation = [[CLLocationalloc] initWithLatitude:lat longitude:lng];

    [enterLocation setGeoLocation:radioLocation];

    //convert our range from miles to meters then set the radius

    //the alarm will go off when the user enters this radius

    [enterLocation setRadius:[station.range doubleValue] * 1609];

    [enterAlarm setStructuredLocation:enterLocation];

    [reminder addAlarm:enterAlarm];

    [reminder setCalendar:[store defaultCalendarForNewReminders]];

 

    NSError *err;

    [store saveReminder:reminder commit:YES error:&err];

    [self setEventId:reminder.calendarItemIdentifier];

    

    //store the event id so we can track if the user

    //has a geofence reminder for this station

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    NSDictionary *reminders = [defaults objectForKey:@"reminders"];

    NSMutableArray *latlng = [NSMutableArray arrayWithArray:reminders.allKeys];

    NSMutableArray *itemIds = [NSMutableArray arrayWithArray:reminders.allValues];

    

    [latlng addObject:[NSString stringWithFormat:@"%@%@",station.lat,station.lng]];

    [itemIds addObject:eventId];

    

    NSDictionary *newReminders = [NSDictionary dictionaryWithObjects:itemIds forKeys:latlng];

    [defaults setObject:newReminders forKey:@"reminders"];

    [defaults synchronize];

    

    return YES;

}

Removing reminders

If the user wishes to remove or check off the event, they can do so in reminders.app, or we can do it programatically.

- (BOOL) removeReminder {

    if (eventId.length == 0) {

        return NO;

    }

    EKEventStore *store = [self getEventStore];

    if (!store) {

        return NO;

    }

    if (!providedStoreAccess) {

        return NO;

    }

    

    EKReminder *reminder = (EKReminder *)[store calendarItemWithIdentifier:eventId];

 

    NSError *err;

    [store removeReminder:reminder commit:YES error:&err];

    

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    NSDictionary *reminders = [defaults objectForKey:@"reminders"];

    NSMutableArray *latlng = [NSMutableArray arrayWithArray:reminders.allKeys];

    NSMutableArray *itemIds = [NSMutableArray arrayWithArray:reminders.allValues];

    

    [latlng removeObject:[NSString stringWithFormat:@"%@%@",station.lat,station.lng]];

    [itemIds removeObject:eventId];

    

    NSDictionary *newReminders = [NSDictionary dictionaryWithObjects:itemIds forKeys:latlng];

    [defaults setObject:newReminders forKey:@"reminders"];

    [defaults synchronize];

    

    [self setEventId:@""];

    returnYES;

    

}

Staying in sync with Reminders.app

We want to be able to display to the user the reminders that our app has set for them. We also need to make sure that we stay in sync with the Event store in case the user checked off or deleted a reminder that our app created.

- (void) setReminderCellDisplay{

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    

    NSDictionary *reminders = [defaults objectForKey:@"reminders"];

    

    //check if the user has any stored reminders to display to them

    if (reminders.count == 0) {

        return;

    }

    

    EKEventStore *store = [self getEventStore];

    if (!store) {

        return;

    }

    if (!providedStoreAccess) {

        return;

    }

    NSMutableArray *remindersToRemove = [[NSMutableArray alloc] init];

    for (NSString *key in reminders) {

        //making sure that no events have been removed outside of this application

        //if so, we are going to remove it from the collection and not mark the reminder cell

        BOOL skip = NO;

        NSString *eventIdFromReminders = [reminders valueForKey:key];

        EKReminder *reminder = (EKReminder *)[store calendarItemWithIdentifier:eventIdFromReminders];

        if (!reminder || reminder.completed) {

            [remindersToRemove addObject:eventIdFromReminders];

            skip = YES;

            NSLog(@”Need to remove:%@”,eventIdFromReminders);

        }

        //this station does have a reminder associated with it

        if ([[NSString stringWithFormat:@"%@%@",station.lat,station.lng] isEqualToString:key] && !skip) {

            [reminderCell setAccessoryType:UITableViewCellAccessoryCheckmark];

            [self setEventId:eventIdFromReminders];

        }

    }

    //nothing to remove…carry on

    if (remindersToRemove.count == 0) {

        return;

    }

    

    NSMutableArray *latlng = [NSMutableArray arrayWithArray:reminders.allKeys];

    NSMutableArray *itemIds = [NSMutableArray arrayWithArray:reminders.allValues];

    

    //we have a reminder that we need to get rid of

    for (NSString *eventIdToRemove in remindersToRemove) {

        NSUInteger index = [itemIds indexOfObject:eventIdToRemove];

        [latlng removeObjectAtIndex:index];

        [itemIds removeObjectAtIndex:index];

        NSLog(@”Removed:%@”,eventIdToRemove);

    }

    

    NSDictionary *newReminders = [NSDictionary dictionaryWithObjects:itemIds forKeys:latlng];

    [defaults setObject:newReminders forKey:@"reminders"];

    [defaults synchronize];

}

Summary

This blog post showed how to create and manage location-based reminders for iPhones and iPads. Geofencing is a powerful tool that we can use to make our lives more productive…or to make sure we don’t miss any of our favorite radio stations.

Christian Radio Locator iPhone app

Mzl wqycyulm 175x175 75

For the last three months or so I’ve been working on an iPhone (and iPad) app in my spare time. It all started when I took the kids to Minneapolis and had a hard time finding radio stations to listen to on the trip. I looked in the App Store for an app that would use my GPS to show me Christian radio stations nearby, but there wasn’t one. So I decided to build my own.

Using public information from the FCC and a few other sources, I built a database in Google docs that contains the frequency for all Christian radio stations, where the tower is located and how far the tower can reach. I also included any streaming audio information and other contact information like Facebook or Twitter that I could find.

Google spreadsheets publish in JSON format (yes, really) and Xcode can automatically deserialize JSON into a properly formatted entity. This is one area that Xcode is far superior to C#. In a just a few lines of code, I can have a list of in-memory strongly typed objects from a web-based JSON feed. To accomplish the same thing natively in .NET would be much more work and wouldn’t feel nearly as clean when it was said and done.

The snazzy icon shown above was built by my very talented wife. She hasn’t yet provided any feedback on the app’s user interface, which is why it is so plain and boring.

I used a navigation view controller and EGO pull to refresh table view to construct the main window. Pulling down to refresh initiates a GPS lookup, which queries the database for radio stations in range (yes, you can pass parameters to Google spreadsheets and get a subset back in JSON). Pulling up on the table extends the range of the search and includes stations that may not be close enough to get clear audio. This feature is not that intuitive and the next version contains an update to that functionality.

Tapping a cell will show a detail view that displays additional information about the station. The user can click to view the station on a map, click to listen to an online stream (if available) or click to see the station’s Facebook or Twitter pages.

Swiping back and forth on the table changes the information that is displayed on the right hand side of the table cell. It scrolls through the city where the tower is located, how far the phone is from the tower, the range of the tower and in the next version a signal strength indicator. This was pretty easy to implement once I figured out how to assign the gesture recognizer delegate. 

Tapping and holding on a cell will jump the user to the map view screen. Which is pretty cool, but very hard for even a power user to discover. To tackle the issue of discoverability, the next version has a series of instructions displayed at the bottom of the screen to show the user the various shortcuts. Once the user has performed the swipes and long holds, the instructions disappear.

I’ve learned a lot developing this app. Spending over a decade exclusively in .NET made the learning curve a bit steep, but once I learned the structure and syntax of Objective-C, I’ve learned to appreciate the power and simplicity of it.

Here are a few screenshots. I would really appreciate any feedback and especially iTunes reviews. Technically it is open source and a smart googler could probably find it. I just haven’t promoted it as open source.

Mzl zlxgzdll 320x480 75

Mzl cxmnezxi 320x480 75

Mzl gceuawaz 320x480 75