feat: integrate new base navigation deeplink handler

This commit is contained in:
Nikita Semenov 2023-04-04 13:21:50 +03:00
parent c442ee2623
commit af044aa591
4 changed files with 113 additions and 105 deletions

View File

@ -26,21 +26,21 @@ import UIKit
open class BaseNavigationStackDeeplinkHandler<Deeplink>: DeeplinkHandler {
public var rootViewControllerKeeper: UIViewController
public var rootViewController: UIViewController
public var asAnyHandlerClosure: Closure<UIViewController, AnyDeeplinkHandler<Deeplink>?>
public init(rootViewControllerKeeper: UIViewController,
public init(rootViewController: UIViewController,
asAnyHandlerClosure: @escaping Closure<UIViewController, AnyDeeplinkHandler<Deeplink>?>) {
self.rootViewControllerKeeper = rootViewControllerKeeper
self.rootViewController = rootViewController
self.asAnyHandlerClosure = asAnyHandlerClosure
}
// MARK: - DeeplinkHandler
open func handle(deeplink: Deeplink) -> Operation? {
let handler = asAnyHandlerClosure(rootViewControllerKeeper) ?? findHandler(for: deeplink,
in: rootViewControllerKeeper)
let handler = asAnyHandlerClosure(rootViewController) ?? findHandler(for: deeplink,
in: rootViewController)
return handler?.handle(deeplink: deeplink)
}

View File

@ -5,34 +5,38 @@
## Как настроить
1. Создать представителей класса `Deeplink`
1. Создать объект, представляющий диплинк. Такой объект должен соответствовать протоколу `Hashable`
*/
import Foundation
import TIDeeplink
import UIKit
extension Deeplink {
static var editProfile: Deeplink {
Deeplink(rawValue: "editProfile")
}
static var office: Deeplink {
Deeplink(rawValue: "office")
}
enum ProjectDeeplink: Hashable {
case editProfile
case office(id: String)
}
//: 2. Создать объект, соответствующий протоколу `DeeplinkMapper`. Его задачей будет - преобразование _URL_ в диплинк
final class ProjectDeeplinkMapper: DeeplinkMapper {
private let availableDeeplinks: [Deeplink] = [.office, .editProfile]
func map(url: URL) -> ProjectDeeplink? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
func map(url: URL) -> Deeplink? {
let stringData = parse(url)
switch components.host {
case "office":
if let id = components.path.split(separator: "/").last {
return .office(id: String(id))
}
return availableDeeplinks.find(byRawValue: stringData)
}
return nil
private func parse(_ url: URL) -> String {
url.host ?? ""
case "editProfile":
return .editProfile
default:
return nil
}
}
}
@ -42,45 +46,43 @@ class MainCoordinator: DeeplinkHandler {
print("Presenting edit profile view controller")
}
func openOfficesScreen() {
print("Presenting offices view controller")
func openOfficesScreen(forId id: String) {
print("Presenting offices view controller by id: \(id)")
}
// MARK: DeeplinkHandler
func handle(deeplink: Deeplink) -> Operation? {
if deeplink == .editProfile {
func handle(deeplink: ProjectDeeplink) -> Operation? {
switch deeplink {
case .editProfile:
return BlockOperation { [weak self] in
self?.openEditProfileScreen()
}
}
if deeplink == .office {
case let .office(id):
return BlockOperation { [weak self] in
self?.openOfficesScreen()
self?.openOfficesScreen(forId: id)
}
}
return nil
}
}
/*:
**Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту
*/
final class ProjectDeeplinksService: TIDeeplinksService {
static let shared = ProjectDeeplinksService()
final class ProjectDeeplinksService: TIDeeplinksService<ProjectDeeplink, MainCoordinator, ProjectDeeplinkMapper> {
static let shared = ProjectDeeplinksService()
}
/*:
Создаем и передаем в сервис Mapper и Handler
Создаем и передаем в сервис Mapper и Handler.
> Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`
> Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`. Если Mapper и Handler передаются не в инициализаторе, то их самостоятельно необходимо привести к виду `AnyDeeplinkHandler` и `AnyDeeplinkMapper` с момощью методов: `eraseToAnyDeeplinkHandler()` и `eraseToAnyDeeplinkMapper()`
*/
let coordinator = MainCoordinator()
let mapper = ProjectDeeplinkMapper()
ProjectDeeplinksService.shared.deeplinkMapper = mapper
ProjectDeeplinksService.shared.deeplinkHandler = coordinator
ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper()
ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler()
/*:
В `AppDelegate` использвуем методы `deferredHandle(url:)` и `handlePendingDeeplinks()` для обработки диплинков.
@ -95,37 +97,45 @@ func application(_ app: UIApplication, open url: URL, options: [UIApplication.Op
return true
}
guard let url = URL(string: "app://office/") else {
guard let url = URL(string: "app://office/123") else {
fatalError()
}
application(.shared, open: url)
/*:
В качестве `DeeplinkHandler` можно использовать базовую реализацию `BaseNavigationStackDeeplinkHandler`, которая позволяет искать handler в иерархии view, способный обработать диплинк
В качестве `DeeplinkHandler` можно использовать базовую реализацию `BaseNavigationStackDeeplinkHandler`, которая позволяет искать handler в иерархии view, способный обработать диплинк.
*/
class ViewController: UIViewController, DeeplinkHandler {
func handle(deeplink: Deeplink) -> Operation? {
if deeplink == .editProfile {
protocol ProjectDeeplinkHandler: DeeplinkHandler where Deeplink == ProjectDeeplink {
}
class ViewController: UIViewController, ProjectDeeplinkHandler {
func handle(deeplink: ProjectDeeplink) -> Operation? {
switch deeplink {
case .editProfile:
return BlockOperation {
print("Presenting edit profile view controller")
}
}
if deeplink == .office {
case let .office(id):
return BlockOperation {
print("Presenting offices view controller")
print("Presenting offices view controller by id: \(id)")
}
}
return nil
}
}
//: Создание Handler. Для настройки передается rootViewController, который должен наследоваться от `UIViewController` и соответствовать протоколу `DeeplinkHandler`. С этого контроллера будет начинаться поиск обработчика
//: Создание Handler. Для настройки передается rootViewController, который должен наследоваться от `UIViewController` С этого контроллера будет начинаться поиск обработчика
let viewController = ViewController()
let navigationController = UINavigationController(rootViewController: viewController)
let handler = BaseNavigationStackDeeplinkHandler(rootViewControllerKeeper: navigationController)
let handler = BaseNavigationStackDeeplinkHandler<ProjectDeeplink>(rootViewController: navigationController) {
guard let projectHandler = $0 as? (any ProjectDeeplinkHandler) else {
return nil
}
return projectHandler.eraseToAnyDeeplinkHandler()
}
//: Далее handler может передаваться для использования в `TIDeeplinksService`
let service = TIDeeplinksService(mapper: mapper, handler: handler)

View File

@ -23,7 +23,9 @@
import Foundation
import TIFoundationUtils
open class TIDeeplinksService<Deeplink: Hashable> {
open class TIDeeplinksService<Deeplink: Hashable,
Handler: DeeplinkHandler,
Mapper: DeeplinkMapper> where Mapper.Deeplink == Deeplink, Handler.Deeplink == Deeplink {
// MARK: - Private properties
@ -39,11 +41,7 @@ open class TIDeeplinksService<Deeplink: Hashable> {
// MARK: - Init
public init<Mapper: DeeplinkMapper, Handler: DeeplinkHandler>(
mapper: Mapper? = nil,
handler: Handler? = nil,
operationQueue: OperationQueue = .main) where Mapper.Deeplink == Deeplink, Handler.Deeplink == Deeplink {
public init(mapper: Mapper? = nil, handler: Handler? = nil, operationQueue: OperationQueue = .main) {
self.deeplinkMapper = mapper?.eraseToAnyDeeplinkMapper()
self.deeplinkHandler = handler?.eraseToAnyDeeplinkHandler()
self.operationQueue = operationQueue

View File

@ -5,21 +5,16 @@
## Как настроить
1. Создать представителей класса `Deeplink`
1. Создать объект, представляющий диплинк. Такой объект должен соответствовать протоколу `Hashable`
```swift
import Foundation
import TIDeeplink
import UIKit
extension Deeplink {
static var editProfile: Deeplink {
Deeplink(rawValue: "editProfile")
}
static var office: Deeplink {
Deeplink(rawValue: "office")
}
enum ProjectDeeplink: Hashable {
case editProfile
case office(id: String)
}
```
@ -27,16 +22,25 @@ extension Deeplink {
```swift
final class ProjectDeeplinkMapper: DeeplinkMapper {
private let availableDeeplinks: [Deeplink] = [.office, .editProfile]
func map(url: URL) -> ProjectDeeplink? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
func map(url: URL) -> Deeplink? {
let stringData = parse(url)
switch components.host {
case "office":
if let id = components.path.split(separator: "/").last {
return .office(id: String(id))
}
return availableDeeplinks.find(byRawValue: stringData)
}
return nil
private func parse(_ url: URL) -> String {
url.host ?? ""
case "editProfile":
return .editProfile
default:
return nil
}
}
}
```
@ -49,26 +53,24 @@ class MainCoordinator: DeeplinkHandler {
print("Presenting edit profile view controller")
}
func openOfficesScreen() {
print("Presenting offices view controller")
func openOfficesScreen(forId id: String) {
print("Presenting offices view controller by id: \(id)")
}
// MARK: DeeplinkHandler
func handle(deeplink: Deeplink) -> Operation? {
if deeplink == .editProfile {
func handle(deeplink: ProjectDeeplink) -> Operation? {
switch deeplink {
case .editProfile:
return BlockOperation { [weak self] in
self?.openEditProfileScreen()
}
}
if deeplink == .office {
case let .office(id):
return BlockOperation { [weak self] in
self?.openOfficesScreen()
self?.openOfficesScreen(forId: id)
}
}
return nil
}
}
```
@ -76,20 +78,20 @@ class MainCoordinator: DeeplinkHandler {
**Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту
```swift
final class ProjectDeeplinksService: TIDeeplinksService {
static let shared = ProjectDeeplinksService()
final class ProjectDeeplinksService: TIDeeplinksService<ProjectDeeplink, MainCoordinator, ProjectDeeplinkMapper> {
static let shared = ProjectDeeplinksService()
}
```
Создаем и передаем в сервис Mapper и Handler
Создаем и передаем в сервис Mapper и Handler.
> Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`
> Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`. Если Mapper и Handler передаются не в инициализаторе, то их самостоятельно необходимо привести к виду `AnyDeeplinkHandler` и `AnyDeeplinkMapper` с момощью методов: `eraseToAnyDeeplinkHandler()` и `eraseToAnyDeeplinkMapper()`
```swift
let coordinator = MainCoordinator()
let mapper = ProjectDeeplinkMapper()
ProjectDeeplinksService.shared.deeplinkMapper = mapper
ProjectDeeplinksService.shared.deeplinkHandler = coordinator
ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper()
ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler()
```
В `AppDelegate` использвуем методы `deferredHandle(url:)` и `handlePendingDeeplinks()` для обработки диплинков.
@ -105,50 +107,48 @@ func application(_ app: UIApplication, open url: URL, options: [UIApplication.Op
return true
}
guard let url = URL(string: "app://office/") else {
guard let url = URL(string: "app://office/123") else {
fatalError()
}
application(.shared, open: url)
```
В качестве `DeeplinkHandler` можно использовать базовую реализацию `BaseNavigationStackDeeplinkHandler`, которая позволяет искать handler в иерархии view, способный обработать диплинк
В качестве `DeeplinkHandler` можно использовать базовую реализацию `BaseNavigationStackDeeplinkHandler`, которая позволяет искать handler в иерархии view, способный обработать диплинк.
```swift
class NavigationController: UINavigationController, DeeplinkHandler {
func handle(deeplink: Deeplink) -> Foundation.Operation? {
nil
}
protocol ProjectDeeplinkHandler: DeeplinkHandler where Deeplink == ProjectDeeplink {
}
class ViewController: UIViewController, DeeplinkHandler {
func handle(deeplink: Deeplink) -> Operation? {
if deeplink == .editProfile {
class ViewController: UIViewController, ProjectDeeplinkHandler {
func handle(deeplink: ProjectDeeplink) -> Operation? {
switch deeplink {
case .editProfile:
return BlockOperation {
print("Presenting edit profile view controller")
}
}
if deeplink == .office {
case let .office(id):
return BlockOperation {
print("Presenting offices view controller")
print("Presenting offices view controller by id: \(id)")
}
}
return nil
}
}
```
Создание Handler. Для настройки передается rootViewController, который должен наследоваться от `UIViewController` и соответствовать протоколу `DeeplinkHandler`. С этого контроллера будет начинаться поиск обработчика
Создание Handler. Для настройки передается rootViewController, который должен наследоваться от `UIViewController` С этого контроллера будет начинаться поиск обработчика
```swift
typealias ProjectNavigationStackDeeplinkHandler = BaseNavigationStackDeeplinkHandler<NavigationController>
let viewController = ViewController()
let navigationController = NavigationController(rootViewController: viewController)
let handler = ProjectNavigationStackDeeplinkHandler(rootViewControllerKeeper: navigationController)
let navigationController = UINavigationController(rootViewController: viewController)
let handler = BaseNavigationStackDeeplinkHandler<ProjectDeeplink>(rootViewController: navigationController) {
guard let projectHandler = $0 as? (any ProjectDeeplinkHandler) else {
return nil
}
return projectHandler.eraseToAnyDeeplinkHandler()
}
```
Далее handler может передаваться для использования в `TIDeeplinksService`