iOS 13: launchOptions always nil? This is the reason & solution

launchOptions have moved and you find them in AppDelegate no longer.

So today I spent quite some time puzzling over why I was always getting nil launchOptions argument in func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) and then finally found a solution.

The reason

The culprit is the new project structure for iOS 13 and above. In the past we were used as AppDelegate as the only class to handle app lifecycle events and responding to shortcuts, URL schemes and what not.

But with iOS 13 and more specifically iPadOS came the push to better support multiple screens for apps we got the SceneDelegate where most of the events now moved.

So even though the didFinishLaunchingWithOptions still looks the same:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
}

You don't get any launchOptions here. It would be better if Apple simply changed the signature but for some reason (lets hope there at least is a reason) the argument is still present but always nil.

The solution

The solution to correctly getting launchOptions in iOS 13 and above is to use SceneDelegate and the willConnectTo session which is automatically there with some basic code. The full signature looks like this:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)

And the connectionOptions is new "reincarnation" of the trusty old launchOptions. To be fair to apple, this is much better to work with.

Say you want to respond to launch from shortcut item (the quick action available with long press on app icon). Previously you would have to reach into the options dictionary and do a cast.

Now it looks like this:

if let shortcut = connectionOptions.shortcutItem {
    // handle shortcut here
}

What about reacting to URL scheme? We have urlContexts set available and can simply pull the first item for basic implementation:

if let firstUrlContext = connectionOptions.urlContexts.first {
    // handle firstUrlContext.url
}

And that is it!

Hope this will save you some time when implementing this and you won't have to go full Sherlock 🕵 finding what is wrong...

One last thing

Since I have shown you how to react to shortcuts and URLs, let's see how to do these tasks when app is already running in the background.

We have this method to handle shortcuts:

 func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
}

And this one to handle URL launch:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
}

Once again, this is much better than prior to iOS 13 changes. 🙂

Thanks for reading!

Uses: Xcode 11 & Swift 5