-
Notifications
You must be signed in to change notification settings - Fork 25
Deeplinking
ComposableDeeplinking is part of the ComposableNavigator package and enables you to parse url scheme like Deeplink
s to navigation paths.
- Open the project file
- Select the app target
- Select the info tab
- Add the url scheme
If your app is using the pre-iOS14 AppDelegate/SceneDelegate life cycle, you will need to override scene(_ scene:, willConnectTo session:, options connectionOptions:)
and scene(_ scene:, openURLContexts contexts:)
in order to get url scheme based Deeplink
s to work.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
private var deeplinkHandler: DeeplinkHandler?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
// setup your app here
// ...
self.deeplinkHandler = DeeplinkHandler(
navigator: navigator,
parser: .exampleApp
)
// If the app performs a cold start from a url scheme deeplink, call openURLContexts to make sure the path is correctly updated
self.scene(
scene,
openURLContexts: connectionOptions.urlContexts
)
}
func scene(
_ scene: UIScene,
openURLContexts contexts: Set<UIOpenURLContext>
) {
if let url = contexts.first?.url,
// the matching parameter needs to match the URL scheme
// defined in the application's project file
let deeplink = Deeplink(url: url, matching: "example") {
deeplinkHandler?.handle(deeplink: deeplink)
}
}
}
An example of how to integrate deeplinks into your SwiftUI application life cycle based application can be found in the example application contained in this repository. Since iOS14, Scene
objects allow to attach an onOpenURL
closure. Whenever the app gets opened by a url scheme deeplink, the registered perform closure is executed. The closure is also executed for universal links, so keep that in mind.
import ComposableDeeplinking
import ComposableNavigator
import SwiftUI
@main
struct ExampleApp: App {
let navigator: Navigator
let dataSource: Navigator.Datasource
let deeplinkHandler: DeeplinkHandler
init() {
dataSource = Navigator.Datasource(
root: HomeScreen()
)
navigator = Navigator(dataSource: dataSource)
deeplinkHander = DeeplinkHandler(
navigator: navigator,
parser: DeeplinkParser.exampleApp
)
}
var body: some Scene {
WindowGroup {
Root(
dataSource: dataSource,
navigator: navigator,
pathBuilder: // ...
)
.onOpenURL(
perform: { url in
// the matching parameter needs to match the URL
// scheme defined in the application's project file
if let deeplink = Deeplink(url: url, matching: "example") {
deeplinkHandler.handle(deeplink: deeplink)
}
}
)
}
}
}
Deeplink parsers parse navigation paths from Deeplink
s. DeeplinkParser
s are wrapper structs around a pure (Deeplink) -> [AnyScreen]?
function and support composition. If a deeplink parser handles the input Deeplink
, it returns a navigation path in the form of an AnyScreen
array. If the deeplink parser is not responsible for parsing the deeplink, it returns nil.
Typically, applications using ComposableDeeplinking
define one central applicationDeeplinkParser
, composing all supported deeplinks.
import ComposableDeeplinking
extension DeeplinkParser {
/// Parses all supported deeplinks in the example app
///
/// Supported deeplinks:
/// * example://home/settings
/// * example://detail?id={id}
static let exampleApp: DeeplinkParser = .anyOf(
.homeSettings,
.details
)
}
The exampleApp
DeeplinkParser
is composing two deeplink parsers. The DeeplinkParser
s composed in the exampleApp
DeeplinkParser
can be compositions themselves, meaning that you can fully modularize your deeplink parsing, if needed. Use prepending(path pathToEntrypoint: [AnyScreen], to parser: DeeplinkParser)
to navigate to your module's entrypoint.
Adding support for another deeplink can be achieved by adding a third entry to the .anyOf DeeplinkParser
. Let's add a DeeplinkParser
for detail?id={id}/settings
Deeplink
s.
import ComposableDeeplinking
extension DeeplinkParser {
/// example://detail?id={id}/settings
static let detailSettings = DeeplinkParser(
parse: { deeplink in
guard deeplink.components.count == 2,
deeplink.components[0].name == "detail",
case let .value(id) = deeplink.components[0].arguments?["id"],
deeplink.components[1].name == "settings"
else {
return nil
}
return [
HomeScreen().eraseToAnyScreen(),
DetailScreen(detailID: id).eraseToAnyScreen(),
SettingsScreen().eraseToAnyScreen()
]
}
)
}
The url scheme representation are not tightly coupled to in-app navigation path and only need to contain the information required to build up a valid navigation path. This mean, that even if the application's navigation tree changes, your deeplinks can stay the same and we only need to adjust the application's deeplink parsing on the client-side.
In order to support this newly added DeeplinkParser
, we need to add it to our exampleApp
DeeplinkParser
. And that's it.
...
extension DeeplinkParser {
/// Parses all supported deeplinks in the example app
///
/// Supported deeplinks:
/// * example://home/settings
/// * example://detail?id={id}
/// * example://detail?id={id}/settings
static let exampleApp: DeeplinkParser = .anyOf(
.homeSettings,
.details,
.detailSettings
)
}
Deeplinks can also be part of a push notifications payload. We can hook into userNotificationCenter(_ center:, didReceive response:, withCompletionHandler completionHandler:)
in UNUserNotificationCenterDelegate
, extract the deeplink url from the userInfo and pass it to our DeeplinkHandler
, which replaces the current navigation path with the new navigation path, if parsing succeeds.
Generated at 2021-04-29T07:59:04+0000 using swift-doc 1.0.0-beta.6.
Types
- AnyPathBuilder
- AnyScreen
- Deeplink
- DeeplinkComponent
- DeeplinkComponent.Argument
- DeeplinkHandler
- DeeplinkParser
- EitherAB
- EitherABC
- EitherABCD
- EitherABCDE
- EitherABCDEF
- EitherABCDEFG
- EitherABCDEFGH
- EitherABCDEFGHI
- EitherABCDEFGHIJ
- IdentifiedScreen
- NavigationNode
- NavigationTreeBuilder
- Navigator
- Navigator.Datasource
- Navigator.DidAppearInvocation
- Navigator.DismissInvocation
- Navigator.GoBackToInvocation
- Navigator.GoToInvocation
- Navigator.GoToPathInvocation
- Navigator.NavigationIdentifier
- Navigator.ReplaceContentInvocation
- Navigator.ReplacePathInvocation
- Navigator.ReplaceScreenInvocation
- NavigatorKey
- OnDismissView
- PathBuilders
- PathBuilders.EmptyBuilder
- PathBuilders.WildcardView
- PathComponentUpdate
- PathUpdate
- Root
- ScreenPresentationStyle
- TreatSheetDismissAsAppearInPresenterKey
- _PathBuilder