Application openURL gets called a few seconds after didFinishLaunchingWithOptions
Asked Answered
V

3

7

I have a deep linking feature in my app that works fine beside one case. I have a 3 different onboarding pages according to the url that opened the app. So when the app is launched i need to know what link(if any) opened the app and then present the right onboarding page. The problem is that i need to know what screen to present in the method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 

but i can only know if a deep link opened the app in

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

which gets called 5 seconds after did didFinishLaunchingWithOptions is called(i counted the seconds). So i have 5 seconds that i see a wrong onboarding page untill openURL is called(if it will be called) .

So my question is: is there any way to know if the app was launched from a url before or during didFinishLaunchingWithOptions?

By the way launchOptions in didFinishLaunchingWithOptions is nil when the app opens from a deep link

Vogul answered 20/2, 2017 at 13:16 Comment(1)
Hello aviv_eik I am also implementing same feature & facing same problem. Can you please help me to sort it. Thanks in advanceKries
S
11

The launch option key your are looking for is UIApplicationLaunchOptionsURLKey (Objective-C) / UIApplicationLaunchOptionsKey.url (Swift).
If you're targeting iOS 9 and upwards you only have to intercept a launch URL from

  • application:didFinishLaunchingWithOptions: (in case the app is not in memory yet)
  • application:openURL:options: (in case the app is already in the background).

Here's a minimalistic implementation of UIApplicationDelegate that should cover both cases - please note that a lot unrelated logic has been omitted for clarity:

Objective-C:

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    NSURL *url = launchOptions[UIApplicationLaunchOptionsURLKey];
    if (url) {
        // TODO: handle URL from here
    }

    return YES;
}

- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {

    // TODO: handle URL from here

    return YES;
}

@end

Swift 5:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

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

        if let url = launchOptions?[.url] as? URL {
            // TODO: handle URL from here
        }

        return true
    }

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {

        // TODO: handle URL from here

        return true
    }
}
Spielman answered 20/2, 2017 at 15:15 Comment(5)
when i open my app from a deeplink 'launchOptions' is nil in didFinishLaunchingWithOptions, but 'openURL' does gets triggerd (with the deep link url). I do show my onboarding page inside 'didFinishLaunchingWithOptions', where do you think i should show the first key window when the app launchedVogul
Ok let me ask a few other questions: Are you opening your app from a web browser? What minimum iOS version are you targeting? Note that application:didFinishLaunchingWithOptions: is only called once per run time. If your app is already in the background, application:openURL:options: will be called instead. My comment regarding window setup only applies if you rely on a storyboard as the 'Main Interface' setting. If you're setting up the interface yourself from application:didFinishLaunchingWithOptions: you'll be fine, please ignore the comment.Spielman
I improved my answer for clarity - it has been tested with Xcode 8.3 on devices running iOS 9.3.4 and iOS 10.3, for both Objective-C and Swift builds. Make sure you did set a value in your Info.plist's URL Types > URL Schemes (e.g. "my-app"), build the app on your device then kill it, then try to open e.g. "my-app://" in any web browser.Spielman
although this is an old question by the time I am writing the comments, still wanted to warn new readers that it is not necessary to handle URL both in application:didFinishLaunchingWithOptions: and application(_:open:options:). It is enough to check just on application(_:open:options:). developer.apple.com/documentation/uikit/uiapplicationdelegate#//…Synchromesh
yep. @KutayDemireren seems to be right - application(_:open:options:) is called even if app was killed beforeKussell
E
8

I just had a similar problem in iOS 13, but things have changed in iOS 13 because the UIWindowSceneDelegate has been introduced and may now do some of the work previously done by UIApplicationDelegate (depending on your app settings).

The answer by @Olivier in this thread was still very useful to me because it points out the two scenarios in which the URL scheme gets handled; namely when the app is not in memory yet, which calls for application:didFinishLaunchingWithOptions:, and when the app has already been loaded and is in the background, which second case calls for application:openURL:options:.

So, as I'm mentioning above, things are a little different since iOS 13 if you are using the default application template generated by XCode 11. I won't get into the details here, so here's an informative tutorial on the topic: Understanding the iOS 13 Scene Delegate.

But the key methods to modify if you are using the new approach with scenes are scene(_:willConnectTo:options:) (docs here) and scene(_:openURLContexts:) (docs here). The former is where to act upon the URL scheme when the app was not already loaded (so it kind of replaces application:didFinishLaunchingWithOptions:), and the latter is where to get the URL when the app was already in the background when the URL scheme was called (so this one replaces application:openURL:options:).

With scene(_:willConnectTo:options:), you can look for the URL of the URL scheme (if any) doing something like this:

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

For scene(_:openURLContexts:), you can look inside the URLContexts set.

I hope this helps!

Epitasis answered 20/1, 2020 at 17:7 Comment(0)
C
1

nice idea from previous. I made some tests..

I confirm iOS 13 odds and bugs.

Preamble: I enabled all flags on plist: (from https://forums.developer.apple.com/thread/118932)

... UIFileSharingEnabled LSSupportsOpeningDocumentsInPlace UISupportsDocumentBrowser .. And added ALL types in plist:

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeIconFiles</key>
        <array/>
        <key>CFBundleTypeName</key>
        <string>abc File</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSHandlerRank</key>
        <string>Owner</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>org.example.app.document.abc</string>
        </array>
    </dict>
</array>


<key>UTExportedTypeDeclarations</key>
    <array>
        <dict>
            <key>UTTypeConformsTo</key>
            <array>
                <string>public.data</string>
            </array>
            <key>UTTypeDescription</key>
            <string>abc File</string>
            <key>UTTypeIconFiles</key>
            <array/>
            <key>UTTypeIdentifier</key>
            <string>org.example.app.document.abc</string>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>abc</string>
                </array>
            </dict>
        </dict>
    </array>

I logged here:

1)

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        print(documentsDir())

        if let url = launchOptions?[.url] as? URL {
            // TODO: handle URL from here
            openWriteAndCloseLog(msg: "1 " + url.absoluteString, withTimestamp: true)
        }
        return true
    }

2)

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    // TODO: handle URL from here
    openWriteAndCloseLog(msg: "2 " + url.absoluteString, withTimestamp: true)
    return true
}

3)

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 
    if let url = connectionOptions.urlContexts.first?.url {
        // handle
        openWriteAndCloseLog(msg: "3 " + url.absoluteString, withTimestamp: true)            
    }
    guard let _ = (scene as? UIWindowScene) else { return }
}

seems we pass ONLY on 3 (as per my debug log I can see inside documents, as I shared via iTunes)

I made a small demo app to test it.

https://github.com/ingconti/DocumentBroswerSampleApp

You can open attachment from (mal for example..) You will see:

enter image description here

Chaff answered 30/1, 2020 at 7:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.