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:
- 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:
-
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).
-
apple-app-site-associationFile:- Create a JSON file named
apple-app-site-association(without a.jsonextension). - Host this file on your website at the root or in the
.well-knowndirectory (e.g.,https://yourwebsite.com/apple-app-site-associationorhttps://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-associationfile: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" } ] } ] } } - Create a JSON file named
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
}
}