diff --git a/CHANGELOG.md b/CHANGELOG.md index df1d5e35..f48c6887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 1.42.0 + +- **Added**: TIDeeplink to support deeplink API + ### 1.41.0 - **Update**: added callbacks for views while skeletons change status to presented or hidden diff --git a/Package.swift b/Package.swift index 642b75ae..0d7f8b56 100644 --- a/Package.swift +++ b/Package.swift @@ -15,12 +15,13 @@ let package = Package( // MARK: - SwiftUI .library(name: "TISwiftUICore", targets: ["TISwiftUICore"]), - + // MARK: - Utils .library(name: "TISwiftUtils", targets: ["TISwiftUtils"]), .library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]), .library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]), .library(name: "TITableKitUtils", targets: ["TITableKitUtils"]), + .library(name: "TIDeeplink", targets: ["TIDeeplink"]), .library(name: "TIDeveloperUtils", targets: ["TIDeveloperUtils"]), // MARK: - Networking @@ -33,13 +34,13 @@ let package = Package( .library(name: "TIMapUtils", targets: ["TIMapUtils"]), .library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]), - + // MARK: - Elements .library(name: "OTPSwiftView", targets: ["OTPSwiftView"]), .library(name: "TITransitions", targets: ["TITransitions"]), .library(name: "TIPagination", targets: ["TIPagination"]), .library(name: "TIAuth", targets: ["TIAuth"]), - + //MARK: - Skolkovo .library(name: "TIEcommerce", targets: ["TIEcommerce"]) ], @@ -52,7 +53,7 @@ let package = Package( .package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0")) ], targets: [ - + // MARK: - UIKit .target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"), .target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"), @@ -60,12 +61,13 @@ let package = Package( // MARK: - SwiftUI .target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"), - + // MARK: - Utils .target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"), .target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils", exclude: ["TIFoundationUtils.app"]), .target(name: "TIKeychainUtils", dependencies: ["TIFoundationUtils", "KeychainAccess"], path: "TIKeychainUtils/Sources"), .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), + .target(name: "TIDeeplink", dependencies: ["TIFoundationUtils"], path: "TIDeeplink", exclude: ["TIDeeplink.app"]), .target(name: "TIDeveloperUtils", dependencies: ["TISwiftUtils", "TIUIKitCore", "TIUIElements"], path: "TIDeveloperUtils/Sources"), // MARK: - Networking @@ -76,16 +78,16 @@ let package = Package( // MARK: - Maps .target(name: "TIMapUtils", dependencies: [], path: "TIMapUtils/Sources"), .target(name: "TIAppleMapUtils", dependencies: ["TIMapUtils"], path: "TIAppleMapUtils/Sources"), - + // MARK: - Elements .target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"), .target(name: "TITransitions", path: "TITransitions/Sources"), .target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"), .target(name: "TIAuth", dependencies: ["TIFoundationUtils", "TIUIKitCore", "KeychainAccess"], path: "TIAuth/Sources"), .target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"), - + // MARK: - Tests - + .testTarget( name: "TITimerTests", dependencies: ["TIFoundationUtils"], diff --git a/README.md b/README.md index 78bbc14b..30237366 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,20 @@ This repository contains the following frameworks: ### Create new Playground ```sh -cd TIModuleName -nef playground --name TIModuleName --cocoapods --custom-podfile PlaygroundPodfile +$ cd TIModuleName + +$ touch PlaygroundPodfile + +$ echo "ENV[\"DEVELOPMENT_INSTALL\"] = \"true\" + +target 'TIModuleName' do + platform :ios, IOS_VERSION_NUMBER + use_frameworks! + + pod 'TIDependencyModuleName', :path => '../../../../TIDependencyModuleName/TIDependencyModuleName.podspec' +end" > PlaygroundPodfile + +$ nef playground --name TIModuleName --cocoapods --custom-podfile PlaygroundPodfile ``` See example of `PlaygroundPodfile` in `TIFoundationUtils` @@ -38,8 +50,8 @@ For every new feature in module create new Playground page with documentation in ### Create symlink to nef playground ```sh -cd TIModuleName -ln -s TIModuleName.app/Contents/MacOS/TIModuleName.playground TIModuleName.playground +$ cd TIModuleName +$ ln -s TIModuleName.app/Contents/MacOS/TIModuleName.playground TIModuleName.playground ``` ### Add nef files to TIModuleName.app/.gitignore @@ -51,15 +63,6 @@ ln -s TIModuleName.app/Contents/MacOS/TIModuleName.playground TIModuleName.playg LICENSE ``` -### Add new playground to pre release script - -`project-scripts/gen_docs_from_playgrounds.sh`: - -```sh -PLAYGROUNDS="${SRCROOT}/TIFoundationUtils/TIFoundationUtils.app -${SRCROOT}/TIModuleName/TIModuleName.app" -``` - ### Exclude .app bundles from package sources #### SPM @@ -85,6 +88,10 @@ ${SRCROOT}/TIModuleName/TIModuleName.app" - [TIFoundationUtils](docs/tifoundationutils) * [AsyncOperation](docs/tifoundationutils/asyncoperation.md) +- [TIUIElements](docs/tiuielements) + * [Skeletons](docs/tiuielements/skeletons.md) + * [Placeholders](docs/tiuielements/placeholder.md) +- [TIDeeplink](docs/tideeplink/deeplinks.md) - [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle. - [Snippets](docs/snippets.md) - useful commands and scripts for development. diff --git a/TIAppleMapUtils/TIAppleMapUtils.podspec b/TIAppleMapUtils/TIAppleMapUtils.podspec index 62abf991..b09aa33c 100644 --- a/TIAppleMapUtils/TIAppleMapUtils.podspec +++ b/TIAppleMapUtils/TIAppleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAppleMapUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIAuth/TIAuth.podspec b/TIAuth/TIAuth.podspec index a5cab593..9af515b0 100644 --- a/TIAuth/TIAuth.podspec +++ b/TIAuth/TIAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAuth' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Login, registration, confirmation and other related actions' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIDeepLink/PlaygroundPodfile b/TIDeepLink/PlaygroundPodfile new file mode 100644 index 00000000..94c744c3 --- /dev/null +++ b/TIDeepLink/PlaygroundPodfile @@ -0,0 +1,9 @@ +ENV["DEVELOPMENT_INSTALL"] = "true" + +target 'TIDeeplink' do + platform :ios, 11 + use_frameworks! + + pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec' + pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec' +end diff --git a/TIDeepLink/Sources/DeeplinkHandler/AnyDeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/AnyDeeplinkHandler.swift new file mode 100644 index 00000000..0a81d2b0 --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkHandler/AnyDeeplinkHandler.swift @@ -0,0 +1,42 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public struct AnyDeeplinkHandler: DeeplinkHandler { + private let handlerClosure: Closure + + public init(handler: Handler) where Handler.Deeplink == Deeplink { + self.handlerClosure = handler.handle + } + + public func handle(deeplink: Deeplink) -> Operation? { + handlerClosure(deeplink) + } +} + +public extension DeeplinkHandler { + func eraseToAnyDeeplinkHandler() -> AnyDeeplinkHandler { + AnyDeeplinkHandler(handler: self) + } +} diff --git a/TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift new file mode 100644 index 00000000..97dee0de --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift @@ -0,0 +1,94 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils +import UIKit + +open class BaseNavigationStackDeeplinkHandler: DeeplinkHandler { + + public var rootViewController: UIViewController + public var asAnyHandlerClosure: Closure?> + + public init(rootViewController: UIViewController, + asAnyHandlerClosure: @escaping Closure?>) { + + self.rootViewController = rootViewController + self.asAnyHandlerClosure = asAnyHandlerClosure + } + + // MARK: - DeeplinkHandler + + open func handle(deeplink: Deeplink) -> Operation? { + if let handler = asAnyHandlerClosure(rootViewController), + let operation = handler.handle(deeplink: deeplink) { + return operation + } + + let (_, operation) = findHandler(for: deeplink, in: rootViewController) + + return operation + } + + // MARK: - Open methods + + open func findHandler(for deeplink: Deeplink, + in viewController: UIViewController) -> (AnyDeeplinkHandler?, Operation?) { + + switch viewController { + case let navController as UINavigationController: + for controllerInNavigationStack in navController.viewControllers.reversed() { + if let handler = asAnyHandlerClosure(controllerInNavigationStack), + let operation = handler.handle(deeplink: deeplink) { + return (handler, operation) + } + } + + case let tabController as UITabBarController: + if let selectedController = tabController.selectedViewController, + let handler = asAnyHandlerClosure(selectedController), + let operation = handler.handle(deeplink: deeplink) { + return (handler, operation) + + } else if var tabControllers = tabController.viewControllers { + if tabController.selectedIndex != NSNotFound { + tabControllers.remove(at: tabController.selectedIndex) + } + + for tabController in tabControllers { + if let handler = asAnyHandlerClosure(tabController), + let operation = handler.handle(deeplink: deeplink) { + return (handler, operation) + } + } + } + + default: + let handler = asAnyHandlerClosure(viewController) + let operation = handler?.handle(deeplink: deeplink) + + return (handler, operation) + } + + return (nil, nil) + } +} diff --git a/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift new file mode 100644 index 00000000..d167dc1b --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift @@ -0,0 +1,30 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public protocol DeeplinkHandler { + associatedtype Deeplink + + func handle(deeplink: Deeplink) -> Operation? +} diff --git a/TIDeepLink/TIDeeplink.app/.gitignore b/TIDeepLink/TIDeeplink.app/.gitignore new file mode 100644 index 00000000..a85c5414 --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/.gitignore @@ -0,0 +1,5 @@ +# gitignore nef files +**/build/ +**/nef/ +LICENSE + diff --git a/TIDeepLink/TIDeeplink.app/Contents/Info.plist b/TIDeepLink/TIDeeplink.app/Contents/Info.plist new file mode 100644 index 00000000..831ea97a --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + launcher + CFBundleIconFile + AppIcon + CFBundleIconName + AppIcon + CFBundleIdentifier + com.fortysevendeg.nef + CFBundleInfoDictionaryVersion + 6.0 + CFBundleSupportedPlatforms + + MacOSX + + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + 10.14 + NSHumanReadableCopyright + Copyright © 2019 The nef Authors. All rights reserved. + + diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/.gitignore b/TIDeepLink/TIDeeplink.app/Contents/MacOS/.gitignore new file mode 100644 index 00000000..18bd1f3b --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/.gitignore @@ -0,0 +1,26 @@ +## gitignore nef files +**/build/ +**/nef/ +LICENSE + +## User data +**/xcuserdata/ +podfile.lock +**.DS_Store + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## CocoaPods +**Pods** + +## Carthage +**Carthage** + +## SPM +.build +.swiftpm +swiftpm diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/Podfile b/TIDeepLink/TIDeeplink.app/Contents/MacOS/Podfile new file mode 100644 index 00000000..94c744c3 --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/Podfile @@ -0,0 +1,9 @@ +ENV["DEVELOPMENT_INSTALL"] = "true" + +target 'TIDeeplink' do + platform :ios, 11 + use_frameworks! + + pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec' + pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec' +end diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Pages/Deeplinks.xcplaygroundpage/Contents.swift b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Pages/Deeplinks.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..87cc2bcf --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Pages/Deeplinks.xcplaygroundpage/Contents.swift @@ -0,0 +1,164 @@ +/*: + # Deeplink API + + _TIDeeplink_ добавляет сервис `TIDeeplinksService` для обработки диплинков + + ## Как настроить + + 1. Создать объект, представляющий диплинк. Такой объект должен соответствовать протоколу `Hashable` + */ +import Foundation +import TIDeeplink +import TIFoundationUtils +import TISwiftUtils +import UIKit + +enum ProjectDeeplink: Hashable { + case editProfile + case office(id: String) +} + +//: 2. Создать объект, соответствующий протоколу `DeeplinkMapper`. Его задачей будет - преобразование _URL_ в диплинк +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:)` нужно обработать передаваемый диплинк и вернуть соответствующее действие +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 { [weak self] completion in + self?.openOfficesScreen { + self?.openOfficesScreen(forId: id) + completion(.success(())) + } + + return Cancellables.nonCancellable() + } + } + } +} + +/*: + **Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту + */ +final class ProjectDeeplinksService: TIDeeplinksService { + 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()`: обрабатывает первый в череди диплинк + */ +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, способный обработать диплинк. + */ + +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` С этого контроллера будет начинаться поиск обработчика +let viewController = ViewController() +let navigationController = UINavigationController(rootViewController: viewController) +let handler = BaseNavigationStackDeeplinkHandler(rootViewController: navigationController) { + guard let projectHandler = $0 as? (any ProjectDeeplinkHandler) else { + return nil + } + + return projectHandler.eraseToAnyDeeplinkHandler() +} + +//: Далее handler может передаваться для использования в `TIDeeplinksService` +let mapper = ProjectDeeplinkMapper() +let service = TIDeeplinksService(mapper: mapper, handler: handler) + +service.deferredHandle(url: url) +service.handlePendingDeeplinks() + +RunLoop.main.run() diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Sources/NefPlaygroundSupport.swift b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Sources/NefPlaygroundSupport.swift new file mode 100644 index 00000000..2ffea80a --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Sources/NefPlaygroundSupport.swift @@ -0,0 +1,30 @@ +import UIKit + +public protocol NefPlaygroundLiveViewable {} +extension UIView: NefPlaygroundLiveViewable {} +extension UIViewController: NefPlaygroundLiveViewable {} + +#if NOT_IN_PLAYGROUND +public enum Nef { + public enum Playground { + public static func liveView(_ view: NefPlaygroundLiveViewable) {} + public static func needsIndefiniteExecution(_ state: Bool) {} + } +} + +#else +import PlaygroundSupport + +public enum Nef { + public enum Playground { + public static func liveView(_ view: NefPlaygroundLiveViewable) { + PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable) + } + + public static func needsIndefiniteExecution(_ state: Bool) { + PlaygroundPage.current.needsIndefiniteExecution = state + } + } +} + +#endif diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/contents.xcplayground b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/contents.xcplayground new file mode 100644 index 00000000..3debe4b3 --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/project.pbxproj b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/project.pbxproj new file mode 100644 index 00000000..d642d3e0 --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/project.pbxproj @@ -0,0 +1,396 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 2FC760DADC7ABCE05229EAD6 /* Pods_TIDeeplink.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EC1315C38BF91446DB6FCA4 /* Pods_TIDeeplink.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0EC1315C38BF91446DB6FCA4 /* Pods_TIDeeplink.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIDeeplink.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BA4C0D578FC5BA8798475E6 /* Pods-TIDeeplink.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIDeeplink.release.xcconfig"; path = "Target Support Files/Pods-TIDeeplink/Pods-TIDeeplink.release.xcconfig"; sourceTree = ""; }; + 8BACBE8322576CAD00266845 /* TIDeeplink.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIDeeplink.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A2898C64EB444BA1068CC72C /* Pods-TIDeeplink.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIDeeplink.debug.xcconfig"; path = "Target Support Files/Pods-TIDeeplink/Pods-TIDeeplink.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8BACBE8022576CAD00266845 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2FC760DADC7ABCE05229EAD6 /* Pods_TIDeeplink.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 154F2E18EA615FB457290B92 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0EC1315C38BF91446DB6FCA4 /* Pods_TIDeeplink.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8B39A26221D40F8700DE2643 = { + isa = PBXGroup; + children = ( + 8BACBE8422576CAD00266845 /* TIDeeplink */, + 8B39A26C21D40F8700DE2643 /* Products */, + CB5B9E4DF89ADAD1D8849E37 /* Pods */, + 154F2E18EA615FB457290B92 /* Frameworks */, + ); + sourceTree = ""; + }; + 8B39A26C21D40F8700DE2643 /* Products */ = { + isa = PBXGroup; + children = ( + 8BACBE8322576CAD00266845 /* TIDeeplink.framework */, + ); + name = Products; + sourceTree = ""; + }; + 8BACBE8422576CAD00266845 /* TIDeeplink */ = { + isa = PBXGroup; + children = ( + 8BACBE8622576CAD00266845 /* Info.plist */, + ); + path = TIDeeplink; + sourceTree = ""; + }; + CB5B9E4DF89ADAD1D8849E37 /* Pods */ = { + isa = PBXGroup; + children = ( + A2898C64EB444BA1068CC72C /* Pods-TIDeeplink.debug.xcconfig */, + 6BA4C0D578FC5BA8798475E6 /* Pods-TIDeeplink.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8BACBE7E22576CAD00266845 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 8BACBE8222576CAD00266845 /* TIDeeplink */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIDeeplink" */; + buildPhases = ( + B00BE0F551706E267B42014F /* [CP] Check Pods Manifest.lock */, + 8BACBE7E22576CAD00266845 /* Headers */, + 8BACBE7F22576CAD00266845 /* Sources */, + 8BACBE8022576CAD00266845 /* Frameworks */, + 8BACBE8122576CAD00266845 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TIDeeplink; + productName = TIDeeplink2; + productReference = 8BACBE8322576CAD00266845 /* TIDeeplink.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8B39A26321D40F8700DE2643 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "47 Degrees"; + TargetAttributes = { + 8BACBE8222576CAD00266845 = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIDeeplink" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8B39A26221D40F8700DE2643; + productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8BACBE8222576CAD00266845 /* TIDeeplink */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8BACBE8122576CAD00266845 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + B00BE0F551706E267B42014F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TIDeeplink-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8BACBE7F22576CAD00266845 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 8B39A27721D40F8800DE2643 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_TESTING_SEARCH_PATHS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8B39A27821D40F8800DE2643 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTING_SEARCH_PATHS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 8BACBE8822576CAD00266845 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A2898C64EB444BA1068CC72C /* Pods-TIDeeplink.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + CURRENT_TIDeeplink_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/TIDeeplink/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIDeeplink; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 8BACBE8922576CAD00266845 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BA4C0D578FC5BA8798475E6 /* Pods-TIDeeplink.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + CURRENT_TIDeeplink_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/TIDeeplink/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIDeeplink; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIDeeplink" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8B39A27721D40F8800DE2643 /* Debug */, + 8B39A27821D40F8800DE2643 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIDeeplink" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8BACBE8822576CAD00266845 /* Debug */, + 8BACBE8922576CAD00266845 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8B39A26321D40F8700DE2643 /* Project object */; +} diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1c8d0afa --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/xcshareddata/xcschemes/TIDeeplink.xcscheme b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/xcshareddata/xcschemes/TIDeeplink.xcscheme new file mode 100644 index 00000000..8df2741a --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcodeproj/xcshareddata/xcschemes/TIDeeplink.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcworkspace/contents.xcworkspacedata b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..fde06efd --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink/Info.plist b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink/Info.plist new file mode 100644 index 00000000..98d14f60 --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + NSHumanReadableCopyright + Copyright © 2019. The nef authors. + + diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/launcher b/TIDeepLink/TIDeeplink.app/Contents/MacOS/launcher new file mode 100755 index 00000000..13bd83dd --- /dev/null +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/launcher @@ -0,0 +1,6 @@ +#!/bin/bash + +workspace="TIDeeplink.xcworkspace" +workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev) + +open "`pwd`/$workspacePath/$workspace" diff --git a/TIDeepLink/TIDeeplink.app/Contents/Resources/AppIcon.icns b/TIDeepLink/TIDeeplink.app/Contents/Resources/AppIcon.icns new file mode 100644 index 00000000..32814f1c Binary files /dev/null and b/TIDeepLink/TIDeeplink.app/Contents/Resources/AppIcon.icns differ diff --git a/TIDeepLink/TIDeeplink.app/Contents/Resources/Assets.car b/TIDeepLink/TIDeeplink.app/Contents/Resources/Assets.car new file mode 100644 index 00000000..79d9ea89 Binary files /dev/null and b/TIDeepLink/TIDeeplink.app/Contents/Resources/Assets.car differ diff --git a/TIDeepLink/TIDeeplink.playground b/TIDeepLink/TIDeeplink.playground new file mode 120000 index 00000000..c54b143d --- /dev/null +++ b/TIDeepLink/TIDeeplink.playground @@ -0,0 +1 @@ +TIDeeplink.app/Contents/MacOS/TIDeeplink.playground \ No newline at end of file diff --git a/TIDeeplink/Sources/DeeplinkMapper/AnyDeeplinkMapper.swift b/TIDeeplink/Sources/DeeplinkMapper/AnyDeeplinkMapper.swift new file mode 100644 index 00000000..1898fc28 --- /dev/null +++ b/TIDeeplink/Sources/DeeplinkMapper/AnyDeeplinkMapper.swift @@ -0,0 +1,42 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public struct AnyDeeplinkMapper: DeeplinkMapper { + private let mappingClosure: Closure + + public init(mapper: Mapper) where Mapper.Deeplink == Deeplink { + self.mappingClosure = mapper.map + } + + public func map(url: URL) -> Deeplink? { + mappingClosure(url) + } +} + +public extension DeeplinkMapper { + func eraseToAnyDeeplinkMapper() -> AnyDeeplinkMapper { + AnyDeeplinkMapper(mapper: self) + } +} diff --git a/TIDeeplink/Sources/DeeplinkMapper/DeeplinkMapper.swift b/TIDeeplink/Sources/DeeplinkMapper/DeeplinkMapper.swift new file mode 100644 index 00000000..a9c5f1c6 --- /dev/null +++ b/TIDeeplink/Sources/DeeplinkMapper/DeeplinkMapper.swift @@ -0,0 +1,30 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public protocol DeeplinkMapper { + associatedtype Deeplink + + func map(url: URL) -> Deeplink? +} diff --git a/TIDeeplink/Sources/TIDeeplinkService.swift b/TIDeeplink/Sources/TIDeeplinkService.swift new file mode 100644 index 00000000..a0b84dcf --- /dev/null +++ b/TIDeeplink/Sources/TIDeeplinkService.swift @@ -0,0 +1,93 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TIFoundationUtils + +open class TIDeeplinksService { + + // MARK: - Private properties + + private var operationQueue: OperationQueue + + private var deeplinkQueue: [Deeplink] = [] + private var inProcessingSet: Set = [] + + // MARK: - Public properties + + public var deeplinkMapper: AnyDeeplinkMapper? + public var deeplinkHandler: AnyDeeplinkHandler? + + // MARK: - Init + + public init( + mapper: Mapper? = nil, + handler: Handler? = nil, + operationQueue: OperationQueue = .main) where Mapper.Deeplink == Deeplink, Handler.Deeplink == Deeplink { + + self.deeplinkMapper = mapper?.eraseToAnyDeeplinkMapper() + self.deeplinkHandler = handler?.eraseToAnyDeeplinkHandler() + self.operationQueue = operationQueue + } + + // MARK: - Open methods + + /// Mapping the given url to deeplink model and appending it to the queue. + /// - Parameter url: URL to map into deeplink model + open func deferredHandle(url: URL) { + guard let deeplink = deeplinkMapper?.map(url: url) else { + return + } + + deeplinkQueue.append(deeplink) + } + + open func reset() { + operationQueue.cancelAllOperations() + deeplinkQueue.removeAll() + inProcessingSet.removeAll() + } + + open func handlePendingDeeplinks() { + guard let deeplink = deeplinkQueue.first, + let handler = deeplinkHandler else { + return + } + + deeplinkQueue.remove(at: .zero) + + guard !inProcessingSet.contains(deeplink), + let operation = handler.handle(deeplink: deeplink) else { + return + } + + inProcessingSet.formUnion([deeplink]) + + let completionOperation = BlockOperation { [weak self] in + self?.inProcessingSet.subtract([deeplink]) + } + + completionOperation.addDependency(operation) + + completionOperation.add(to: operationQueue) + } +} diff --git a/TIDeeplink/TIDeeplink.podspec b/TIDeeplink/TIDeeplink.podspec new file mode 100644 index 00000000..d22fe7b5 --- /dev/null +++ b/TIDeeplink/TIDeeplink.podspec @@ -0,0 +1,25 @@ +Pod::Spec.new do |s| + s.name = 'TIDeeplink' + s.version = '1.42.0' + s.summary = 'Deeplink service API' + s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', + 'castlele' => 'nikita.semenov@touchin.ru' } + s.source = { :git => 'https://gitlab.ti/touchinstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.3'] + + sources = '/Sources/**/*' + if ENV["DEVELOPMENT_INSTALL"] # installing using :path => + s.source_files = sources + s.exclude_files = s.name + '.app' + else + s.source_files = s.name + sources + s.exclude_files = s.name + '/*.app' + end + + s.dependency 'TIFoundationUtils', s.version.to_s + +end diff --git a/TIDeveloperUtils/TIDeveloperUtils.podspec b/TIDeveloperUtils/TIDeveloperUtils.podspec index 693d6107..211f0c91 100644 --- a/TIDeveloperUtils/TIDeveloperUtils.podspec +++ b/TIDeveloperUtils/TIDeveloperUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIDeveloperUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Universal web view API' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIEcommerce/TIEcommerce.podspec b/TIEcommerce/TIEcommerce.podspec index 9a3116d7..0acab9b1 100644 --- a/TIEcommerce/TIEcommerce.podspec +++ b/TIEcommerce/TIEcommerce.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIEcommerce' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Cart, products, promocodes, bonuses and other related actions' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/contents.xcplayground b/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/contents.xcplayground index 00daa653..3debe4b3 100644 --- a/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/contents.xcplayground +++ b/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/contents.xcplayground @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index 730bfb71..18c9a0ba 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Set of helpers for Foundation framework classes.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIGoogleMapUtils/TIGoogleMapUtils.podspec b/TIGoogleMapUtils/TIGoogleMapUtils.podspec index 2f0358a8..682d0fa7 100644 --- a/TIGoogleMapUtils/TIGoogleMapUtils.podspec +++ b/TIGoogleMapUtils/TIGoogleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIGoogleMapUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIKeychainUtils/TIKeychainUtils.podspec b/TIKeychainUtils/TIKeychainUtils.podspec index 8be956b2..b9ede117 100644 --- a/TIKeychainUtils/TIKeychainUtils.podspec +++ b/TIKeychainUtils/TIKeychainUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIKeychainUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Set of helpers for Keychain classes.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIMapUtils/TIMapUtils.podspec b/TIMapUtils/TIMapUtils.podspec index f8aa2d12..6b243fec 100644 --- a/TIMapUtils/TIMapUtils.podspec +++ b/TIMapUtils/TIMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMapUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Set of helpers for map objects clustering and interacting.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIMoyaNetworking/TIMoyaNetworking.podspec b/TIMoyaNetworking/TIMoyaNetworking.podspec index 48989acc..2816bbf5 100644 --- a/TIMoyaNetworking/TIMoyaNetworking.podspec +++ b/TIMoyaNetworking/TIMoyaNetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMoyaNetworking' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Moya + Swagger network service.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TINetworking/TINetworking.podspec b/TINetworking/TINetworking.podspec index 99189ba1..d3cb51e4 100644 --- a/TINetworking/TINetworking.podspec +++ b/TINetworking/TINetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworking' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Swagger-frendly networking layer helpers.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TINetworkingCache/TINetworkingCache.podspec b/TINetworkingCache/TINetworkingCache.podspec index 0c4ef4e8..7c6f108d 100644 --- a/TINetworkingCache/TINetworkingCache.podspec +++ b/TINetworkingCache/TINetworkingCache.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworkingCache' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Caching results of EndpointRequests.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIPagination/TIPagination.podspec b/TIPagination/TIPagination.podspec index 8ff4e932..29cf019b 100644 --- a/TIPagination/TIPagination.podspec +++ b/TIPagination/TIPagination.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIPagination' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Generic pagination component.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TISwiftUICore/TISwiftUICore.podspec b/TISwiftUICore/TISwiftUICore.podspec index 4bfbb7db..cc9aa89a 100644 --- a/TISwiftUICore/TISwiftUICore.podspec +++ b/TISwiftUICore/TISwiftUICore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUICore' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index 374c9211..b84ff47f 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Bunch of useful helpers for Swift development.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index 2feec8b2..d0b561e4 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Set of helpers for TableKit classes.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index 9b841ec8..5a2ebe0e 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Bunch of useful protocols and views.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index 14b82282..6f43e351 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIWebView/TIWebView.podspec b/TIWebView/TIWebView.podspec index 8b9c3739..af561a0a 100644 --- a/TIWebView/TIWebView.podspec +++ b/TIWebView/TIWebView.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIWebView' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Universal web view API' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIYandexMapUtils/TIYandexMapUtils.podspec b/TIYandexMapUtils/TIYandexMapUtils.podspec index 18721a70..c6d6ee72 100644 --- a/TIYandexMapUtils/TIYandexMapUtils.podspec +++ b/TIYandexMapUtils/TIYandexMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIYandexMapUtils' - s.version = '1.41.0' + s.version = '1.42.0' s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.' s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/docs/tideeplink/deeplinks.md b/docs/tideeplink/deeplinks.md new file mode 100644 index 00000000..834329c2 --- /dev/null +++ b/docs/tideeplink/deeplinks.md @@ -0,0 +1,180 @@ + +# 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 { [weak self] completion in + self?.openOfficesScreen { + self?.openOfficesScreen(forId: id) + completion(.success(())) + } + + return Cancellables.nonCancellable() + } + } + } +} +``` + + **Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту + +```swift +final class ProjectDeeplinksService: TIDeeplinksService { + 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(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() +``` diff --git a/project-scripts/gen_docs_from_playgrounds.sh b/project-scripts/gen_docs_from_playgrounds.sh index 1c5e3242..784d2536 100755 --- a/project-scripts/gen_docs_from_playgrounds.sh +++ b/project-scripts/gen_docs_from_playgrounds.sh @@ -7,10 +7,17 @@ # SRCROOT - path to project folder. # -PLAYGROUNDS="${SRCROOT}/TIFoundationUtils/TIFoundationUtils.app -${SRCROOT}/TIUIElements/TIUIElements.app" +if [ -z ${SRCROOT} ]; then + echo SRCROOT environment variable not set + echo Run \'source setup\' to set SRCROOT environment variable + exit 1 +fi -for playground_path in ${PLAYGROUNDS}; do - nef compile --project ${playground_path} - nef markdown --project ${playground_path} --output ${SRCROOT}/docs +for module_name in $(cat ${SRCROOT}/project-scripts/ordered_modules_list.txt); do + APP_FILE=${SRCROOT}/${module_name}/${module_name}.app + + if [ -d "$APP_FILE" ]; then + nef compile --project $APP_FILE + nef markdown --project $APP_FILE --output ${SRCROOT}/docs + fi done diff --git a/project-scripts/ordered_modules_list.txt b/project-scripts/ordered_modules_list.txt index 71f5d1ca..181bc08e 100644 --- a/project-scripts/ordered_modules_list.txt +++ b/project-scripts/ordered_modules_list.txt @@ -17,3 +17,4 @@ TIYandexMapUtils TIEcommerce TIWebView TIDeveloperUtils +TIDeeplink