Override app delegate in Unity for iOS and OSX (2/4)
Notification Center

In the previous part of the tutorial series – Override app delegate in Unity for iOS and OSX (1/4) Plugin workflow – we established a solid base we can build plugins upon. Now we can start actually override app delegate behaviour, with the most soft way using notifications.

TL;DR

You may get the example project immediately by heading to GitHub (in that case you should really be aware of the folder structure at the end of the article, as there are numerous sub-projects can be found all around). However, I strongly recommend to take the next 15 minutes to understand what is going on, and read along (so the prerequisites).

See Unity.Blog.Override_App_Delegate at

Override app delegate for iOS in Unity using notifications

Fortunately on iOS (in UIKit in particular) there are countless notifications (events basically) that the system emits, even for app launching events. So having the fact that now the iOS Unity player application preloads our plugin library, we may simply register for the corresponding application notifications provided by the os (namely UIApplicationDidFinishLaunchingNotification in UIKit).

Tip: when it comes to native iOS plugin development, once you have the Unity build, you may iterate the iOS plugin library without rebuilding the Unity project. Just add another Copy Files Phase to the library project Build Phases, and directly copy the library into the resulting Unity player Xcode project.

The idea here is to create an Objective-C singleton upon class loading, then add it as an observer for the given notification. First we should import the UIKit headers, as the notifications are implemented therein.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>


@interface Override_iOS : NSObject
@end
See Override_iOS.h at

Then we implement a bare simple singleton pattern, an instance will be retained in a private static variable called _instance. It is suitable now to be registered as notification observer.

#import "Override_iOS.h"


__strong Override_iOS *_instance;


@implementation Override_iOS


+(void)load
{
    NSLog(@"[Override_iOS load]");
    _instance = [Override_iOS new];
    [[NSNotificationCenter defaultCenter] addObserver:_instance
                                             selector:@selector(applicationDidFinishLaunching:)
                                                 name:UIApplicationDidFinishLaunchingNotification
                                               object:nil];
}

-(void)applicationDidFinishLaunching:(NSNotification*) notification
{ NSLog(@"[Override_iOS applicationDidFinishLaunching:%@]", notification); }


@end
See Override_iOS.m at

Unity iOS Player Console Log applicationDidFinishLaunching

As you run ⌘+R the Unity iOS player application with this version of the plugin, you can see the console output from the plugin’s applicationDidFinishLaunching:. You may notice however, that the plugin gets notified after (!) the Unity application delegate.

Tip: If you have added the extra Copy Files Phase to the library project as suggested in the note above, then you don’t have to rebuild the Unity project during this part.

Override app delegate to get URL scheme data (deep linking)

Unity PlayerSettings URL schemes

A quiet common issue that needs override app delegate behaviour in Unity is actually to catch data passed via URL scheme (known as deep linking). To create one, add a URL scheme called override in Unity iOS PlayerSettings, then build the Unity iOS player Xcode project again.

At the time of this writing, Unity has an issue when you launch the application from a simple link. If you launch the application that way, most likely you won’t have any annotation attached to the payload (if you launch the application from another application, the annotations will be probably there).

Unity iOS Player URL Scheme Hotfix

Unfortunately, Unity implementation assume that those annotations are always present. To temporarily workaround this issue, find the line containing addItem(@”annotation”, annotation) in the iOS Unity player Xcode project, and simply comment it out (see line 293 above). With that you can avoid crashing the Unity player during Simulator test sessions (you should not be bothered with the hotfix, as in the next part of the tutorial, we gonna override that method altogether).

iOS Simulator Safari

Now if you run the app in the simulator ⌘+R, it will register the corresponding URL scheme to the simulator. After that, make sure you stop the app by hitting ⌘+. in Xcode (otherwise applicationDidFinishLaunching: won’t get called next time). Open up simulator console pressing ⌘+/ in Simulator app (or head to View/Open System Log…). Open up Mobile Safari right in Simulator, then type in an URL with the very scheme we created before: override://Greetings+from+Safari!. After you hit Open in the confirmation popup, all the magic happens. On the console log, amongst many others you’ll find something similar like below.

iOS Simulator System Log applicationDidFinishLaunching

Boom, there you have captured the deep link URL data (in UIApplicationLaunchOptionsURLKey). Furthermore, we can store the URL data in a property, and provide a public static accessor for the singleton instance of our class.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>


@interface Override_iOS : NSObject


@property (nonatomic, strong) NSString *URL;
+(Override_iOS*)instance;


@end
source eppz! at
#import "Override_iOS.h"


 __strong Override_iOS *_instance;


@implementation Override_iOS


+(void)load
{
    NSLog(@"[Override_iOS load]");
    _instance = [Override_iOS new];
    _instance.URL = @"Greetings from iOS!";
    [[NSNotificationCenter defaultCenter] addObserver:_instance
                                             selector:@selector(applicationDidFinishLaunching:)
                                                 name:UIApplicationDidFinishLaunchingNotification
                                               object:nil];
}

+(Override_iOS*)instance
{ return _instance; }

-(void)applicationDidFinishLaunching:(NSNotification*) notification
{
    NSLog(@"[Override_iOS applicationDidFinishLaunching:%@]", notification);
    
    // Store URL, if any.
    if (notification.userInfo == nil) return;
    if ([notification.userInfo.allKeys containsObject:UIApplicationLaunchOptionsURLKey] == NO) return;
    self.URL = [[notification.userInfo objectForKey:UIApplicationLaunchOptionsURLKey] absoluteString];
}


@end
source eppz! at
#import "UnityString_C++.mm"
#import "Override_iOS.h"


extern "C"
{

    
    const char* getMessage()
    { return UnityStringFromNSString([[Override_iOS instance] URL]); }
    
    
}
See Override_C++.mm at

Having that, we can hook up that URL data into the external getMessage function of our plugin (see above), so we can get the deep link URL data as the message in Override.cs once the Unity scene has been loaded. Again, open a link in Mobile Safari with the shceme: override://Greetings+from+Simulator!, so it will be hooked up right into the text label.

[IMAGE]

Limitations of override app delegate in Unity using notifications

While this can be a working solution in your use case, notifications have some limitations. First, UIKit notifications will be always triggered after (!) Unity implementations invoked. You cannot send a UnitySendMessage from a UIApplicationDidEnterBackgroundNotification notification, as Unity will shut down the player in it’s own implementation of applicationDidEnterBackground:.

Second, unfortunately iOS (nor OSX) don’t have notifications for every UIApplicationDelegate calls. You cannot be notified on a application:openURL:options: call, which is vital to catch URL scheme triggered application foreground transitions.

Having all the considerations above, I found the best way to extend / override app delegate behaviours is using the Objective-C runtime, and extend the app delegate by method swizzling at runtime (something really similar like Reflection with Mono runtime). As you’ll see in the next part of the tutorial series, it is close to subclassing, but in a more subtle / flexible way.

DISCLAIMER. THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.) IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.).