🇨🇳 中文 · Documentation · Blog tutorial
A SwiftUI routing foundation for modular Apple-platform apps.
iOS 17+ · macOS 14+ · tvOS 17+ · watchOS 10+ · Swift 6
URLRouter gives an app one predictable front door for navigation. A button, a push notification, a Universal Link, or another Feature can all submit the same HTTPS URL. URLRouter validates it, lets the owning Feature resolve it, and updates SwiftUI navigation in the presentation style declared by the URL.
It is useful once an app has more than a few screens: callers no longer need to know another Feature's View type, initializer, or navigation container. They only use that Feature's documented URL contract.
| If you want to… | Start here |
|---|---|
| Open one page from a SwiftUI button | Five-minute quick start |
| Add Universal Links and a modular Feature Package | Getting started guide |
| Understand ownership and URL contracts | Architecture guide |
| Add remote switches, queueing, telemetry, or CI checks | Production governance |
| Follow a complete beginner-friendly walkthrough | Technical blog |
The README is deliberately short. The linked guides contain the rationale, production details, and Chinese counterparts without forcing every app to adopt advanced features on day one.
In Xcode, choose File → Add Package Dependencies… and add:
https://github.com/relaxfinger/URLRouter.git
Add URLRouter to the App target and to any Feature Package that declares a
RouteModule.
dependencies: [
.package(url: "https://github.com/relaxfinger/URLRouter.git", from: "2.4.7")
]URLRouterPolicyProvider is an optional product from the same package. Add it
only when the App needs a cache-first remote route-policy lifecycle. Feature
packages should normally depend on URLRouter only.
.product(name: "URLRouter", package: "URLRouter")
// App-shell target only, when remote policy is needed:
.product(name: "URLRouterPolicyProvider", package: "URLRouter")import SwiftUI
import URLRouter
enum ArticleFeature {
static let module = RouteModule(
id: "articles",
resolve: { link in
guard case ["articles", let id] = link.pathComponents, !id.isEmpty else {
return nil
}
return ModuleRoute(
moduleID: "articles",
routeID: "detail",
parameters: ["id": id]
)
},
destination: { route in
guard route.routeID == "detail", let id = route.parameters["id"] else {
return nil
}
return AnyView(ArticleDetailView(articleID: id))
}
)
}Returning nil from resolve means “this URL is not mine.” A Feature can own
many URLs; keep their parsing and destination creation together.
import SwiftUI
import URLRouter
@main
struct CompanyApp: App {
@State private var router = ModuleRouter()
private let registry = ModuleRouteRegistry(modules: [ArticleFeature.module])
var body: some Scene {
WindowGroup {
RouterHost(router: router) {
ContentView()
} destination: { route in
registry.destination(for: route)
}
.moduleLinkRouting(
router: router,
registry: registry,
allowedHosts: ["example.com"]
)
}
}
}Install RouterHost and moduleLinkRouting exactly once per scene. Keep one
ModuleRouter per window so multi-window navigation stays independent.
struct ArticleRow: View {
@Environment(\.openURL) private var openURL
let articleID: String
var body: some View {
Button("Read article") {
openURL(URL(string: "https://example.com/articles/\(articleID)?presentation=push&version=1")!)
}
}
}The URL is the public contract:
https://example.com/articles/42?presentation=push&version=1
presentation is required and may be push, tab, sheet, or
fullScreenCover. Start with one route like this. Then follow the
getting-started guide to use URL builders,
Universal Links, tabs, and a versioned route contract safely.
Do not build every feature at once.
| Need | Add |
|---|---|
| Web links should open the same route as the app | Universal Links |
| A Feature or all routing must be remotely paused | URLRouterPolicyProvider and a ModuleRoutePolicyStore |
| A notification, link, and button can arrive together | One ModuleRouteCoordinator per scene |
| Support needs to explain why a link failed | ModuleRouteObservability |
| Marketing or web clients depend on published links | RouteContracts.json and contract CI |
These are opt-in. URLRouter does not choose your network client, authentication flow, remote-config vendor, analytics vendor, or backend. The App owns those decisions; the package provides focused routing seams.
URLRouterDemo is an iOS 17+ reference app. It shows local Feature Packages,
all four presentation styles, cross-package navigation, a cache-first policy
lifecycle, telemetry, and coordinated concurrent routes.
swift test
swift Scripts/validate_route_contract.swift RouteContracts.jsonThe core library and RouterHost support all four listed Apple platforms. On
macOS, SwiftUI presents a fullScreenCover route as a sheet because macOS does
not provide fullScreenCover.
Sources/URLRouter/ # core routing library
Sources/URLRouterPolicyProvider/ # optional policy-refresh product
Tests/ # SwiftPM unit tests
Features/ # local Feature-package examples
URLRouterDemo/ # executable iOS reference app
docs/ # task-focused documentation
URLRouter is available under the MIT License. Before contributing, read CONTRIBUTING.md. For support, security reports, and the maintenance policy, see SUPPORT.md, SECURITY.md, and MAINTENANCE.md.