CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

71

Quality

89%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

deeplinks.mdskills/swiftui-navigation/references/

Deep links and navigation

Contents

  • Intent
  • Core patterns
  • Example: router entry points
  • Example: attach to a root view
  • Design choices to keep
  • Pitfalls
  • Universal Links
  • Custom URL Schemes
  • NSUserActivity Continuation (Handoff)

Intent

Route external URLs into in-app destinations while falling back to system handling when needed.

Core patterns

  • Centralize URL handling in the router (handle(url:), handleDeepLink(url:)).
  • Inject an OpenURLAction handler that delegates to the router.
  • Use .onOpenURL for app scheme links and convert them to web URLs if needed.
  • Let the router decide whether to navigate or open externally.

Example: router entry points

@MainActor
final class RouterPath {
  var path: [Route] = []
  var urlHandler: ((URL) -> OpenURLAction.Result)?

  func handle(url: URL) -> OpenURLAction.Result {
    if isInternal(url) {
      navigate(to: .status(id: url.lastPathComponent))
      return .handled
    }
    return urlHandler?(url) ?? .systemAction
  }

  func handleDeepLink(url: URL) -> OpenURLAction.Result {
    // Resolve federated URLs, then navigate.
    navigate(to: .status(id: url.lastPathComponent))
    return .handled
  }
}

Example: attach to a root view

extension View {
  func withLinkRouter(_ router: RouterPath) -> some View {
    self
      .environment(
        \.openURL,
        OpenURLAction { url in
          router.handle(url: url)
        }
      )
      .onOpenURL { url in
        router.handleDeepLink(url: url)
      }
  }
}

Design choices to keep

  • Keep URL parsing and decision logic inside the router.
  • Avoid handling deep links in multiple places; one entry point is enough.
  • Always provide a fallback to @Environment(\.openURL) via OpenURLAction.

Pitfalls

  • Don’t assume the URL is internal; validate first.
  • Avoid blocking UI while resolving remote links; use Task.

Universal Links

Universal links let iOS open your app when a user taps a standard HTTPS URL, with no custom scheme required. They require server-side configuration and an Associated Domains entitlement.

Apple App Site Association (AASA)

Host a JSON file at https://example.com/.well-known/apple-app-site-association (no file extension, served with Content-Type: application/json):

{
  "applinks": {
    "details": [
      {
        "appIDs": ["TEAMID.com.example.app"],
        "components": [
          { "/": "/items/*", "comment": "Match item detail paths" },
          { "/": "/profile/*" }
        ]
      }
    ]
  }
}

Key rules:

  • AASA must be served over HTTPS with a valid certificate — no redirects.
  • Apple's CDN caches the file; updates can take 24-48 hours. Use https://app-site-association.cdn-apple.com/a/v1/example.com to verify the cached version.
  • Use components (modern) over the legacy paths array.

Associated Domains entitlement

In your app's .entitlements file (or Signing & Capabilities in Xcode), add:

com.apple.developer.associated-domains = [
    "applinks:example.com",
    "applinks:www.example.com"
]

For development/testing, prefix with applinks:example.com?mode=developer to bypass the CDN cache.

Handling Universal Links in SwiftUI

Use .onOpenURL for link-based launches and onContinueUserActivity for NSUserActivity-based handoff:

@main
struct MyApp: App {
    @State private var router = Router()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(router)
                .onOpenURL { url in
                    router.handle(url: url)
                }
                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
                    guard let url = activity.webpageURL else { return }
                    router.handle(url: url)
                }
        }
    }
}

Docs: Supporting universal links

Custom URL Schemes

Custom URL schemes (e.g., myapp://) let other apps or websites open your app. They do not require server configuration but offer no fallback if the app is not installed.

Registering in Info.plist

Add CFBundleURLTypes to your target's Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
    <key>CFBundleURLName</key>
    <string>com.example.myapp</string>
  </dict>
</array>

Handling with .onOpenURL

.onOpenURL { url in
    // url.scheme == "myapp"
    // url.host == "items", url.pathComponents for routing
    guard url.scheme == "myapp" else { return }
    router.handle(url: url)
}

Prefer universal links over custom schemes for publicly shared links — they provide a better UX (web fallback) and are more secure (domain-verified).

NSUserActivity Continuation (Handoff)

Handoff lets users start an activity on one device and continue it on another. SwiftUI provides .onContinueUserActivity and .userActivity modifiers.

Advertising an activity

struct ItemDetailView: View {
    let item: Item

    var body: some View {
        ScrollView { /* content */ }
            .userActivity("com.example.viewItem") { activity in
                activity.title = item.title
                activity.isEligibleForHandoff = true
                activity.isEligibleForSearch = true
                activity.targetContentIdentifier = item.id.uuidString
                activity.webpageURL = URL(string: "https://example.com/items/\(item.id)")
            }
    }
}

Receiving a continued activity

.onContinueUserActivity("com.example.viewItem") { activity in
    guard let id = activity.targetContentIdentifier else { return }
    router.navigate(to: .item(id: id))
}

Key rules:

  • Activity types must be declared in Info.plist under NSUserActivityTypes.
  • Set isEligibleForHandoff = true and optionally isEligibleForSearch / isEligibleForPrediction.
  • Provide a webpageURL as fallback when the app is not installed on the receiving device.

skills

CHANGELOG.md

README.md

tile.json