/DeepCoordinatorKit

Simple library that help to handle deep-links right in specific content

Primary LanguageSwift

DeepCoordinatorKit

CI

Lightweight library that pass deeplinks trought coordiantor tree and respond on them. Depends on CoordinatorKit.

Navigation

Installation

Ready to use on iOS 9+. iOS only.

Swift Package Manager

In Xcode go to FilePackagesUpdate to Latest Package Versions and insert url:

https://github.com/kotostrophe/DeepCoordiantorKit

or add it to the dependencies value of your Package.swift:

dependencies: [
    .package(url: "https://github.com/kotostrophe/DeepCoordinatorKit", .upToNextMajor(from: "1.0.0")),
]

Manually

If you prefer not to use any of dependency managers, you can integrate manually. Put Sources/DeepCoordiantorKit folder in your Xcode project. Make sure to enable Copy items if needed and Create groups.

Library is developed with dependency of another library called CoordinatorKit. If you decided to add this library manually then do that with depended part.

Usage

DeepLinkResponder it's a base protocol that help to pass deeplink throught the nodes. By default protocol DeepLinkResponder uses only Coordinatable protocol from CoordinatorKit. It help for building tree-like structure from nodes.

public protocol DeepLinkResponder: AnyObject {
    var deepLinkLocator: DeepLinkLocatorProtocol { get }    // store all the deeplink handler for this object

    func becomeFirstResponder(child: DeepLinkResponder?)    // run in case of first responder
    func canRespond(on path: String) -> Bool                // supplementary method for seeking target handler 
    func respond(on path: String)                           // activates handler that respond on deeplink path
    func hitTest(with path: String) -> DeepLinkResponder?   // mechanism of finding target deeplink handler
}

Deeplink passing throught the initial node (root) and searching node that can respond on it. Protocol DeepLinkResponder have default body methods. Check that in file Coordinatable+DeepLinkResponder.swift

Default realization of canRespond(on:) method compare all available routes with deeplink route path. If any route with similar path exists then return true.

func canRespond(on path: String) -> Bool {
    deepLinkLocator.routes
        .contains(where: { route in path == route })
}

Default realization of respond(on:) method find first responder with similar path and prepare action if handler exists.

func respond(on path: String) {
    deepLinkLocator.routes
        .first(where: { route in path == route })?
        .prepareAction(with: path)()
}

hitTest(with:) it is main method of handling deeplink. It composit two previous methods canRespond(on:) and respond(on:). It uses recoursive mechanism to find deeplink responder. By the structure it looks like graph depth-first search.

hitTest(with:) must be called to the initial point (root) of coordinators tree

func hitTest(with path: String) -> DeepLinkResponder? {
    if canRespond(on: path) { return self }
    for targetResponder in childLocator.coordiantors.compactMap({ $0 as? DeepLinkResponder }) {
        guard let targetResponder = targetResponder.hitTest(with: path) else { continue }
        return targetResponder
    }
    return nil
}

Example

applicationCoordinator uses a initial point of nodes tree. When deeplink passes trought the application it activates method findFirstResponderIfNeeded(of:) that run method hitTest(with:) inside of it. When responder will be found it will respond on deeplink.

import UIKit

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    // MARK: - Properties
    
    var applicationCoordinator: ApplicationCoordinatorProtocol?
    var window: UIWindow?

    // MARK: - UIWindowSceneDelegate
    
    ...
    
    func scene(_ scene: UIScene, openURLContexts urlContexts: Set<UIOpenURLContext>) {
        findFirstResponderIfNeeded(of: urlContexts.first?.url.path)
    }

    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        findFirstResponderIfNeeded(of: userActivity.webpageURL?.path)
    }
    
    // MARK: - Deeplink
    
    func findFirstResponderIfNeeded(of deeplinkPath: String?) {
        guard let path = deeplinkPath else { return }
        guard let firstResponder = applicationCoordinator?.hitTest(with: path) else { return }
        firstResponder.becomeFirstResponder(child: nil)
        firstResponder.respond(on: path)
    }
}

Example app

In the repository deeplink-responder presented example of coordiantor pattern via using CoordinatorKit. As an addition there was developed deeplink handling mechanism via DeepCoordinatorKit.