How to Handle Deep Links in SwiftUI

Learn how to implement and handle both custom URL schemes and Universal Links │ in your SwiftUI application to navigate users to specific content.

Intermediate

Handling deep links in SwiftUI involves configuring your app to respond to specific URLs and then parsing those URLs to navigate to the appropriate content within your application. There are two primary types of deep links: Custom URL Schemes and Universal Links.

1. Custom URL Schemes

Custom URL schemes allow you to define a unique prefix (e.g., myapp://) that, when tapped, will open your application.

Configuration:

  1. Add URL Type in Xcode:
    • In your Xcode project, select your target.
    • Go to the "Info" tab.
    • Under "URL Types," click the "+" button to add a new URL type.
    • Set the "Identifier" (e.g., com.yourcompany.yourapp). It's best practice to use your app's bundle ID as a prefix.
    • Set the "URL Schemes" to your desired scheme (e.g., myapp).

Handling in SwiftUI:

You use the .onOpenURL view modifier to handle incoming URLs. This modifier is available since iOS 14.

import SwiftUI

struct ContentView: View {
    @State private var receivedURL: URL?

    var body: some View {
        VStack {
            Text("Deep Link Handler")
            if let url = receivedURL {
                Text("Received URL: \(url.absoluteString)")
                // Parse the URL and navigate accordingly
                // Example: myapp://product?id=123
                if url.host == "product",
                   let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
                   let id = components.queryItems?.first(where: { $0.name == "id" })?.value {
                    Text("Product ID: \(id)")
                }
            } else {
                Text("No deep link received yet.")
            }
        }
        .onOpenURL { url in
            receivedURL = url
            print("Received custom URL scheme: \(url)")
        }
    }
}

2. Universal Links

Universal Links are standard HTTPS links (e.g., https://yourwebsite.com/path) that can open your app directly if it's installed, or fall back to your website if not. They provide a seamless user experience.

Configuration:

  1. Associated Domains Entitlement:

    • In your Xcode project, select your target.
    • Go to the "Signing & Capabilities" tab.
    • Click "+ Capability" and add "Associated Domains."
    • Add an entry in the format applinks:yourwebsite.com (e.g., applinks:example.com).
  2. apple-app-site-association File:

    • Create a JSON file named apple-app-site-association (without a .json extension).
    • Host this file on your website at the root or in the .well-known directory (e.g., https://yourwebsite.com/apple-app-site-association or https://yourwebsite.com/.well-known/apple-app-site-association).
    • The file specifies which paths on your website should open your app. You'll need your Team ID and Bundle ID for this file.

    Example apple-app-site-association file:

    json { "applinks": { "details": [ { "appIDs": [ "YOUR_TEAM_ID.com.yourcompany.yourapp" ], "components": [ { "/": "/products/*", "comment": "Matches any URL in /products/ path" }, { "/": "/profile/*", "comment": "Matches any URL in /profile/ path" } ] } ] } }

Handling in SwiftUI:

Similar to custom URL schemes, you use the .onOpenURL modifier.

import SwiftUI

struct ContentView: View {
    @State private var receivedUniversalLink: URL?

    var body: some View {
        VStack {
            Text("Universal Link Handler")
            if let url = receivedUniversalLink {
                Text("Received Universal Link: \(url.absoluteString)")
                // Parse the URL and navigate
                // Example: https://yourwebsite.com/products/123
                if url.pathComponents.contains("products"),
                   let productId = url.lastPathComponent {
                    Text("Product ID from Universal Link: \(productId)")
                }
            } else {
                Text("No universal link received yet.")
            }
        }
        .onOpenURL { url in
            receivedUniversalLink = url
            print("Received universal link: \(url)")
        }
    }
}

3. Parsing and Navigation

For more complex applications, you'll need to parse the incoming URL and use SwiftUI's navigation tools to direct the user to the correct view.

import SwiftUI

enum AppRoute: Hashable {
    case home
    case product(id: String)
    case profile(id: String)
    case settings
}

class DeepLinkManager: ObservableObject {
    @Published var navigationPath = NavigationPath()

    func handleURL(_ url: URL) {
        guard let host = url.host else { return }

        // Example for custom URL scheme: myapp://product?id=123
        // Example for universal link: https://yourwebsite.com/products/123

        if host == "product" || url.pathComponents.contains("products") {
            if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
               let id = components.queryItems?.first(where: { $0.name == "id" })?.value ?? url.lastPathComponent {
                navigationPath.append(AppRoute.product(id: id))
            }
        } else if host == "profile" || url.pathComponents.contains("profile") {
            if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
               let id = components.queryItems?.first(where: { $0.name == "id" })?.value ?? url.lastPathComponent {
                navigationPath.append(AppRoute.profile(id: id))
            }
        }
        // Add more routing logic for other paths
    }
}