Opening an app from a URL is a very powerful iOS feature. It draws users to your application and can create shortcuts to specific functions. This week, we’ll take a closer look at deep linking on iOS and how to create a URL scheme for your app.
When we talk about deep linking in mobile applications, it means creating a specific URL to open the mobile application. It comes in two formats:
- Custom URL scheme for your application registration:
scheme://videos
- Open your application from the registry domainGeneral links:
mydomain.com/videos
Today, we’ll focus on the former.
I’ll focus on the UIKit implementation code, but if you’re also looking for it, I’ll also briefly cover SwiftUI.
Setting the URL scheme
Setting up a custom URL scheme for iOS is the same whether you’re using SwiftUI or UIKit. In Xcode, under your project configuration, select your target and navigate to the Info TAB. You will see a section at the bottom of the URL Types.
Click + and I can create a new type. I often reuse app bundles for identifiers. For URL schemes, I recommend using the application name (or shortening) as short as possible. It should not contain any custom characters. For example, I’ll use Deeplink.
*. * The application is ready to recognize the new URL, now we need to process it when we receive it.
SwiftUI Deep links.
If you don’t have any AppDelegate and SceneDelegate files, which is the case for most of the SwiftUI implementation, we don’t have much work to do.
In our App implementation, we can capture the url opened from the onOpenURL(perform:) operation.
import SwiftUI
@main
struct DeeplinkSampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
print(url.absoluteString)
}
}
}
}
Copy the code
To test it, I can install the application on the emulator and launch the given URL from the terminal application
xcrun simctl openurl booted "deeplink://test"
Copy the code
* cool! * Let’s look at how UIKit is implemented differently.
UIKit deep links
On paper, UIKit or SwiftUI shouldn’t have an impact on the way we handle deep links. However, it mostly boils down to an AppDelegate or one, which is more common for UIKit applications. SceneDelegate
For an old app with only an AppDelegate, the app is opened by capturing deep links in the following way.
extension AppDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
print(url.absolueString)
return true
}
}
Copy the code
This function returns a Boolean value if the application can handle the given URL.
For newer applications that are included, the SceneDelegate callback will be there. It’s important to note that the AppDelegate is not called even if you implement it.
extension SceneDelegate { func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { guard let firstUrl = URLContexts.first? .url else { return } print(firstUrl.absoluteString) } }Copy the code
In this implementation, we can notice that we no longer need to return any results. However, instead of just a URL, the argument passed is aSet<>, which opens one or more urls. I don’t have a use case, we’ll have more urls, so I’ll just keep one for now.
As before, we can install the application on the emulator and try to see if everything is set up correctly. We should see our deep link URL printed.
xcrun simctl openurl booted "deeplink://test"
Copy the code
Once setup is complete, the idea is to create routes to identify and open the correct screen. Let’s dive into the water.
Deep link handler implementation
The idea is simple, for a given link, we need to determine what user journey or screen we should open. Because they can be many features of the entire application, and because we want to avoid a lot of Switch cases dealing with it, we’ll get smarter and divide and conquer.
For this example, let’s imagine that we have a video editing application. These are the three main tabs for editing new videos, listing edited videos, and then an account page that contains information about different applications and users.
We can think of three main paths
deeplink://videos/new
– Start a new video editing journeydeeplink://videos
– Log in to the Video List TAB screendeeplink://account
– Login account screen
First, I’ll create a deep link handler protocol to define the minimum requirements for any new handler.
protocol DeeplinkHandlerProtocol {
func canOpenURL(_ url: URL) -> Bool
func openURL(_ url: URL)
}
Copy the code
I will also define a DeeplinkCoordinator that will remain on the handler and find the correct handler to use. It also returns a Boolean value like AppDelegatehas, so we can use it in different implementations.
protocol DeeplinkCoordinatorProtocol {
@discardableResult
func handleURL(_ url: URL) -> Bool
}
final class DeeplinkCoordinator {
let handlers: [DeeplinkHandlerProtocol]
init(handlers: [DeeplinkHandlerProtocol]) {
self.handlers = handlers
}
}
extension DeeplinkCoordinator: DeeplinkCoordinatorProtocol {
@discardableResult
func handleURL(_ url: URL) -> Bool{
guard let handler = handlers.first(where: { $0.canOpenURL(url) }) else {
return false
}
handler.openURL(url)
return true
}
}
Copy the code
Now we can define separate handlers, one for each different path. Let’s start with the simplest account journey.
final class AccountDeeplinkHandler: DeeplinkHandlerProtocol { private weak var rootViewController: UIViewController? init(rootViewController: UIViewController?) { self.rootViewController = rootViewController } // MARK: - DeeplinkHandlerProtocol func canOpenURL(_ url: URL) -> Bool { return url.absoluteString == "deeplink://account" } func openURL(_ url: URL) { guard canOpenURL(url) else { return } // mock the navigation let viewController = UIViewController() viewController.title = "Account" viewController.view.backgroundColor = .yellow rootViewController? .present(viewController, animated: true) } }Copy the code
For simplicity, I just test for matching urls and navigate to the correct screen. I also set the background color to see what my landing spot would be. In your case, we could just set the correct UIViewController instead of empty.
I do the same thing for different video journeys.
final class VideoDeeplinkHandler: DeeplinkHandlerProtocol { private weak var rootViewController: UIViewController? init(rootViewController: UIViewController?) { self.rootViewController = rootViewController } // MARK: - DeeplinkHandlerProtocol func canOpenURL(_ url: URL) -> Bool { return url.absoluteString.hasPrefix("deeplink://videos") } func openURL(_ url: URL) { guard canOpenURL(url) else { return } // mock the navigation let viewController = UIViewController() switch url.path { case "/new": viewController.title = "Video Editing" viewController.view.backgroundColor = .orange default: viewController.title = "Video Listing" viewController.view.backgroundColor = .cyan } rootViewController? .present(viewController, animated: true) } }Copy the code
Now we can inject them into DeeplinkCoordinator and have it handle the correct route. We’ll have two variations, the first for AppDelegate.
class AppDelegate: UIResponder, UIApplicationDelegate { lazy var deeplinkCoordinator: DeeplinkCoordinatorProtocol = { return DeeplinkCoordinator(handlers: [ AccountDeeplinkHandler(rootViewController: self.rootViewController), VideoDeeplinkHandler(rootViewController: self.rootViewController) ]) } var rootViewController: UIViewController? { return window? .rootViewController } // ... func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool { return deeplinkCoordinator.handleURL(url) } }Copy the code
The second is SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate { lazy var deeplinkCoordinator: DeeplinkCoordinatorProtocol = { return DeeplinkCoordinator(handlers: [ AccountDeeplinkHandler(rootViewController: self.rootViewController), VideoDeeplinkHandler(rootViewController: self.rootViewController) ]) }() var rootViewController: UIViewController? { return window? .rootViewController } // ... func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { guard let firstUrl = URLContexts.first? .url else { return } deeplinkCoordinator.handleURL(firstUrl) }Copy the code
We can test it again as we have so far, hopefully landing on the right screen (expect orange background).
xcrun simctl openurl booted "deeplink://videos/new"
Copy the code
In summary, once the URL scheme is set up, we define a funnel to capture all the deep links used to open the application, and use protocol-oriented programming to create multiple implementations of the handler, one for each particular path.
This implementation can be extended for newer paths and can easily be unit tested to ensure that everything works as expected.
Having said that, there may be some improvements for more secure behavior, such as validating full paths instead of relative paths. Only the present is navigated, but it focuses on the handler rather than the transformation itself.
In the security instructions, if you are still passing parameters in deep links, be sure to validate the expected types and values. If we’re not careful, it could expose different injection vulnerabilities.
From there, you should have a good idea of how to use and handle deep links to open your application and jump to a specific screen. This code can be found on Github.
Here also recommend some interview related content, WISH you dig friends can get satisfactory offer!
-
1.BAT and other major manufacturers iOS interview questions + answers
-
2.Must-read books for Advanced Development in iOS (Classics must Read)
-
3.IOS Development Advanced Interview “resume creation” guide
-
(4)IOS Interview Process to the basics