Skip to content

DoubleNodeOpen/DeepLinkKit-swift

Repository files navigation

DeepLinkKit-swift

Swift 6.0 iOS 17.0+ tvOS 16.0+ MIT License

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.

Features

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

Installation

Swift Package Manager

Add DeepLinkKit-swift to your project using Xcode:

  1. File > Add Package Dependencies...
  2. Enter: https://github.com/yourusername/DeepLinkKit-swift
  3. Select version/branch
  4. Add to your target

Or add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/yourusername/DeepLinkKit-swift", from: "1.0.0")
]

Quick Start

UIKit App

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
    }
}

SwiftUI App

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))
        }
    }
}

Usage Examples

Basic Routing

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)

Pattern Matching

// 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")
}

Query Parameters

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

Creating Deep Links

// 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)

Error Handling

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)")
}

Conditional Routing

// Only handle deep links when user is logged in
let router = DeepLinkRouter {
    return UserSession.current.isLoggedIn
}

Universal Links

func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    Task {
        try? await router.handle(userActivity: userActivity)
    }
    return true
}

SwiftUI Integration

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")")
        }
    }
}

AppLinks Support

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"
)

Advanced Features

Protocol-Based Handlers

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)
}

Migration from Original DeepLinkKit

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)
}

Requirements

  • iOS 17.0+ / tvOS 16.0+ / macCatalyst 16.0+ / macOS 13.0+ / watchOS 9.0+
  • Swift 6.0+
  • Xcode 16.0+

Documentation

Full documentation is available in the source code using DocC. Generate documentation in Xcode:

Product > Build Documentation

Testing

Run tests in Xcode: Cmd+U

Or via command line:

swift test

License

DeepLinkKit-swift is released under the MIT License. See LICENSE for details.

Acknowledgments

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.

Authors

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues, questions, or contributions, please visit:


Made with ❤️ by Darren Ehlers and DoubleNode, LLC Based on the original DeepLinkKit by Button, Inc.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages