In WidgetKit
, I am using widgetURL
to construct a custom URL schema, so that I can launch the main app via deep link.
I am trying to handle a custom URL scheme (widget://
) in SceneDelegate
, by launching a view controller from a root view controller.
Here’s my code of launching a view controller from a root view controller.
private func handleIncomingURL(_ url: URL) {
if let scheme = url.scheme, scheme == "widget" {
if let rootViewController = self.window?.rootViewController {
// https://stackoverflow.com/questions/33520899/single-function-to-dismiss-all-open-view-controllers
// Dismiss all previous launched VC except root view controller.
rootViewController.dismiss(animated: false, completion: nil)
let greenViewController = GreenViewController.instanceFromMainStoryBoard()
rootViewController.present(greenViewController, animated: true)
} else {
print("no rootViewController")
}
}
}
There are 2 use cases when handling custom URL scheme.
- Previous app is still in app stack. Previous app is restored from the app stack.
- The app is not found in app stack. New app is launched.
Previous app is still in app stack
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// Called on existing scenes
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
handleIncomingURL(url)
}
}
}
The app is not found in app stack
// Called on new scenes
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if let url = connectionOptions.urlContexts.first?.url {
handleIncomingURL(url)
}
}
Thing works well when previous app is still in app stack. rootViewController
is not nil, scene(_:openURLContexts:)
is called and our greenViewController
can launched without issue.
However, when the app is not found in app stack, scene(_:willConnectTo:)
is called and rootViewController
is nil. Hence, we are not able to launch our greenViewController
.
May I know, what is the appropriate action to launch a view controller, in scene(_:willConnectTo:)
when root controller is nil?
2
Answers
Inside the
func scene(willConnectTo session)
method:The 0.7 second delay is IMO a reasonable amount of time the app needs to load a root view controller. Furthermore, since I use this for deeplinks, it is a more appealing experience for the user if the deeplink happens after some delay, and not immediately.
There is a reliable way to do it, but not using the
scene(_:willConnectTo)
just in case you would consider an alternative approach.Here is the ‘modern’ way to handle deep links:
You place the following code in SwiftUI
This code comes from the GitHub project
https://github.com/pawello2222/WidgetExamples
You can prove this works reliably by first running the app. Then edit your Home Screen to add the Deep Link widget (there are a few in the galley under Example Widgets; keep swiping until you get to it).
Then you kill off the actual WidgetExamples app using the swipe up from bottom of screen gesture. This removes the app from the "App Stack" (the list of notionally running apps from the end user’s perspective).
Then you can set a breakpoint in the
if url.scheme
line.Then in Xcode you can
Debug > Attach to Process by PID or Name
and type inWidgetExamples
.Now when you tap on the Deep Link widget, it will launch the WidgetExamples app, and hit your breakpoint. The app is properly launched and showing a user interface before your breakpoint is hit.