HKWarnings on iOS6

I’ve noticed a few people running HKWarnings on iOS6 already. Hardcore early-adopters!

Number of iPads running iOS6: 15
Number of non-iPads running iOS6: 23

That’s out of 20,000 users, or so, so not many.

As with all new releases of iOS software there are changes and incompatibilities that may cause issues with your current apps that were built for a previous version of iOS.

We are not yet allowed to talk about the changes in iOS6:

Xcode 4.5 Developer Preview and iOS 6 beta are pre-release software and are considered Apple Confidential Information and are subject to the terms of your iOS Developer ProgramLicense Agreement. Unauthorized distribution or disclosure of Apple Confidential Information is prohibited.

We are also not yet allowed to submit apps built against the iOS6 SDK, so anything that is broken will have to stay broken for the moment.

So far I have only spotted a few issues with HKWarnings running on iOS6:

  1. An occasional startup crash when attempting to get a SecureUDID.
  2. A green background on the typhoon tracks page (see below!)
  3. The screens do not rotate if you hold your device upside-down.
  4. The Settings screen on the iPad version has a brown background, not grey.

For the interested:

  1. SecureUDID uses UIPasteboard. UIPasteboard is not thread-safe, and it seems thread timing has changed in iOS 6 to cause a crash.

  2. The TC Track screen uses a UIWebView to display the typhoon tracks. UIWebView uses WebKit, as does Safari, as does Google Chrome. The HTML I use is very simple:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
    <head>
    <title></title>
    </head>
    <body bgcolor="rgb(59, 59, 59)">
        <!-- insert urls to TC tracks here -->
    </body>
</html>

An example is here: testBG1.html

View that with an iOS device running 5.0 or 5.1 and the background is brown/iron. View it with iOS6, the background is green!

So I fired up Google Chrome, opened the Developer Tools window, loaded my test web page and noticed something called Matched CSS Rules.

No idea what that is, but it’s what’s causing the background to go green. So I figured if I wrote my own CSS for background-color, it might fix it:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
    <head>
    <style type="text/css">
        body{background-color:#3B3B3B;}
    </style>
    <title></title>
    </head>
    <body>
        <!-- insert urls to TC tracks here -->
    </body>
</html>

An example is here: testBG2.html

This new HTML with a bit of CSS produces a brown/iron background on iOS5 and iOS6. So that’s fixed. Maybe I just don’t know my HTML/CSS!

  1. Still under Apple’s NDA, but I haven’t figured it out yet.
  2. Same for this one, something changed with background colours on UITableViews.

So, for the early adopters of iOS6 using HKWarnings, please bear with me whilst I figure the issues out and wait for Apple to allow us to submit fixes.

Thanks.

Push Notifications with Urban Airship

The simplest way to set up your app for push notifications, I think, is to use Urban Airship? and their push product:

Urban Airship Push is a scalable, easy-to-implement solution that allows you to take advantage of the rich possibilities offered by push notifications?.

They say:

We can get you up and running within a few short hours?

It’s quicker than that.

Here are the steps I followed.

  • Join Urban Airship (UA)
  • Provision your app for push in the developer portal, generate a new certificate signing request and download your new certificate.
  • Create your app in UA and upload the certificate. UA have some instructions here.
  • You might need to regenerate your provisioning profile and reinstall – just to be safe.
  • Add the code to your app to register and handle push notifications. UA have some sample code.
  • Choose the right provisioning profile? and build.

That’s it! When the app starts up it registers with Apple for push notifications, if that’s successful it registers with UA. You are then ready to send notifications. You can test from the UA website.

You will need to write a service to push notifications to UA. I’ll cover that in another post.

Some gotchas and notes

Make sure you choose the right provisioning profile when building otherwise you will see the error below when you run:

no valid 'aps-environment' entitlement string found for application

Make sure you use a dev provisioning profile with the dev app you set up in UA or the prod/adhoc profile with the production app. Otherwise the Apple Push service will reject the device token and UA marks the token as inactive.

If you only ever need to broadcast to all devices, you do not need to keep a list of valid devices.

If you want to be able to send to individual devices or groups (using the UA tags) then you need to keep a list of valid devices and keep the list up to date by querying the UA feedback service.

The code you need to add to your app:

#define kApplicationKey @"YOUR APP KEY"
#define kApplicationSecret @"YOUR APP SECRET"

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

// reset notification badge count
application.applicationIconBadgeNumber = 0;

//Register for notifications
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert)];?

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)_deviceToken {

// Get a hex string from the device token with no spaces or < >
self.deviceToken = [[[[_deviceToken description] stringByReplacingOccurrencesOfString:@"<"withString:@""]
stringByReplacingOccurrencesOfString:@">" withString:@""]
stringByReplacingOccurrencesOfString: @" " withString: @""];

NSLog(@"Device Token: %@", self.deviceToken);

if ([application enabledRemoteNotificationTypes] == 0) {
NSLog(@"Notifications are disabled for this application. Not registering with Urban Airship");
return;
}

// this is straight out of the UA sample code
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
NSString *UAServer = @"https://go.urbanairship.com";
NSString *urlString = [NSString stringWithFormat:@"%@%@%@/", UAServer, @"/api/device_tokens/", self.deviceToken];
NSURL *url = [NSURL URLWithString: urlString];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
request.requestMethod = @"PUT";

// Authenticate to the server
request.username = kApplicationKey;
request.password = kApplicationSecret;

[request setDelegate:self];
[request setDidFinishSelector: @selector(registrationSuccessMethod:)]; // if you want to do something with the token
[request setDidFailSelector: @selector(requestWentWrong:)];
[queue addOperation:request];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *) error {
NSLog(@"Failed to register with error: %@", error);
}

- (void)requestWentWrong:(ASIHTTPRequest *)request {

[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSError *_error = [request error];

NSLog(@"ERROR: NSError query result: %@", _error);

UIAlertView *someError = [[UIAlertView alloc] initWithTitle:
@"Network error" message: NSLocalizedString( @"Error registering with notifiction server",
@"Error registering with notifiction server")
delegate: self
cancelButtonTitle: @"OK"
otherButtonTitles: nil];
[someError show];
[someError release];
}

If you want to store the device tokens then:

- (void)registrationSuccessMethod:(ASIHTTPRequest *) request {

[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

// send to your server
// however you like
}

The iPhoneOS handles push notifications when your app isn’t running, but what if you receive a push when your app is open? You need to implement:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSDictionary *apsInfo = [userInfo objectForKey:@"aps"];
NSString *alert = [apsInfo objectForKey:@"alert"];
NSString *sound = [apsInfo objectForKey:@"sound"];
NSString *badge = [apsInfo objectForKey:@"badge"];

// if you want to set the badge number
application.applicationIconBadgeNumber = [[apsInfo objectForKey:@"badge"] integerValue];

// more likely you want to set it to zero
application.applicationIconBadgeNumber = 0;

// play sound?

// show push msg
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Push Notification" message:alert delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}

Checking For An Internet Connection

[Via]

This code tells you if you have a network connection. You need the Reachability class from Apple.

#import "Reachability.h"

+(BOOL) doWeHaveInternetConnection{

	Reachability *hostReach = [[Reachability reachabilityWithHostName: @"www.google.com"] retain];
	NetworkStatus netStatus = [hostReach currentReachabilityStatus];
	BOOL isAvailable = NO;

	if (netStatus == NotReachable)
	{
		NSLog(@"NotReachable");
		isAvailable =  NO;
	}

	if (netStatus == ReachableViaWiFi)
	{
		NSLog(@"ReachableViaWiFi");
		isAvailable =  YES;

	}

	if (netStatus == ReachableViaWWAN)
	{
		NSLog(@"ReachableViaWWAN");
		isAvailable = YES;
	}

	[hostReach release];
	return isAvailable;
}

try/catch

How to use try/catch:

Cup *cup = [[Cup alloc] init]; ?

@try { ?       
     [cup fill]; ?
}
@catch (NSException *exception) {
     NSLog(@"main: Caught %@: %@", [exception name],  [exception reason]); ?
} ?       
@finally {
     [cup release]; ?
}