Category Archives: iOS

Version 1.5 of Christian Radio Locator released

Today, we released version 1.5 of Christian Radio Locator. I know I said I was pretty much done with version 1.2, but there were a couple of things I really wanted to see if I could accomplish. Namely, I wanted to move from Google Docs to Amazon SimpleDB – which has been a very interesting transition that deserves its own blog post. I also wanted to learn more about how the In-App purchase process works, so you can now buy a “Pro Version” for $0.99. All future “big” features will only be available to those that have purchased the Pro version. Based on app download numbers and update numbers, I have a pretty good idea of how many people use the app. I’m very curious how many of those people are willing to pay to support future development. I’m guessing about 1%, which would be enough to cover the cost of hosting the database. The first feature to be Pro only is Favorites.

Here are the highlights of the release:

Favorites

2013 03 18 11 02 05

I’m very excited about this new feature. You can add a station as a favorite and it will always be displayed on the front screen, even if you are currently out of range of that station. This is useful for keeping up with your favorite home station when you are on the road, or to listen to a station that you have found in a different city. For me, I’ve discovered several great stations (Hot 95.9, Renegade Radio) that I now have quick access to directly from the home screen. This feature is only available to users that have made the in-app purchase to Pro.

Hide stations

2013 03 18 11 02 32

Some stations just aren’t your cup of tea. No judgement here. Just swipe on the station to prevent it from showing up. To unhide a station, click “Settings”, then “Edit Hidden stations”.

Download station’s app

2013 03 18 11 01 53

Many stations have their own iPhone or iPad app. We will display “Download station app” on the station detail screen. Click it to view it directly from our app. Sometimes it takes a second to show up. Be patient. Or don’t, sometimes clicking a bunch of times makes it load faster.

Smarter searches

Type just about anything in the search bar and we will be able to figure it out. Search by station name, call sign, frequency or city. Pull down slightly on home screen to see search bar.

Smarter launchers

Most of the stations have their own Facebook and Twitter pages. Rather than showing the Facebook page in Safari, we will open it directly in the Facebook app if you have it installed. For Twitter, we support the native Twitter app, Tweetbot and Echofon. As a bonus, Tweetbot supports callback urls, so you can visit a station’s twitter feed in Tweetbot and when you are done, it will relaunch Christian Radio Locator.

New tips

The help tips have been expanded and improved. Performing an action displayed in the help tip will advance you to the next tip. Click “Settings”, then “Hide tips” to prevent them from being shown. Click “Reset help tips” to start over from the beginning.

URL Schemes

Other developers can now launch Christian Radio Locator using our URL Schemes.

We support:
clrapp://search/q=<search string>
crlapp://search/id=<database id>
clrapp://map/city=<city name or lat/lng> 

Version 1.2 of Christian Radio Locator available for download

Badge1

Version 1.2 of Christian Radio Locator has been approved by Apple and is available in the App Store. The biggest feature of this release is the redesign and station grouping of the front screen. More release notes are available in a previous post. I also discussed the design process in much detail here and here.

Screen Shot 2013 01 18 at 7 33 26 PM

It was interesting that the app was in review for almost 15 HOURS!  Either they were just blown away with the new design, or they listen to every single station available on the app – all 1800 of them! Okay, it’s much more likely that they went home for the night and started up the next day. Until Apple provides more transparency to the app review process, we can only speculate.

If you are already like our Facebook fan page, you are awesome and I thank you. If not, please consider liking us. You can also follow @CRLApp on twitter.

Version 1.2 of Christian Radio Locator submitted to App Store

Yesterday, I submitted version 1.2 of Christian Radio Locator to the App Store. Apple reviewed and released the last version pretty quickly, so hopefully this version will be available in the next week or so.

New features

The biggest update is a redesign of the front screen. There are also some smaller improvements like including removing (instead of greying out) the “Listen now” button if that station doesn’t have an online stream. It will also open the stream directly in Safari if you long press the “Listen now” button. This is useful for stations that don’t support background streaming. The “Report Problem” screen is a little more contextually smart. You can no longer submit a “streaming not working” for a station that doesn’t have online streaming :) I got quite a few bug reports like that.

The future

With this update, the app is feature complete. All the original development goals have been hit. There are a few tweaks that would be nice, like better handling of edge case interruptions when streaming. It also runs a little slower than it probably should on the iPad 2. A separate, custom interface that takes advantage of the extra screen space of the iPad is probably something that would be fun to tackle in the future.

Android?

A common question for any iOS developer is “when is the Android version going to be released?” Well, considering this is just a fun side project, my wife and I have 3 kids under the age of 5 and I’m co-owner of a company, the answer is “not anytime soon if ever.” I’d like to move the backend database out of google spreadsheets into MSSQL and when I do that, I’ll probably put a REST interface on it so anybody can access the data. Maybe then, somebody else will create an Android or Window’s phone version. Technically, another developer could do it now, but the interface isn’t as specific to stations as it could be to allow another developer to really create a version on another platform with ease.

Screenshots

With this update, the existing screenshots were looking a bit rough, so those got updated as well.

2013 01 12 09 52 27

 

2013 01 12 09 53 11

 

2013 01 12 09 53 55

Latest redesign of CRL

I blogged earlier about redesigning the front screen of Christian Radio Locator. With my wife’s help, I think I’ve settled on a design that we will stick with for now. 

The most dramatic change was adding tint color to the Status bar and the Navigation bar. I didn’t like it at first, but it really started to grow on me after using it for a week or so. Hopefully other people like it as well. The same blue color that the icon uses as a base is used for the tint color. There is also now a launch image that is basically a blown up version of the icon. So when the app is opened, there is a nice flow of the icon almost growing into the app’s front screen.

The color in the cell separators was removed upon Chelsea’s prodding. Her exact words were “it looks amateur.” There is now a more subtle grey between the cells.

The app uses enormego’s Pull To Refresh table view, and they allow setting a background color of the view that is shown when the uses pulls down on the table. It is now set to the same color as the icon, but with a 50% opacity so it is less distracting and so the “black linen” still shows through. 

Here is a series of screen shots that shows the evolution:

Frontscreensmall

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.

Forcing myself to design

Being pretty is more important than being functional.

Honestly, I can’t even believe I am saying it. I’ve spent the majority of my development career focusing on functionality and performance. I place my entire programming philosophy on the idea that if the data is displayed accurately and quickly, users will not care about the facade that delivers the data. The only times I’ve deviated from battleship grey have been out of boredom, curiosity or after having received a donation from a real designer.

Then I got an iPhone. My own usage patterns forced me to take a second look at the importance of the window dressing around data. I find myself preferring well-designed apps rather then an app that has all the pertinent data. Extraneous or even missing data is almost excusable if the app itself is good looking.

So I’m forcing myself to make Christian Radio Locator look better. Luckily, my wife has a much better eye for design and she can help me out. After all, she did design the app’s icon.

I started by adding groups to the front page so only the stations with good signal strength are grouped together. Fringe stations and out of range stations have their own groups. I then changed the background to the black linen used by OS X. It was tricky to change the font and text color of the headers (had to override viewForHeaderInSection), and it feels a little hacked together. The header text is “indented” via prepended space characters. I’ll probably need to revisit that. 

Going with the darker theme, I changed the Status Bar to Black Translucent and the Navigator Bar andToolbar to Black Opaque. Initially, I set the Navigator Bar and Toolbar to Black Translucent, but that made them “float” over the content, which wasn’t what I was after. To give it just a small amount of color, I changed the Cell separators to be turquoise with 50% opacity.

Here is the current before and after. I’d really appreciate any pointers.

Beforeandafter

Christian Radio Locator v1.1 in App Store

The latest update for Christian Radio Locator has been approved and is available in the App Store.  

Notable bug fixes include faster startup and including of frequency when submitting a new station (whoops). The station detail screen has been prettied up and the main screen will display “streaming available” for stations with online streaming on the iPad version or when the iPhone is in landscape mode. A complete change log can be found in the app (click “About”) or on this previous post.

Thanks for the kind words and please, please, please leave reviews in iTunes.