Fix a Universal Link that lands on the wrong screen after a cold start
A Universal Link opens your app to a specific product screen. When the app is already running it works; when the app was not running (a tap from Messages), it opens the default tab instead. Diagnose why the cold-start case is dropped and fix it.
Constraints:
- Keep one routing entry point — do not duplicate the parsing logic.
- Do not disable Universal Links or fall back to a custom scheme.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// window setup only — no deep-link handling here
}
// Warm path only: fires when the app is already running.
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity.webpageURL { Router.shared.open(url) }
}
}
Find and fix the bug so a cold launch opens the right screen.
On a cold start iOS delivers the NSUserActivity in willConnectTo via connectionOptions, not through scene(_:continue:). The code handles only the warm continue path, so a cold launch from Messages opens the default screen. Read it on connect and route through the same handler.
- ✗Handling only the warm
continuepath and ignoringconnectionOptions - ✗Assuming cold and warm launches deliver the activity through the same callback
- ✗Reaching for launch arguments or a server round-trip instead of the scene options
- →Why must the router tolerate being asked to navigate before the UI is on screen?
- →How would you handle a link that also arrives as a
URLContexton connect?
On a cold launch there is no running app to receive scene(_:continue:), so iOS delivers the NSUserActivity inside the scene's connectionOptions. Handle both paths through one router:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// Cold start: the activity is waiting here, not in `continue`.
if let activity = connectionOptions.userActivities.first,
let url = activity.webpageURL {
Router.shared.open(url)
}
}
// Warm path: the app was already running.
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity.webpageURL { Router.shared.open(url) }
}
}
Both callbacks now funnel into Router.shared.open, so a tap from Messages that cold-starts the app reaches the same product screen as a tap while the app is already open.