Back in June, at WWDC 13, Apple announced some new frameworks and hardware for game developers: Sprite Kit, Game Controllers and the MFI Game Controller spec.

The MFI Game Controller spec provided a specification for 3rd party hardware manufacturers to use when creating a game controller designed for iOS that Apple would be able to approve, allowing them to guarantee quality and compatibility to their customers.

The two companies to be making the first controllers were MOGA and Logitech, with an expected release timeframe to coincide with iOS 7. That fell short though and the controllers weren't available to purchase until late November.

The Controllers

The spec outlines two types of controllers:

Form-FittingControllers: Form-fitting controllers wrap around the device, much like a case and allow a variety of control methods to be combined, for example physical buttons on the controller combined with motion and touch control on the device. Since the controller is form fitting it will only fit a particular device, e.g. an iPhone. However, both MOGA and Logitech provide adaptors to fit iPods for their products.

Wireless GamePads: Wireless gamepads follow the more traditional Xbox / Playstation style controller. These controllers connect via bluetooth and will work with iPhones, iPod Touches, iPads and Macs. Since they are designed to be used from a distance, they don't combine physical controls with touch or motion.

There are also two controller profiles available for manufacturers to implement. a Controller Profile outlines the types of controls on the device:

Standard Profile: The standard profile provides a directional pad, four face buttons (A, B, X, Y) and two shoulder buttons.

Extended Profile: The extended profile has all of the controls of the standard profile while adding two triggers on the shoulders and two analogue thumb-stick controls.

Each button on the controller is designed to be analogue, meaning they are all pressure sensitive. And finally, every controller has a pause button and a set of 4 LED indicators to show which player is assigned to a controller.

MOGA Ace Power

MOGA were first on the scene with the Ace Power, a form-fitting controller with the extended profile and a built-in 1800mAh battery. The battery allows you to charge your iPhone while you play, allowing you extra time with that battery-killing 3D shooter that was previously impossible to play with touch controls.

One of the few criticisms I have of the Ace Power is that it's USB port doesn't pass through to the iPhone's Lightning Port, meaning debugging with it is impossible (unless Apple decides to bring back wireless debugging again). At one point I was reduced to adding a text view to the screen and logging to it in order to have an idea of what values I was getting from the controls.

Sonic The Hedgehog running in Provenance with improvised debugging enabled

Next, whatever firmware is running inside the controller tends to crash now and again. When this happens you'll have to reset the controller using the recessed reset button on the back using a paper clip (or an iPhone SIM tool). So far it has only happened to me 3 times in many hours of use, so it's hopefully not a big issue.

And my final criticism involves the nature of the analogue buttons. Even though it feels like the button is pressed, sometimes it needs a little more pressure before the game registers it as being pressed. This could possibly be Apple's fault as the documentation states that a button's pressed state will be YES for analogue values in excess of 0.25. Equally, it could be MOGA's fault for making the button feel like its pressed when its value only reads around 0.1.

I worked around that final criticism in my own games by reading the value of a button directly, as opposed to using the 'digital' pressed property. While this skirts around the issue of missing some button presses it also bypasses the hysteresis that Apple applies in order to filter out accidental or incidental button presses. However, I think for some games that responsive buttons are more important.

Game Controller Framework

Apple's Game Controller Framework receives a lot of praise from me for being simple, easy to understand and easy to implement.

Connection/Discovery

Detecting physically connected controllers is extremely easy. Once they're connected the framework posts the GCControllerDidConnect notification, and the GCControllerDidDisconnect notification when it's disconnected. There is the controllers class method on GCController that returns an array of already connected/paired controllers.

Pairing wireless controllers is more involved. If a controller is not already paired, then you must initiate the pairing process using

startWirelessControllerDiscovery
WithCompletionHandler:  

The completion handler will be called when the discovery process finishes or is cancelled.

Apple provides no UI for pairing controllers, as, in theory, it should just work, however, the documentation recommends creating your own UI if you want to allow the user to select which controller to use if multiple controllers are connected.

Thankfully, once a controller is paired with a device it will be remembered and will automatically connect when it is powered on, meaning that once the initial pairing is done, you should only need to listen for the notifications to tell when they're connected or disconnected.

Once a controller is connected you can set its playerIndex which will illuminate the corresponding LED on the controller if the index is between 0 and 3. You can unset the playerIndex by passing GCControllerPlayerIndexUnset, which will turn off any lit LEDs.

iPhone 5s inside the MOGA Ace Power

Reading Inputs

As I said previously, all the buttons are analogue, but some can be treated digitally by accessing the pressed property. Again, pressed returns YES if a button's analogue value is in excess of 0.25.

You can read a button's analogue value directly using its value property. This is a normalised value between 0.0 and 1.0 for buttons and -1.0 and 1.0 for axes such as the directional pad and thumb-sticks. This means you don't have to worry about calibration or deadzone calculations (particularly for thumb-sticks and directional pads). Apple manages the deadzone calculations for you and if a control is within its deadzone, it's value will be 0.0.

In addition to accessing the inputs directly, which is common for games that poll the controller, you can also set block handlers for controls that will be executed when a control's value changes. You can set block handlers on individual controls, groups of controls or even the entire controller.

The pause button is the only button on the controller that cannot be polled, and thus you must set the controllerPausedHandler property, where you will implement whatever logic you need to pause your game. Users will expect a quick and easy way pause games and so Apple highly encourages this behaviour to be implemented.

It is important to be aware of which profile the connected controller is using, which can be determined by the gamepad and extendedGamepad properties. If the controller doesn't support a given profile then it will return nil. Technically, all controllers support the gamepad profile, so it should only be the extendedGamepad profile that you need to test for.

And that's pretty much all there is to it. You should be able to get Game Controller up and running in your game very quickly and easily, so there's almost no excuse not to if you're a game dev (hint hint).

Things To Remember

There are a few things to remember with game controllers:

  • Apple won't allow you to require a game controller in order to play your game. You must still implement another control system like touch or motion.
  • The user may connect/disconnect a controller at any time while you game is still running
    • If a controller is connected while the game is running, pause the game and hide any onscreen controls.
    • If a controller is disconnected, pause the game and revert to whatever control system you would use when a controller is not present (touch or motion).
  • Always implement the pause handler.
    • Apple's documentation says that the pause handler should toggle between paused and running states. Even if your game is paused for another reason (modal alert, or backgrounding etc), then pressing the pause button should resume play. Your UI must also indicate that the game is paused and explain how to resume.
  • Not all game controllers will be physically attached to the device.
    • There is an attachedToDevice property that will inform you whether or not the controller is physically connected to the device, like a form-fitting controller would be. You can use this property to determine whether or not you can combine touch or motion controls with the game controller to enhance the gameplay.
    • You should also use it to know when a controller is not attached to the device so that you can hide touch controls or provide other means of controlling your game's UI using the controller. (Just think when Apple finally allow 3rd party apps on the AppleTV, no touch screen there ;) but I digress).
  • While Apple won't let you require a game controller for your app, you can require a particular profile for your game controller support.
    • For example, if you are making a shooter that requires both analogue controls in order to maneuver the player you can require the extended profile.
    • In the case where the connected controller doesn't fit the required profile for your game, Apple states that it is more appropriate to ignore the controller than provide a sub-standard experience and to continue using whatever control scheme you would use without a controller.
  • If your game doesn't do it already, remember to disable the idle timer so that the screen doesn't dim/lock while playing.
    • The controller inputs aren't detected as user interaction by the OS, so the screen will lock after the timeout if you don't disable the idle timer.

While playing with Sprite Kit recently I ran into an odd crasher when backgrounding the app that threw me for a loop.

The stack trace was showing gpus_ReturnNotPermittedKillClient: as the offending method. Of course this isn't in my code anywhere.

After some trial and error with the code I tracked the crash down to sounds being played. Even if a sound had been played and was now finished it would still crash. A little further investigation led me to find out that this crash was related to AVAudioSession  when being backgrounded. Sprite Kit uses AVFoundation under the hood to make it easy for you to play your sounds and music within your game.

But AVAudioSession apparently cannot be active while in the background (it causes the crash if it is)... The fix was simple: deactivate the audio session when the app enters the background and reactivate it when it returns.

//
//  SSAppDelegate.m
//  Space Ship
//
//  Created by James Addyman on 18/12/2013.
//  Copyright (c) 2013 James Addyman. All rights reserved.
//

#import "SSAppDelegate.h"

@import AVFoundation;

@implementation SSAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{   
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    [[AVAudioSession sharedInstance] setActive:NO error:nil];
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [[AVAudioSession sharedInstance] setActive:NO error:nil];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
}

- (void)applicationWillTerminate:(UIApplication *)application
{
}

@end

Since Sprite Kit sets up the audio session for you within the framework, I would expect this to be handled by the framework and not crash your app in this way. I will be filing a bug report with Apple and I suggest everyone else does the same so that they fix it as soon as possible. I'll update this post when I get a radar number so it can be used to dupe the issue.

I recently came up against an issue in an iOS app where the development server for the web service I needed to access was using a self-signed SSL certificate.

If you've ever come across this scenario before, you'll know that iOS will not accept the certificate, as it is untrusted - so how do you access the web service?

NSURLConnection provides a couple of delegate APIs to handle these kinds of situations:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

In the first method, you'll need to return YES or NO depending on whether or not your delegate object can authenticate against the different types of protection spaces. In this case, we're interested in a protection space that uses the authentication method: NSURLAuthenticationMethodServerTrust. So if it's that kind, return YES, like so:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

Secondly, we need to manually tell the URL connection that the certificate is trusted by us, like this:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if ([challenge.protectionSpace.host isEqualToString:@"myhostname.com"])
        {
            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
        }
    }

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

Notice that the above code is checking the authentication challenge for the hostname provided in the authentication. If the hostname is equal to the hostname we are expecting, then we tell the authentication challenge that we'd like to trust the certificate.

Et voila! The URL connection now succeeds when using a self-signed SLL certificate!

It should be noted that this was for a development environment where I had no control over the cert being used or how it was originally signed. In no cases should a production server be using a self-signed untrusted cert.

Whether the web service you're working with is using HTTP Basic Auth or HTTP Digest Auth, as long as you're using NSURLConnectionNSURLCredential has you covered.

Apple have made it very easy for us developers. All we have to do is implement a delegate method.

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge previousFailureCount] < 0)
    {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
        return;
    }

    NSURLCredential *credential = [NSURLCredential credentialWithUser:aUsername 
                                                                 password:aPassword 
                                                              persistence:NSURLCredentialPersistenceForSession];
    [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}

And that's it. NSURLCredential will handle both HTTP Basic and Digest auth for you, including all the ugly base64 encoding and MD5 hashing.

Apple has a couple of weeds taking root in their walled garden recently.

The most recent being the Pokemon fiascos. There are a few "apps" in the App Store that are currently occupying alarmingly high rankings in the top app listings. One of these is "Pokemon Yellow". "Wow, Pokemon Yellow is in the App Store!" Hold it right there.

It's a scam, (you knew that, right?) According to the countless 1 Star reviews, the "game" simply opens, displays a picture of one of the newer Pokemon Nintendo DS games (not even Pokemon Yellow) and then promptly closes, leaving the poor sap who just paid 99c feeling a little more than dumb.

So what's the problem here? The fact that someone is ripping off Mega Giant Nintendo for a quick buck? The fact that the app doesn't work as advertised? That fact it's blatantly a scam? These are all big problems, but, for me, they aren't the problem. Sure people are losing out on their money - but individually it's only 99c, it probably costs more for a bag of Haribo. Collectively, though, that is a lot of money. The amount of downloads it takes to get that high in the rankings leads to serious money.

However, the real problem is how the hell did this manage to get approved in the first place?! This is precisely the type of thing Apple's review process is designed to prevent. Yet here we are, with hundreds (if not thousands) of people buying into false promises, having their money taken from them in dishonest ways. This is exactly what Apple tried to get rid of.

As a developer myself, I have had apps rejected in the past. Some of them have been silly reasons, some have been more sensible, but all of them have been in line with their Published Guidelines (requires developer login).

Examples include:

  • Having an app rejected because the table cell selection states were persistent, which contradicts the Human Interface Guidelines.
  • Having an app rejected for using a circular, blue 'plus' button as an 'invite friend button', when its intended use is for adding existing contacts. (Again, with the HIG).
  • Attempting to sell an in-app purchase that used the name "Premier League" in its title. I was told I needed written consent from the copyright holder in order to use its trademarks. Perfectly fair enough.

So how the hell did this Pokemon Yellow get past these insanely picky reviewers?

The app uses Nintendo trademarks, copyrights and imagery for starters. If it was enough for my "Premier League" IAP to be rejected, why isn't it enough for this one?

Not only that, the app doesn't work as advertised. Unless, it says "will immediately close after opening" in the description (which, I couldn't find at all, so maybe I'm missing it?). Again, this on its own should be enough for rejection.

Finally, the app, even if it did work, offers no substantial benefit or use whatsoever, yet another reason, which by itself should result in rejection.

What the hell Apple? This place is meant to be a haven from all this shit.

The only thing I can think of that would lead to this is an inside job. Maybe one of the reviewers wanted a quick buck and could successfully hide his true identity, like a real super villain. Where's Batman when you need him?