Sharing outfits between users in Maya’s Dress Up

Maya’s Dress Up is our most recent game, available on iOS devices. It’s a dressup/make over game where the player gets to style up Maya and her friends, using various clothes and accessories. Because we believed that players might like to publish their creations, we included sharing features, such as Publish on Facebook and Email to a Friend, eversince v.1.0.0. The player has been able to send an email to a friend, attaching a screenshot of an outfit she had created, along with a URL to the game’s website.

As part of our next game update, we wanted to let players import an outfit that a friend had sent them. In other words, if the receiving player had the game installed on her device, she could import the styling selections of her friend directly into the game.

The easiest and most obvious way to do that was to use Custom URL Schemes.

Custom URL Schemes

A URL is formatted like this: schemeName://host/path?query.

The scheme name of a URL (also called protocol) is its first part. For example http:// or ftp://. Except for the natively supported schemes, http://, https://, ftp://, mailto://, tel:// and sms://, iPhone provides the ability for developers to specify their own custom schemes.

When a custom scheme is called, the corresponding application is executed, with any optional parameters that may be passed on. For example this is the way the Facebook app is called from inside other applications. So, a custom URL is the way to run an iOS application externally via a hyperlink, with the ability of passing along additional parameters.

Passing data

The information we needed to pass through was an ‘outfit’. Outfits in the game are serialized in a format such as bgid|modelName|garment1|garment2|garment3. The custom url would then be something like: mayasdressup://?outfit=bgid|modelName|garment1|garment2|garment3. The part after the ‘?’ is the query of the URL.

Parsing data

When the user clicks on the custom URL or types it on the browser, our application is launched, carrying the query parameters. We can then parse the query string and extract the desired information. This happens in the application:didFinishLaunchingWithOptions: method.

- (BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize the application
[self initApplication];
// LaunchParamManager is a class I created to hold launch information. It will be used later in order to apply the launch options
launchParamsManager = [LaunchParamManager new];
BOOL result;
NSURL *url = [launchOptions objectForKey:@"UIApplicationLaunchOptionsURLKey"];
// Check the scheme to see if the scheme is the one we have defined for our application (your app may use other schemes as well, for example if you have implemented Facebook connect)
if ([[url scheme] isEqualToString:@"mayasdressup"]) {
[
launchParamsManager parseAndStoreAppLaunchParams:url];
result = YES;
}
else {
// iOS 3.2 has a slightly different behaviour,so we need to take that into account
if([[[UIDevice currentDevice] systemVersion] hasPrefix:@"3.2"]) {
[self application:application handleOpenURL:url];
}
result = NO;
}
return result;
}

We had to unserialize the data and store the Outfit object somewhere, so that we can access it later in the code, when the app had finished initialization. So we created a class called LaunchParamManagerthat does exactly that. This helps keep things organized.

NOTE: One important thing to remember here is that the various delegate methods need to be implemented in a certain way, so that the code works well in both multi-tasking and single-tasking devices. Make sure you do NOT implement the applicationDidFinishLaunching:method in your app, but rather go with application:didFinishLaunchingWithOptions:. Apple recommends that anyway.

Also you’ll need to implement application:handleOpenURL: This method is called when the application is already running in the background, on multi-tasking environments. Here’s what we did with it:

What it does is store the URL data in a simple dictionary object, so that they are around when needed later on.

After parsing the custom URL params and storing that information, we should apply them. We do this in 2 places: 1) when the application has just being launched (as soon as the UI was ready) and 2) when the application has just become active, after going to the background, at the applicationDidBecomeActive: method, because this is the method that executes when the app resumes in a multi-tasking environment.

Encoding

We didn’t want to show the exact structure of an outfit to our users, so we created a simple encoding function that created a non-readable representation of the outfit. We used a simple bit-shifting algorithm, which created urls that looked something like this:

I know this isn’t very pretty to look at, something that could probably alienate a user that finds this link in her email, but it is something that we couldn’t avoid.

One important issue to note here is the form of the URL after the encoding has been applied. As you may see, there are instances of the ampersand (&) and slash (/) characters. Unfortunately these characters are also used as seperators when parsing the query. Remember that a URL goes something like ‘…/foo/?param=value&param2=value2&param3=value3’.

In our case, the & was an actual character, not a separator. So we had to escape the string. We tried [NSString stringByAddingPercentEscapesUsingEncoding:] but this encoded some characters, while leaving the ampersands and slashes intact (this took about 2.5 hours to notice, because the encoded string was big and difficult to read…)

The solution came with a CoreFoundation function, CFURLCreateStringByAddingPercentEscapes().

his created a string that was indeed properly formatted and I could parse it when the custom URL loaded.

Conclusion

Implementing a custom URL scheme with an extra encoding layer was not very difficult but it wasn’t very straightforward either. I had to look up a lot of stuff online throughout this and I ran into a few issues. We are launching this sharing feature on our next app update (probably within 2 weeks) and we hope users will much fun with it!