Adding a URL scheme to your app requires only a few steps, but there are a few details to keep in mind. How your app handles deep links depends on a few factors. Does your app use UIKit or SwiftUI for its user interface? Is your app scene-based or not? We cover each scenario in this tutorial.

Why Add a URL Scheme

If you are reading this, then I assume you know what the value of a URL scheme is. The idea is simple. By defining a URL scheme, your app tells the system it can open URLs that conform to a predefined format. Let me give you an example. Let's say we are building a notes application and add a URL scheme. The URLs have the following format.

notes://new-note
notes://note/123

The scheme of the URL is specific to your app although any app can define a URL scheme that uses the notes scheme. If multiple apps on a device define the same URL scheme, the behavior is undefined. It's therefore important that you carefully choose the URL scheme(s) of your app.

Setting Up the Project

Open your project in Xcode or create a new project if you'd like to follow along with me. Choose the App template from the iOS > Application template.

How to Add a URL Scheme to Your App

Name the project Notes and set Interface to SwiftUI. The steps for a project that uses UIKit (storyboards or XIB files) is identical.

How to Add a URL Scheme to Your App

Register URL Scheme

The first step is registering the URL scheme. Click the project in the Project Navigator on the left and select the Notes target from the list of targets. Open the Info tab at the top to access the configuration that is specific to this target.

Adding Support for Deep Links

There are two ways to register a URL scheme. The easiest option is to define a URL scheme in the URL Types section at the bottom. Click the + button to add a URL scheme. Give the URL scheme an identifier to uniquely identify it. The URL Schemes field defines the URL scheme, for example, notes.

The Role field of the URL scheme can be a bit confusing. The possible values are Editor, Viewer, and None. Set Role to Editor if your app defines the URL scheme and the URL scheme can be used for reading and writing. Set Role to Viewer if your app supports the URL scheme, but only for reading or viewing data. Use this option if you want to support third-party authentication (e.g., Google and Facebook authentication). The None option defines that no specific role is assigned to the URL scheme.

Adding Support for Deep Links

As I wrote earlier, it is possible that multiple apps register the same URL scheme. It is therefore important to provide a unique identifier to make sure the URL scheme is unique. Apple recommends using reverse domain notation (DNS) that includes the name of your app and company. This is similar to how you would choose your app's bundle identifier.

The other option to define a URL scheme is by editing the target's Info.plist. Scroll to the custom target properties section at the top of the Info tab. Select the last row of the table and press Enter to create a new row. Set the key to CFBundleURLTypes. This creates a key with name URL types. The value is an array of dictionaries. Each dictionary represents a URL scheme your app supports. Your app can support multiple URL schemes, which is convenient if you need to add support for third-party authentication.

Adding a URL Scheme to Info.plist

Each dictionary consists of two key-value pairs. The first key is CFBundleURLSchemes. The value is an array of strings. Each string of the array is a URL scheme your app supports. For this example, we set the URL scheme to notes. The second key is CFBundleURLName and its value is a string that uniquely identifies the URL scheme.

<key>CFBundleURLTypes</key>
<array>
	<dict>
		<key>CFBundleURLSchemes</key>
		<array>
			<string>notes</string>
		</array>
		<key>CFBundleURLName</key>
		<string>com.cocoacasts.notes</string>
	</dict>
</array>

Handling Incoming URLs

SwiftUI

Handling an incoming URL or deep link is pretty simple in SwiftUI. Let me show you how it works. Open NotesApp.swift and apply the onOpenURL view modifier to the ContentView. the onOpenURL view modifier accepts a closure that is invoked when the app is asked to handle an incoming URL. The closure accepts the deep link, a URL object, as an argument. In this example, we print the deep link to the console.

import SwiftUI

@main
struct NotesApp: App {

    // MARK: - App

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    print(url)
                }
        }
    }

}

UIKit

Handling deep links works a bit differently using Apple's UIKit framework. When the system opens your app to handle a deep link, it invokes the application(_:open:options:) method of the UIApplicationDelegate protocol. The return type of this method is Bool. The method should return false if it isn't able to handle the incoming URL.

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

	...

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        // Handling Incoming URL

        return true
    }

}

The application(_:open:options:) method is a bit more flexible than the onOpenURL view modifier. The third parameter is a dictionary of options that contains information about the incoming URL, for example, which app triggered the deep link.

Scenes

If your app is scene-based, then you need to handle the deep link through the UIWindowSceneDelegate protocol. The system invokes the scene(_:willConnectTo:options:) method if the app isn't running and the scene(_:openURLContexts:) method if the app is running or suspended.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let url = connectionOptions.urlContexts.first?.url else {
        return
    }

    // Handle URL
    openURL(url)
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else {
        return
    }

    // Handle URL
    openURL(url)
}

private func openURL(_ url: URL) {
    // ...
}

Testing the Implementation

Build and run the app in the simulator. We have a few options to test the URL scheme. Open a terminal and execute the following command.

xcrun simctl openurl booted "notes://deep-link"

Let me explain what is happening. xcrun is a command-line utility provided by Apple that lets you use developer tools, simctl in this example. As the name suggests, simctl is a developer tool to control the simulators installed on your machine. We use the openurl command to open a given URL, a deep link in this example. The booted specifier tells simctl that it should target the currently booted simulator. If you have multiple simulators running, then simctl picks the one that was most recently booted. The last component of the command is the deep link itself.

Execute the command to try it out. The output in the console confirms that the deep link was successfully handled by the app.

notes://deep-link

A deep link is nothing more than a URL your app handles. Any app can trigger a deep link. Open the Reminders app on the simulator and create a reminder. Add the URL as a note to the reminder. We can open the Notes app by tapping the URL. Because the Notes app we built supports the notes URL scheme, the system launches our app. It's that simple.

Testing Deep Links in the Simulator

How to Test Deep Links on a Physical Device

To test deep links on a physical device, I usually use Apple's Notes app. I create a note for the app I'm testing and add the URLs I want to test. What I like about this approach is that you can enter the URLs on your machine and access the note on your test device (assuming you are signed in with the same Apple ID on both devices).

Best Practices

Data Validation

If your app defines a URL scheme, then you need to be mindful of security. What do I mean by that? Consider a URL scheme as an API anyone can use to interact with your app. Ask yourself the question can someone with less good intentions misuse the API of your app, that is, the URL scheme?

To prevent someone from misusing your app's URL scheme, make sure your app validates the data that is embedded in the URL. The same is true for any API. Never trust external input, including the user's input. Validate and sanitize data if needed to avoid vulnerabilities.

Handling URLs

You define the format of the URLs your app supports. A URL scheme is like an API your app exposes to the world. To reliably handle incoming URLs, use Foundation's URLComponents struct. Take a look at Working With URLComponents In Swift for a detailed tutorial on how to easily and reliably work with URLs in your app.