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.
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.
This doesn’t work for me. Safari in the mobile just gives an error when I click the links above saying it’s invalid. When I paste the link on Note and click it, the app opens but it still doesn’t do anything and it crashes.
I’ve read apple just doesn’t support URI Schemes like this anymore but prefers Universal links. If you could provide a similar article for Unity and Universal Links in iOS, that’d be very nice.
Hi,it will be really helpful if u could add screen shots where to exactly paste the code.While trying the second method should we remove QTDeep from URL scheme from build settings?.I opened the project downloaded from github and ran two scenes Main.unity and deep.unity.Later ran links on browser.
QTDeep://?color=red
QTDeep://?color=yellow
QTDeep://?color=pink
QTDeep://?color=gray
but nothing is happening while running deep.unity and Main.unity closes.
Thanks for this helpful tutorial.
As of May 2019, I found that this did not work due to application:openURL:sourceApplication being deprecated. Instead I had to use application:openURL:options, and then it worked for me
So in DeepLinkAppDelegate.m, change:
– (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];
}
To:
– (BOOL)application:(UIApplication*)application openURL:(NSURL*)url options:(NSDictionary *)options;
{
_lastURL = url.absoluteString;
const char *URLString = [url.absoluteString UTF8String];
UnitySendMessage(“_DeepLinkReceiver”, “URLOpened”, URLString);
return [super application:application openURL:url options:options];
}
Thanks CM, the sands of the iOS API are constantly shifting!