Post

Deprecated UIApplicationDelegate URL Handling in iOS 26

Migrate from deprecated UIApplicationDelegate application(_:open:options:) to UISceneDelegate scene(_:openURLContexts:) in iOS 26.

Deprecated UIApplicationDelegate URL Handling in iOS 26

Apple deprecated UIApplicationDelegate’s application(_:open:options:) method in iOS 26. URL handling now belongs exclusively to UISceneDelegate.

Deprecated Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import UIKit

extension AppDelegate: UIApplicationDelegate {
    // ⚠️ Deprecated in iOS 26.0
    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey: Any] = [:]
    ) -> Bool {
        let sourceApp = options[.sourceApplication] as? String
        handleIncomingURL(url, from: sourceApp)
        return true
    }
}

Migration: UISceneDelegate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import UIKit

@available(iOS 13.0, *)
extension SceneDelegate: UISceneDelegate {
    func scene(
        _ scene: UIScene,
        openURLContexts URLContexts: Set<UIOpenURLContext>
    ) {
        guard let urlContext = URLContexts.first else {
            return
        }

        let url = urlContext.url
        let sourceApp = urlContext.options.sourceApplication

        handleIncomingURL(url, from: sourceApp)
    }
}

Key Differences

AspectAppDelegate (Deprecated)SceneDelegate
AvailableiOS 2.0 – iOS 26.0iOS 13.0+
ParameterSingle URLSet<UIOpenURLContext>
Source Appoptions[.sourceApplication]urlContext.options.sourceApplication
ReturnBoolVoid

Shared URL Handler

Extract URL handling logic into a shared function both delegates can call during transition periods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation

enum DeepLinkHandler {
    static func handle(_ url: URL, sourceApp: String?) {
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
            return
        }

        switch components.host {
        case "profile":
            let userID = components.queryItems?.first(where: { $0.name == "id" })?.value
            navigateToProfile(userID: userID)
        case "settings":
            navigateToSettings()
        default:
            break
        }
    }

    private static func navigateToProfile(userID: String?) {
        // Route to profile screen
    }

    private static func navigateToSettings() {
        // Route to settings screen
    }
}

SwiftUI Apps

For SwiftUI apps using @main App protocol, use the onOpenURL modifier:

1
2
3
4
5
6
7
8
9
10
11
12
13
import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    DeepLinkHandler.handle(url, sourceApp: nil)
                }
        }
    }
}

Scene Configuration

Ensure your Info.plist includes scene configuration for URL handling to work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <false/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
            </dict>
        </array>
    </dict>
</dict>

Important Notes

  • UISceneDelegate is available since iOS 13; most apps should already support it
  • The Set<UIOpenURLContext> typically contains a single context in practice
  • sourceApplication can be nil due to privacy restrictions or when opened from Shortcuts/Widgets
  • Remove the deprecated AppDelegate method once minimum deployment target is iOS 26+

☕ Support My Work

If you found this post helpful and want to support more content like this, you can buy me a coffee!

Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏

This post is licensed under CC BY 4.0 by the author.