Working with Deep Linking
1. Implementing URL Schemes
| Property | Description | Example |
|---|---|---|
| URL Scheme | Custom scheme used to open the app from a deep link | myapp:// |
| Info.plist Entry | Register the URL scheme in the app's Info.plist | |
Example: Handling URL Scheme in AppDelegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
// Handle the URL here
if url.scheme == "myapp" {
// Process deep link
return true
}
return false
}
2. Handling Universal Links
| Property | Description | Notes |
|---|---|---|
| associatedDomains | Array of associated domains for Universal Links | Must be declared in app's Info.plist under "Associated Domains" |
| continueUserActivity | Method to handle Universal Link activation | Implement in AppDelegate or SceneDelegate |
| userActivity | Activity object containing Universal Link data | Contains URL and other metadata from the link |
Example: Implementing Universal Link Handling
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
// Handle the Universal Link URL here
handleUniversalLink(url)
return true
}
func handleUniversalLink(_ url: URL) {
// Process the URL and navigate to appropriate screen
print("Received Universal Link: \(url)")
}
Note: Ensure your app's Associated Domains entitlement is properly configured in Xcode and that the
.well-known/apple-app-site-association file is correctly set up on your domain.Warning: Universal Links require a valid SSL certificate and proper configuration of the
apple-app-site-association file to function correctly.3. Configuring Associated Domains
| Property | Value | Description |
|---|---|---|
| associated-domains | applinks:example.com |
Defines the domain that can be used for universal links |
| associated-domains | applinks:blog.example.com |
Defines a subdomain for universal links |
Example: Configuring Associated Domains in Xcode
// In your app's Info.plist
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
<string>applinks:blog.example.com</string>
</array>
Note: The
applinks: prefix is required for universal links to function correctly.Warning: Make sure the domain is properly configured with a
apple-app-site-association file to enable universal links.4. Processing Incoming URLs
| Property | Description | Example |
|---|---|---|
| application(_:didReceiveRemoteNotification:fetchCompletionHandler:) | Method called when a remote notification is received | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { |
| application(_:openURL:options:) | Method to handle URL opening in iOS 9+ | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { |
| application(_:continue:restorationHandler:) | Method to handle universal links | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { |
Example: Parsing URL Components
import UIKit
class URLHandler {
func handleURL(_ url: URL) {
guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: false) else { return }
print("Scheme: \(components.scheme ?? "")")
print("Host: \(components.host ?? "")")
print("Path: \(components.path)")
if let queryItems = components.queryItems {
for item in queryItems {
print("Query: \(item.name) = \(item.value ?? "")")
}
}
}
}
Note: Ensure that your app's Info.plist includes the URL schemes and associated domains to properly handle incoming URLs.
5. Using NSUserActivity for Handoff
| Property | Value | Description |
|---|---|---|
| activityType | String |
Unique identifier for the type of activity |
| title | String |
User-readable title of the activity |
| userInfo | [String: Any] |
Dictionary of data to pass between app instances |
| webpageURL | URL? |
URL to open in a web browser if needed |
Example: Creating an NSUserActivity
let activity = NSUserActivity(activityType: "com.example.openItem")
activity.title = "Open Item"
activity.userInfo = ["itemId": "12345"]
activity.webpageURL = URL(string: "https://example.com/item/12345")
activity.isEligibleForHandoff = true
Note: Ensure
isEligibleForHandoff is set to true for activities you want to support handoff.
Warning: The
activityType must be unique to your app and follow reverse domain naming.
Handoff Workflow
- App creates
NSUserActivitywith relevant data - Activity is made eligible for handoff via
isEligibleForHandoff - User initiates handoff from one device to another
- App on receiving device restores state using
userInfo
6. Implementing Spotlight Search Integration
| Property | Value | Description |
|---|---|---|
| spotlightIdentifier | String |
Unique identifier for the item in Spotlight search |
| domainIdentifier | String |
Domain to categorize the item in Spotlight search |
| title | String |
Title of the item shown in Spotlight search results |
| contentDescription | String |
Description of the content for accessibility |
| contentType | String |
Content type for Spotlight search (e.g., com.example.document) |
Example: Configuring Spotlight Search Item
import CoreSpotlight
import UniformTypeIdentifiers
let attributeSet = CSSearchableItemAttributeSet(itemContentType: UTType.document.identifier)
attributeSet.title = "My Document"
attributeSet.contentDescription = "A sample document for Spotlight search"
attributeSet.keywords = ["document", "sample"]
attributeSet.domainIdentifier = "com.example.documents"
let item = CSSearchableItem(
uniqueIdentifier: "doc-123",
domainIdentifier: "com.example.documents",
attributeSet: attributeSet
)
Example: Indexing Content with Spotlight
import CoreSpotlight
let searchableItems = [item]
CSSearchableIndex.default().indexSearchableItems(searchableItems) { error in
if let error = error {
print("Error indexing item: \(error)")
} else {
print("Item indexed successfully")
}
}
Note: Ensure that your app's
Info.plist includes the appropriate UTExportedTypeDeclarations to support indexing content with Spotlight.
Warning: Spotlight search indexing is asynchronous. Make sure to handle errors and update UI accordingly.
| Method | Description | Return Type |
|---|---|---|
| indexSearchableItems(_:completionHandler:) | Indexes searchable items with Spotlight | Void |
| deleteSearchableItems(withIdentifiers:domainIdentifier:completionHandler:) | Deletes searchable items from Spotlight | Void |
| deleteSearchableItems(withDomainIdentifiers:completionHandler:) | Deletes all searchable items in a domain | Void |
7. Working with Siri Shortcuts
Siri Shortcuts allow users to trigger actions in your app through voice commands or SiriKit integrations. They are built using the Intents framework in iOS.
| Component | Description | Notes |
|---|---|---|
| Intent | An action or task that can be performed by Siri. | Defined in the Intent Definition file (.intentdefinition). |
| Intent Extension | A separate target in your app that handles intent processing. | Must be enabled in the app’s capabilities and configured in the app’s Info.plist. |
| SiriKit | Framework that enables Siri integration. | Part of the Intents framework; supports custom and built-in intents. |
Example: Defining a Custom Intent
import Intents
@available(iOS 14.0, *)
class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
// Handle the intent here
return self
}
}
Example: Handling an Intent in App
import Intents
func handle(intent: INIntent) {
if let weatherIntent = intent as? INGetWeatherIntent {
// Process the weather intent
}
}
Note: Siri Shortcuts require the
NSUserActivity and IntentsUI frameworks to be properly configured in your app.
Warning: Custom intents must be registered in the app’s
Info.plist and defined in the Intent Definition file to be recognized by Siri.
Key Steps for Siri Shortcuts
- Create an Intent Definition file in your project.
- Define custom intents using the
.intentdefinitionfile. - Implement an Intent Extension target to handle the intents.
- Register the app’s capabilities for Siri Shortcuts in Xcode.
8. Handling Quick Actions
| Action | Description | Implementation |
|---|---|---|
| application(_:performActionFor:completionHandler:) | Handles quick actions when app is launched | func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {<br> switch shortcutItem.type {<br> case "com.example.openProfile":<br> // Navigate to profile screen<br> completionHandler(true)<br> default:<br> completionHandler(false)<br> }<br>} |
| application(_:didFinishLaunchingWithOptions:) | Initializes quick action handling on app launch | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {<br> if let shortcutItem = launchOptions[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {<br> handleQuickAction(shortcutItem)<br> }<br> return true<br>} |
Example: Defining Quick Action in Info.plist
<key>UIApplicationSceneManifest</key><br><dict><br> <key>UISceneConfigurations</key><br> <dict><br> <key>UIWindowSceneSessionRoleApplication</key><br> <dict><br> <key>UISceneDelegateClassName</key><br> <string>SceneDelegate</string><br> <key>UISceneConfigurations</key><br> <dict><br> <key>UIWindowSceneSessionRoleApplication</key><br> <dict><br> <key>UISceneDelegateClassName</key><br> <string>SceneDelegate</string><br> </dict><br> </dict><br> </dict><br> </dict><br></dict>
Note: Quick actions must be defined in
Info.plist under UIApplicationShortcutItems.Warning: Quick actions are only available on iOS 13 and above.
9. Using Deferred Deep Linking
Deferred deep linking allows you to redirect users to specific content within your app even when they haven't installed it yet. This is particularly useful for marketing campaigns or referral systems.
| Property | Description | Example |
|---|---|---|
| deferredDeepLinkURL | URL that is used to handle deferred deep links | https://example.com/deferred?user_id=123 |
| appInstallStatus | Indicates whether the app is installed or not | installed or not_installed |
Example: Handling Deferred Deep Links
import UIKit
import UserNotifications
class DeferredDeepLinkHandler {
func handleDeferredLink(_ url: URL) {
// Check if the app is installed
if UIApplication.shared.applicationState == .active {
// App is already open, navigate to content
navigateToContent(url)
} else {
// Store URL for later use when app opens
UserDefaults.standard.set(url.absoluteString, forKey: "deferredLink")
}
}
func navigateToContent(_ url: URL) {
// Parse the URL and navigate to appropriate screen
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return }
// Example logic to extract parameters
if let userId = components.queryItems?.first(where: { $0.name == "user_id" })?.value {
// Navigate to user-specific content
print("Navigating to user content for ID: \(userId)")
}
}
}
Note: Deferred deep links are handled differently depending on the platform (iOS, Android) and the service used (e.g., Branch, Firebase Dynamic Links). Ensure that your deep link handling logic is consistent across platforms.
Warning: Make sure to validate the URL before navigating to prevent security issues. Always sanitize and verify the parameters received from the deferred link.
10. Validating Deep Link Parameters
| Parameter | Type | Validation Rule | Notes |
|---|---|---|---|
| url | String |
Must start with myapp:// |
Required scheme for deep linking |
| path | String |
Must match /user/(\d+) |
Accepts numeric user IDs only |
| query | [String: String] |
Keys must be id, action |
Only allowed query parameters |
Example: Validating Deep Link URL
let url = URL(string: "myapp://user/123?action=profile")!
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
guard let scheme = components.scheme, scheme == "myapp" else {
print("Invalid scheme")
return
}
guard let path = components.path, path.hasPrefix("/user/") else {
print("Invalid path")
return
}
let userId = String(path.dropFirst(6)) // Extracts "123"
if !userId.matches(regex: "^\\d+$") {
print("Invalid user ID")
return
}
guard let queryItems = components.queryItems else {
print("No query items")
return
}
let allowedKeys = ["id", "action"]
for item in queryItems {
if !allowedKeys.contains(item.name) {
print("Invalid query key: \(item.name)")
return
}
}
Note: Use
URLComponents to parse and validate URL structure safely.