Mastering OpenURL & Deep Links in SwiftUI & iOS

  • Apr 7, 2025

Mastering OpenURL & Deep Links in SwiftUI & iOS

OpenURL is a powerful mechanism in SwiftUI that allows our app to handle both internal and external links. In this comprehensive guide, we will walk through the basics to advanced usage of OpenURL in SwiftUI.

OpenURL is a powerful mechanism in SwiftUI that allows our app to handle both internal and external links. In this comprehensive guide, we will walk through the basics to advanced usage of OpenURL in SwiftUI.

Let’s start with basics of OpenURL. 

The openURL allows our apps to handle URLs in a consistent way across different views and components. We can get started with openURL modifier. 

struct OpenURLExample: View {
    @Environment(\.openURL) private var openURL
    
    var body: some View {
        Button("Visit DevTechie") {
            openURL(URL(string: "https://www.devtechie.com")!)
        }
    }
}

Let’s create a custom view to handle external links. We will create a new view named “ExternalLink.” This view will require a URL and a label as initialization parameters. Once we have these parameters, we can use them to open the URL.

import SwiftUI

struct OpenURLExample: View {
    var body: some View {
        List {
            ExternalLink(url: URL(string: "https://www.devtechie.com")!, label: "DevTechie.com")
            ExternalLink(url: URL(string: "https://www.devtechie.com/blog")!, label: "DevTechie Blog")
            ExternalLink(url: URL(string: "https://www.youtube.com/devtechie")!, label: "DevTechie on YouTube")
        }
    }
}

struct ExternalLink: View {
    let url: URL
    let label: String
    
    @Environment(\.openURL) private var openURL
    
    var body: some View {
        Button(label) {
            openURL(url)
        }
    }
}

We can open other types of links using openURLAction. For instance, we can open the app settings as demonstrated below.

struct OpenURLExample: View {
    var body: some View {
        ExternalLink(url: URL(string: UIApplication.openSettingsURLString)!, label: "Open Settings")
    }
}

struct ExternalLink: View {
    let url: URL
    let label: String
    
    @Environment(\.openURL) private var openURL
    
    var body: some View {
        Button(label) {
            openURL(url)
        }
    }
}

OpenURLAction also allows us to determine whether the action was successful or unsuccessful by passing a closure as an argument.

struct OpenURLExample: View {
    var body: some View {
        SettingsButtonView()
    }
}

struct SettingsButtonView: View {
    @Environment(\.openURL) var openURL
    @State private var showAlert = false

    var body: some View {
        Button("Open Settings") {
            Task {
                let settingsURL = URL(string: UIApplication.openSettingsURLString)!
                openURL(settingsURL) { accepted in
                    if accepted == false {
                        showAlert = true
                    }
                }
            }
        }
        .alert("Unable to open Settings", isPresented: $showAlert) {
            Button("OK", role: .cancel) {}
        } message: {
            Text("Please open Settings manually to change permissions.")
        }
        .padding()
        .buttonStyle(.borderedProminent)
    }
}

We can also utilize SwiftUI’s Link view to open URLs. Let’s build an example to demonstrate this functionality. We will create a view that includes a TextEditor and, using the Date Detector API, we’ll open the first URL detected within the TextEditor.

import SwiftUI

struct OpenURLExample: View {
    @State private var text = ""
    @State private var detectedURL: URL? = nil

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("DevTechie.com")
                .font(.title)

            TextEditor(text: $text)
                .frame(height: 150)
                .border(Color.gray.opacity(0.5), width: 1)
                .onChange(of: text) { _, _ in
                    detectedURL = extractFirstURL(from: text)
                }

            if let url = detectedURL {
                Link("Open Detected URL: \(url.absoluteString)", destination: url)
                    .foregroundColor(.blue)
                    .underline()
            }

            Spacer()
        }
        .padding()
    }

    func extractFirstURL(from string: String) -> URL? {
        let types: NSTextCheckingResult.CheckingType = .link
        guard let detector = try? NSDataDetector(types: types.rawValue) else { return nil }
        let matches = detector.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))
        return matches.first?.url
    }
}

We can create deep links into our app using OpenURLAction. For this we will first add a custom URL scheme to the app so to add custom scheme 

  • Go to your project settings → Info tab → URL Types

  • Add a new one: Identifier: e.g., com.devtechie.app

  • URL Schemes: e.g., devtechie

We will add onOpenURL modifier to the TextEditor 

TextEditor(text: $text)
  .frame(height: 150)
  .border(Color.gray.opacity(0.5), width: 1)
  .onChange(of: text) { _, _ in
      detectedURL = extractFirstURL(from: text)
  }
  .onOpenURL { url in
      text += "\nOpened URL: \(url)"
  }

Complete example should look like this

struct OpenURLExample: View {
    @State private var text = ""
    @State private var detectedURL: URL? = nil

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("DevTechie.com")
                .font(.title)
            
            TextEditor(text: $text)
                .frame(height: 150)
                .border(Color.gray.opacity(0.5), width: 1)
                .onChange(of: text) { _, _ in
                    detectedURL = extractFirstURL(from: text)
                }
                .onOpenURL { url in
                    text += "\nOpened URL: \(url)"
                }

            if let url = detectedURL {
                Link("Open Detected URL: \(url.absoluteString)", destination: url)
                    .foregroundColor(.blue)
                    .underline()
            }

            Spacer()
        }
        .padding()
    }

    func extractFirstURL(from string: String) -> URL? {
        let types: NSTextCheckingResult.CheckingType = .link
        guard let detector = try? NSDataDetector(types: types.rawValue) else { return nil }
        let matches = detector.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))
        return matches.first?.url
    }
}

Let’s type devtechie://test in mobile safari and see the prompt to open the app.

We can expand this example and even take the user to a specific page from the deeplink URL.

import SwiftUI

struct OpenURLExample: View {
    @State private var text = ""
    @State private var latestURL: URL?
    @State private var messageFromDeepLink: String?

    var body: some View {
        NavigationStack {
            VStack(alignment: .leading, spacing: 16) {
                Text("DevTechie.com")
                    .font(.title)

                TextEditor(text: $text)
                    .frame(height: 150)
                    .border(Color.gray.opacity(0.5), width: 1)
                    .onOpenURL { url in
                        latestURL = url
                        text += "\nOpened via onOpenURL: \(url)"
                    }

                if let message = messageFromDeepLink {
                    Text("→ \(message)")
                        .foregroundColor(.green)
                        .padding(.top)
                }

                Spacer()
            }
            .padding()
        }
        .onChange(of: latestURL) { _, newURL in
            handleURL(newURL)
        }
    }

    private func handleURL(_ url: URL?) {
        guard let url = url else { return }

        if url.scheme == "devtechie" {
            switch url.host {
            case "test":
                messageFromDeepLink = "Navigated to test section!"
            case "support":
                messageFromDeepLink = "Navigated to support section"
            default:
                messageFromDeepLink = "Unknown deep link"
            }
        } else {
            messageFromDeepLink = "System URL: \(url.absoluteString)"
        }
    }
}

Let’s dive deeper into this concept and implement a coordinator design pattern to directly navigate the user to a specific page within the application.

We will create an enum first for all the routes in the app

// MARK: - Enum for Routes
enum Route: Hashable {
    case test
    case support
}

Next, we will create a navigation coordinator that will publish the NavigationPath object, which contains the route associated with the page where the deep link should take the user..

import Observation
// MARK: - Coordinator
@Observable
class NavigationCoordinator {
    var path = NavigationPath()

    func handleDeepLink(_ url: URL) {
        guard url.scheme == "devtechie" else { return }

        switch url.host {
        case "test":
            path.append(Route.test)
        case "support":
            path.append(Route.support)
        default:
            break
        }
    }
}

We will create two views corresponding to the routes.


struct TestView: View {
    var body: some View {
        VStack {
            Text("🧪 Test View")
                .font(.largeTitle)
                .padding()
            Spacer()
        }
        .navigationTitle("Test")
    }
}

struct SupportView: View {
    var body: some View {
        VStack {
            Text("🛠 Support View")
                .font(.largeTitle)
                .padding()
            Spacer()
        }
        .navigationTitle("Support")
    }
}

It’s time to update the OpenURLExample view to include a coordinator and implement the logic for handling deep link navigation using navigationDestination modifier.

struct OpenURLExample: View {
    @State private var coordinator = NavigationCoordinator()
    @State private var text = ""

    var body: some View {
        NavigationStack(path: $coordinator.path) {
            VStack(alignment: .leading, spacing: 16) {
                Text("DevTechie.com")
                    .font(.title)

                TextEditor(text: $text)
                    .frame(height: 150)
                    .border(Color.gray.opacity(0.5), width: 1)
                    .onOpenURL { url in
                        text += "\nOpened URL: \(url)"
                        coordinator.handleDeepLink(url)
                    }

                Spacer()
            }
            .padding()
            .navigationDestination(for: Route.self) { route in
                switch route {
                case .test:
                    TestView()
                case .support:
                    SupportView()
                }
            }
        }
    }
}

Build and run

OpenURL provides a powerful mechanism for handling both internal and external navigation in your app. 


Visit us at