181 lines
6.4 KiB
Markdown
181 lines
6.4 KiB
Markdown
|
||
# Deeplink API
|
||
|
||
_TIDeeplink_ добавляет сервис `TIDeeplinksService` для обработки диплинков
|
||
|
||
## Как настроить
|
||
|
||
1. Создать объект, представляющий диплинк. Такой объект должен соответствовать протоколу `Hashable`
|
||
|
||
```swift
|
||
import Foundation
|
||
import TIDeeplink
|
||
import TIFoundationUtils
|
||
import TISwiftUtils
|
||
import UIKit
|
||
|
||
enum ProjectDeeplink: Hashable {
|
||
case editProfile
|
||
case office(id: String)
|
||
}
|
||
```
|
||
|
||
2. Создать объект, соответствующий протоколу `DeeplinkMapper`. Его задачей будет - преобразование _URL_ в диплинк
|
||
|
||
```swift
|
||
final class ProjectDeeplinkMapper: DeeplinkMapper {
|
||
func map(url: URL) -> ProjectDeeplink? {
|
||
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
|
||
return nil
|
||
}
|
||
|
||
switch components.host {
|
||
case "office":
|
||
if let id = components.path.split(separator: "/").last {
|
||
return .office(id: String(id))
|
||
}
|
||
|
||
return nil
|
||
|
||
case "editProfile":
|
||
return .editProfile
|
||
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
3. Реализовать у объекта отвечающего за навигацию протокол`DeeplinkHandler`. В необходимом для реализации методе `handle(deeplink:)` нужно обработать передаваемый диплинк и вернуть соответствующее действие
|
||
|
||
```swift
|
||
class MainCoordinator: DeeplinkHandler {
|
||
func openEditProfileScreen() {
|
||
print("Presenting edit profile view controller")
|
||
}
|
||
|
||
func openOfficesScreen(completion: VoidClosure?) {
|
||
print("Presenting offices view controller")
|
||
completion?()
|
||
}
|
||
|
||
func openOfficesScreen(forId id: String) {
|
||
print("Presenting offices view controller by id: \(id)")
|
||
}
|
||
|
||
// MARK: DeeplinkHandler
|
||
|
||
func handle(deeplink: ProjectDeeplink) -> Operation? {
|
||
switch deeplink {
|
||
case .editProfile:
|
||
return BlockOperation { [weak self] in
|
||
self?.openEditProfileScreen()
|
||
}
|
||
|
||
case let .office(id):
|
||
return ClosureAsyncOperation<Void, Never> { [weak self] completion in
|
||
self?.openOfficesScreen {
|
||
self?.openOfficesScreen(forId: id)
|
||
completion(.success(()))
|
||
}
|
||
|
||
return Cancellables.nonCancellable()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту
|
||
|
||
```swift
|
||
final class ProjectDeeplinksService: TIDeeplinksService<ProjectDeeplink> {
|
||
static let shared = ProjectDeeplinksService()
|
||
|
||
convenience init(mapper: ProjectDeeplinkMapper = .init(), handler: MainCoordinator = .init()) {
|
||
self.init(mapper: mapper, handler: handler, operationQueue: .main)
|
||
}
|
||
}
|
||
```
|
||
|
||
Создаем и передаем в сервис Mapper и Handler.
|
||
|
||
> Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`. Если Mapper и Handler передаются не в инициализаторе, то их самостоятельно необходимо привести к виду `AnyDeeplinkHandler` и `AnyDeeplinkMapper` с момощью методов: `eraseToAnyDeeplinkHandler()` и `eraseToAnyDeeplinkMapper()`
|
||
|
||
```swift
|
||
let coordinator = MainCoordinator()
|
||
let mapper = ProjectDeeplinkMapper()
|
||
ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper()
|
||
ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler()
|
||
```
|
||
|
||
В `AppDelegate` использвуем методы `deferredHandle(url:)` и `handlePendingDeeplinks()` для обработки диплинков.
|
||
|
||
- `deferredHandle(url:)`: пытается создать из _URL_ диплинк и добавить его в очередь на обработку
|
||
- `handlePendingDeeplinks()`: обрабатывает первый в череди диплинк
|
||
|
||
```swift
|
||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||
ProjectDeeplinksService.shared.deferredHandle(url: url)
|
||
ProjectDeeplinksService.shared.handlePendingDeeplinks()
|
||
|
||
return true
|
||
}
|
||
|
||
guard let url = URL(string: "app://office/123") else {
|
||
fatalError()
|
||
}
|
||
|
||
application(.shared, open: url)
|
||
```
|
||
|
||
В качестве `DeeplinkHandler` можно использовать базовую реализацию `BaseNavigationStackDeeplinkHandler`, которая позволяет искать handler в иерархии view, способный обработать диплинк.
|
||
|
||
```swift
|
||
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")
|
||
}
|
||
|
||
case let .office(id):
|
||
return BlockOperation {
|
||
print("Presenting offices view controller by id: \(id)")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Создание Handler. Для настройки передается rootViewController, который должен наследоваться от `UIViewController` С этого контроллера будет начинаться поиск обработчика
|
||
|
||
```swift
|
||
let viewController = ViewController()
|
||
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`
|
||
|
||
```swift
|
||
let mapper = ProjectDeeplinkMapper()
|
||
let service = TIDeeplinksService(mapper: mapper, handler: handler)
|
||
|
||
service.deferredHandle(url: url)
|
||
service.handlePendingDeeplinks()
|
||
|
||
RunLoop.main.run()
|
||
```
|