A modern Swift 6 implementation of deep link routing for iOS and tvOS applications. Built with strict concurrency, async/await, and SwiftUI support.
DeepLinkKit-swift is a complete rewrite of the original DeepLinkKit by Button, Inc., modernized for Swift 6 with zero Objective-C code.
✨ Pure Swift 6 - Strict concurrency compliance with Sendable types 🎯 Type-Safe Routing - Pattern matching with compile-time safety ⚡️ Async/Await - Modern asynchronous API throughout 🔒 Thread-Safe - MainActor isolation for UI operations 📱 SwiftUI Ready - View modifiers and environment integration 🔗 AppLinks Support - Full AppLinks 1.0 compatibility 🧪 Fully Tested - 100+ unit tests with comprehensive coverage
Add DeepLinkKit-swift to your project using Xcode:
- File > Add Package Dependencies...
- Enter:
https://github.com/yourusername/DeepLinkKit-swift - Select version/branch
- Add to your target
Or add to your Package.swift:
dependencies: [
.package(url: "https://github.com/yourusername/DeepLinkKit-swift", from: "1.0.0")
]import DeepLinkKit_swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let router = DeepLinkRouter()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Register routes
router["/products/:id"] = { deepLink in
guard let id = deepLink["id"] else { return }
// Navigate to product detail
print("Opening product: \(id)")
}
return true
}
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
Task {
try? await router.handle(url: url)
}
return true
}
}import SwiftUI
import DeepLinkKit_swift
@main
struct MyApp: App {
@StateObject private var routing = ObservableRouter()
init() {
setupRoutes()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(routing)
.handleDeepLinks(with: routing.router)
}
}
private func setupRoutes() {
routing["/products/:id"] = { [routing] deepLink in
guard let id = deepLink["id"] else { return }
routing.navigate(to: ProductRoute(id: id))
}
}
}let router = DeepLinkRouter()
// Simple route
router["/settings"] = { _ in
print("Opening settings")
}
// Route with parameters
router["/users/:username"] = { deepLink in
let username = deepLink["username"]
print("Opening profile for: \(username ?? "unknown")")
}
// Multiple parameters
router["/products/:category/:id"] = { deepLink in
let category = deepLink["category"]
let id = deepLink["id"]
print("Category: \(category ?? ""), ID: \(id ?? "")")
}
// Handle a URL
let url = URL(string: "myapp://users/john")!
try await router.handle(url: url)// Wildcard routes
router["/products/*"] = { deepLink in
print("Any product route")
}
// Host matching (no leading slash)
router["timeline"] = { _ in
print("Timeline route")
}
// Matches: myapp://timeline
// Full URL patterns (with scheme)
router["myapp://settings"] = { _ in
print("Settings with specific scheme")
}router["/search"] = { deepLink in
let query = deepLink["q"]
let source = deepLink["source"]
print("Search for '\(query ?? "")' from \(source ?? "app")")
}
// Handles: myapp://search?q=test&source=email// Build a deep link
var builder = DeepLinkBuilder(scheme: "myapp", path: "/products")
builder["id"] = "123"
builder["source"] = "email"
builder.callbackURL = URL(string: "sourceapp://callback")
let url = builder.buildURL()
// Result: myapp://products?id=123&source=email&dpl_callback_url=...
// Open the URL
await UIApplication.shared.open(url)do {
try await router.handle(url: url)
} catch RouterError.routeNotFound(let url) {
print("No route found for: \(url)")
} catch RouterError.handlerFailed(let route, let error) {
print("Handler for \(route) failed: \(error)")
} catch {
print("Unexpected error: \(error)")
}// Only handle deep links when user is logged in
let router = DeepLinkRouter {
return UserSession.current.isLoggedIn
}func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
Task {
try? await router.handle(userActivity: userActivity)
}
return true
}struct ContentView: View {
@EnvironmentObject var routing: ObservableRouter
var body: some View {
NavigationStack {
VStack {
Button("Open Product") {
Task {
try? await routing.handle(
url: URL(string: "myapp://products/123")!
)
}
}
}
}
.onChange(of: routing.lastHandledURL) { _, url in
print("Handled: \(url?.absoluteString ?? "nil")")
}
}
}router["/products/:id"] = { deepLink in
// Access AppLinks metadata
if let referralURL = deepLink.appLinksReferralURL {
print("Came from: \(referralURL)")
}
if let appName = deepLink.appLinksReferralAppName {
print("Referring app: \(appName)")
}
// Access custom extras
let extras = deepLink.appLinksExtras
}
// Create an AppLinks deep link
var builder = DeepLinkBuilder(scheme: "myapp", path: "/products/123")
builder.setAppLinksReferral(
referralURL: URL(string: "sourceapp://")!,
appName: "Source App"
)For complex routing that requires view controller presentation:
@MainActor
class ProductRouteHandler: RouteHandler {
func targetViewController(for deepLink: DeepLink) -> (any TargetViewController)? {
let vc = ProductDetailViewController()
return vc
}
func shouldHandle(deepLink: DeepLink) -> Bool {
// Conditional logic
return deepLink["id"] != nil
}
}
// Register the handler
router.register(route: "/products/:id") { deepLink in
let handler = ProductRouteHandler()
try await handler.handle(deepLink: deepLink)
}| Objective-C (1.x) | Swift 6 (2.0) |
|---|---|
DPLDeepLink |
DeepLink |
DPLMutableDeepLink |
DeepLinkBuilder |
DPLDeepLinkRouter |
DeepLinkRouter |
handleURL:withCompletion: |
handle(url:) async throws |
| Block handlers | async throws closures |
Example Migration:
// Old Objective-C
self.router[@"/products/:id"] = ^(DPLDeepLink *link) {
NSString *productId = link[@"id"];
[self showProductWithId:productId];
};// New Swift 6
router["/products/:id"] = { deepLink in
guard let productId = deepLink["id"] else { return }
await showProduct(withId: productId)
}- iOS 17.0+ / tvOS 16.0+ / macCatalyst 16.0+ / macOS 13.0+ / watchOS 9.0+
- Swift 6.0+
- Xcode 16.0+
Full documentation is available in the source code using DocC. Generate documentation in Xcode:
Product > Build DocumentationRun tests in Xcode: Cmd+U
Or via command line:
swift testDeepLinkKit-swift is released under the MIT License. See LICENSE for details.
This project is a Swift 6 modernization of the original DeepLinkKit created by Button, Inc. We are grateful for their pioneering work in making deep linking easier for iOS developers.
- Original DeepLinkKit: https://github.com/button/DeepLinkKit
- Copyright © 2015-2016 Button, Inc.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues, questions, or contributions, please visit:
- GitHub Issues: https://github.com/yourusername/DeepLinkKit-swift/issues
- Documentation: See inline documentation and DocC
Made with ❤️ by Darren Ehlers and DoubleNode, LLC Based on the original DeepLinkKit by Button, Inc.