Opening iOS URL Schemas (Deep Linking) in Unity

How do I make a custom URL/application schema (ie QTDeep://?color=green) for my Unity iOS app that I can open from a webpage/another app?

There’s a lot of people asking this question dating back over a year and most of the little tidbits you need to make it happen are out there. I’ve pieced it together and I’ll explain how to jam it in quick and dirty style, then we’ll wrap it up into a reusable plugin so let’s get cracking.

First thing we need to do is define the URL schemas our app supports, this is done in the iOS Player Settings under Other > Configuration. With that you can now open this app with QTDeep:// which might be good enough for some projects. I want to be able to pass arguments in. 

Screen Shot 2018-04-16 at 5.56.28 PM

For that we have to play around in Xcode a little and modify the AppController class. Yay Programming Time!

Quickest and Dirtiest:

In the Xcode project open UnityAppController.m and find the method

– (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation

and add the line

UnitySendMessage(“ReceivingObject”, “ReceivingMethod”, [url absoluteString].UTF8String);

In the case of the link QTDeep://?color=blue This will cause the GameObject named “ReceivingObject” in the currently open scene to have the equivalent of SendMessage(“ReceivingMethod”, “QTDeep://?color=blue”) called on it. A little string manipulation and switch case and you’re in business.

There are some problems doing it this way. First, the app has to be running. If the link is opened without the app running it will open the app but the message will be lost. Second, since we did this straight in Xcode the next time the project is rebuilt the changes made to UnityAppController will be lost. Let’s go a step further!

Extending The App Controller

We’re going to add three files and put them in “Plugins/iOS”: A header for our app controller (I’m an old school c programmer and not really an objective-c expert so I’ll admit the header might not be necessary.), the implementation for our app controller, and separate implementation file for our DLL linkage. I’ve named mine DeepLinkAppDelegate.h, DeepLinkAppDelegate.m, and DeepLinkBridge.m. It’s important they are in a folder named “iOS” which is in a folder named “Plugins”, but other than that the names/locations of the files do not matter.

DeepLinkAppDelegate.h

#ifndef DeepLinkAppDelegate_h
#define DeepLinkAppDelegate_h

#import "UnityAppController.h"

@interface DeepLinkAppDelegate : UnityAppController
@property (nonatomic, copy) NSString* lastURL;
- (void) deepLinkIsAlive;
- (char *) deepLinkURL;
@end

#endif /* DeepLinkAppDelegate_h */

DeepLinkAppDelegate.m

#import "DeepLinkAppDelegate.h"

// Makes sure your app controller delegate is the one that gets loaded.
IMPL_APP_CONTROLLER_SUBCLASS(DeepLinkAppDelegate)

@implementation DeepLinkAppDelegate

- (void) deepLinkIsAlive
{
	if (_lastURL)
	{
		const char *URLString = [_lastURL cStringUsingEncoding:NSASCIIStringEncoding];
    	UnitySendMessage("_DeepLinkReceiver", "URLOpened", URLString);
	}
}
- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation
{
	_lastURL = url.absoluteString;
	const char *URLString = [url.absoluteString UTF8String];
    UnitySendMessage("_DeepLinkReceiver", "URLOpened", URLString);

	return [super application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
- (char *) deepLinkURL
{
	return [(_lastURL ? _lastURL : @"") UTF8String];
}
@end

The important part is that we’re storing the last URL opened with the app and I’ve made it available two different ways. When the URL is opened it sends a UnitySendMessage, if the app was just opened then the game engine isn’t running yet and there’s no receiver so it’s cached until the receiver calls DeepLinkReceiverIsAlive from the Unity side of things. I also added a function for directly polling the last opened URL. (I’m not a fan of polling, but it’s definitely a very “Unity” way of handling it.) Here’s how we connect it all to Unity:

Plugin Bridge

DeepLinkBridge.mm Exposes native C code to Unity

#import "DeepLinkAppDelegate.h"

extern "C" {
	void DeepLinkReceiverIsAlive();
	char * GetDeepLinkURL();
}

void DeepLinkReceiverIsAlive() {
	DeepLinkAppDelegate *appDelegate = (DeepLinkAppDelegate *)[UIApplication sharedApplication].delegate;
	[appDelegate deepLinkIsAlive];
}
char * GetDeepLinkURL() {
	DeepLinkAppDelegate *appDelegate = (DeepLinkAppDelegate *)[UIApplication sharedApplication].delegate;
	return [appDelegate deepLinkURL];
}

DeepLinkListener.cs Notifies App Controller and repeats URLOpened as a UnityEvent

public class DeepLinkListener : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void DeepLinkReceiverIsAlive();
    [System.Serializable]
    public class StringEvent : UnityEvent { }
    public StringEvent urlOpenedEvent;
    public bool dontDestroyOnLoad = true;

    void Start()
    {
        if (dontDestroyOnLoad)
            DontDestroyOnLoad(this.gameObject);
        DeepLinkReceiverIsAlive(); // Let the App Controller know it's ok to call URLOpened now.
    }

    public void URLOpened(string url)
    {
        urlOpenedEvent.Invoke(url);
    }
}

There’s some examples you can see in the project on GitHub. Build it and try some of these links:

Further Experimentation

Now that we can cache and dispatch the event we could pass the target Game Object and Method in with the URL. While this creates some pretty obvious security concerns, it could make for a cool hacking game where the users have to poke at the game’s internal code using URL schemes. But that’s beyond the scope of this project.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s