feat: integrate new base navigation deeplink handler
This commit is contained in:
parent
c442ee2623
commit
af044aa591
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
Loading…
Reference in New Issue