├── Deeplink-example
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Core
│ ├── Module.swift
│ ├── RoutingAssembly.swift
│ ├── RoutingEndpoint.swift
│ ├── RoutingErrorHandler.swift
│ └── RoutingProvider.swift
├── Module2
│ ├── ContactsEndpoint.swift
│ ├── ContactsDetailViewController.swift
│ ├── ContactsModule.swift
│ └── ContactsViewController.swift
├── Module1
│ ├── BookmarksEndpointDetail.swift
│ ├── BookmarksEndpointUnavailable.swift
│ ├── BookmarksDetailViewController.swift
│ ├── BookmarksDetailViewController2.swift
│ ├── BookmarksModule.swift
│ └── BookmarksViewController.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
└── AppDelegate.swift
├── README.md
├── Deeplink-example.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── LICENSE
└── .gitignore
/Deeplink-example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deeplinks
2 |
3 | This is example project for article https://nbelopotapov.medium.com/better-routing-with-deep-links-in-ios-app-451c52371ac8
4 |
--------------------------------------------------------------------------------
/Deeplink-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Deeplink-example/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Deeplink-example/Core/Module.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Module.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol FeatureModule {
11 | static var module: FeatureModule { get }
12 | func isTransitionAvailable(for key: String) -> Bool
13 | }
14 |
--------------------------------------------------------------------------------
/Deeplink-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Deeplink-example/Core/RoutingAssembly.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoutingAssembly.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Assembly for preparing transition for one specific module
11 | protocol RoutingAssembly {
12 |
13 | /// Method finds endpoint for transition path passed into method
14 | /// - Parameter key: transition path
15 | func endpoint(for key: String) -> RoutingEndpoint.Type?
16 | }
17 |
--------------------------------------------------------------------------------
/Deeplink-example/Core/RoutingEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoutingEndpoint.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Protocol describes endpoint
11 | protocol RoutingEndpoint: AnyObject {
12 |
13 | /// Check whether transition available with this endpoint
14 | var isAvailable: Bool { get }
15 | init()
16 |
17 | /// Starts transition in specified navigation controller
18 | func startTransition(in navigation: UINavigationController?)
19 | }
20 |
--------------------------------------------------------------------------------
/Deeplink-example/Module2/ContactsEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactsEndpoint.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ContactsEndpoint: RoutingEndpoint {
11 | var isAvailable: Bool {
12 | return ContactsModule.module.isTransitionAvailable(for: ContactsModuleTransitionKey.contactsDetail.rawValue)
13 | }
14 |
15 | func startTransition(in navigation: UINavigationController?) {
16 | let viewController = ContactsDetailViewController()
17 | navigation?.pushViewController(viewController, animated: true)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Deeplink-example/Module1/BookmarksEndpointDetail.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarksEndpointDetail.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class BookmarksEndpointDetail: RoutingEndpoint {
11 | var isAvailable: Bool {
12 | return BookmarksModule.module.isTransitionAvailable(for: BookmarksModuleTransitionKey.bookmarkDetail.rawValue)
13 | }
14 |
15 | func startTransition(in navigation: UINavigationController?) {
16 | let viewController = BookmarksDetailViewController()
17 | navigation?.pushViewController(viewController, animated: true)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Deeplink-example/Module1/BookmarksEndpointUnavailable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarksEndpointUnavailable.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class BookmarksEndpointUnavailable: RoutingEndpoint {
11 | var isAvailable: Bool {
12 | return BookmarksModule.module.isTransitionAvailable(for: BookmarksModuleTransitionKey.bookmarkDetail2.rawValue)
13 | }
14 |
15 | func startTransition(in navigation: UINavigationController?) {
16 | let viewController = BookmarksDetailViewController2()
17 | navigation?.pushViewController(viewController, animated: true)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Deeplink-example/Module2/ContactsDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactsDetailViewController.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ContactsDetailViewController: UIViewController {
11 | private let textLabel = UILabel()
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | textLabel.translatesAutoresizingMaskIntoConstraints = false
15 | textLabel.text = "\(ContactsDetailViewController.self)"
16 |
17 | view.addSubview(textLabel)
18 | NSLayoutConstraint.activate([
19 | textLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
20 | textLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
21 | ])
22 | view.backgroundColor = .white
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Deeplink-example/Module1/BookmarksDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarksDetailViewController.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class BookmarksDetailViewController: UIViewController {
11 | private let textLabel = UILabel()
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | textLabel.translatesAutoresizingMaskIntoConstraints = false
15 | textLabel.text = "\(BookmarksDetailViewController.self)"
16 |
17 | view.addSubview(textLabel)
18 | NSLayoutConstraint.activate([
19 | textLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
20 | textLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
21 | ])
22 | view.backgroundColor = .white
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Deeplink-example/Module1/BookmarksDetailViewController2.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarksDetailViewController2.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class BookmarksDetailViewController2: UIViewController {
11 | private let textLabel = UILabel()
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | textLabel.translatesAutoresizingMaskIntoConstraints = false
15 | textLabel.text = "\(BookmarksDetailViewController2.self)"
16 |
17 | view.addSubview(textLabel)
18 | NSLayoutConstraint.activate([
19 | textLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
20 | textLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
21 | ])
22 | view.backgroundColor = .white
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 nikitabelopotapov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Deeplink-example/Module2/ContactsModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactsModule.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ContactsModuleTransitionKey: String {
11 | case contactsDetail
12 | }
13 |
14 | final class ContactsModule {
15 | private static let privateModule = ContactsModule()
16 | let availabilityFacade = ContactsAvailabilityFacade()
17 | }
18 |
19 | extension ContactsModule: FeatureModule {
20 | static var module: FeatureModule {
21 | return ContactsModule.privateModule
22 | }
23 | }
24 |
25 | extension ContactsModule: RoutingAssembly {
26 | func isTransitionAvailable(for key: String) -> Bool {
27 | switch key {
28 | case ContactsModuleTransitionKey.contactsDetail.rawValue:
29 | return availabilityFacade.isContactsDetailTransitionAvailable
30 | default:
31 | return false
32 | }
33 | }
34 |
35 | func endpoint(for key: String) -> RoutingEndpoint.Type? {
36 | switch key {
37 | case ContactsModuleTransitionKey.contactsDetail.rawValue:
38 | return ContactsEndpoint.self
39 | default:
40 | return nil
41 | }
42 | }
43 | }
44 |
45 | /// Mock struct to show how endpoint availability could be used
46 | struct ContactsAvailabilityFacade {
47 | let isContactsDetailTransitionAvailable = true
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/Deeplink-example/Core/RoutingErrorHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoutingErrorHandler.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | /*
11 | Error handling during transition
12 | in this example we only cover unsupported transition - endpoint doesn't exists in assemblies
13 | unavailable transition - transition turned off for some reason
14 | */
15 | protocol RoutingErrorHandlerProtocol {
16 |
17 | ///
18 | func handleUnavailableTransition()
19 | func handleUnsupportedTransition()
20 | }
21 |
22 | final class RoutingErrorHandler: RoutingErrorHandlerProtocol {
23 | func handleUnsupportedTransition() {
24 | let alert = UIAlertController(title: "Unsupported transition", message: "This transition is unsupported", preferredStyle: .alert)
25 | let cancelAction = UIAlertAction(title: "ОК", style: .cancel, handler: nil)
26 | alert.addAction(cancelAction)
27 | UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
28 | }
29 |
30 | func handleUnavailableTransition() {
31 | let alert = UIAlertController(title: "Unavailable transition", message: "This transition is unavailable", preferredStyle: .alert)
32 | let cancelAction = UIAlertAction(title: "ОК", style: .cancel, handler: nil)
33 | alert.addAction(cancelAction)
34 | UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Deeplink-example/Module1/BookmarksModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarksModule.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum BookmarksModuleTransitionKey: String {
11 | case bookmarkDetail
12 | case bookmarkDetail2
13 | }
14 |
15 |
16 | final class BookmarksModule {
17 | private static let privateModule = BookmarksModule()
18 | let availabilityFacade = BookmarksAvailabilityFacade()
19 | }
20 |
21 | extension BookmarksModule: FeatureModule {
22 | static var module: FeatureModule {
23 | return BookmarksModule.privateModule
24 | }
25 | }
26 |
27 | extension BookmarksModule: RoutingAssembly {
28 | func endpoint(for key: String) -> RoutingEndpoint.Type? {
29 | switch key {
30 | case BookmarksModuleTransitionKey.bookmarkDetail.rawValue:
31 | return BookmarksEndpointDetail.self
32 | case BookmarksModuleTransitionKey.bookmarkDetail2.rawValue:
33 | return BookmarksEndpointUnavailable.self
34 | default:
35 | return nil
36 | }
37 | }
38 |
39 | func isTransitionAvailable(for key: String) -> Bool {
40 | switch key {
41 | case BookmarksModuleTransitionKey.bookmarkDetail.rawValue:
42 | return availabilityFacade.isBookmarkDetailTransitionAvailable
43 | case BookmarksModuleTransitionKey.bookmarkDetail2.rawValue:
44 | return availabilityFacade.isBookmarkDetailTransition2Available
45 | default:
46 | return false
47 | }
48 | }
49 | }
50 |
51 |
52 | /// Mock struct to show how endpoint availability could be used
53 | struct BookmarksAvailabilityFacade {
54 | let isBookmarkDetailTransitionAvailable = true
55 | let isBookmarkDetailTransition2Available = false
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/Deeplink-example/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Deeplink-example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleURLTypes
20 |
21 |
22 | CFBundleTypeRole
23 | Editor
24 | CFBundleURLSchemes
25 |
26 | deeplink-example
27 |
28 |
29 |
30 | CFBundleVersion
31 | 1
32 | LSRequiresIPhoneOS
33 |
34 | UIApplicationSupportsIndirectInputEvents
35 |
36 | UILaunchStoryboardName
37 | LaunchScreen
38 | UIRequiredDeviceCapabilities
39 |
40 | armv7
41 |
42 | UISupportedInterfaceOrientations
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | UISupportedInterfaceOrientations~ipad
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationPortraitUpsideDown
52 | UIInterfaceOrientationLandscapeLeft
53 | UIInterfaceOrientationLandscapeRight
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Deeplink-example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Deeplink-example/Module2/ContactsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactsViewController.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ContactsViewController: UIViewController {
11 | let contactDetailButton = UIButton()
12 | let emptyTransitionButton = UIButton()
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | view.backgroundColor = .white
17 |
18 | view.addSubview(contactDetailButton)
19 | contactDetailButton.setTitleColor(.black, for: .normal)
20 | contactDetailButton.setTitle("Contact Details", for: .normal)
21 | contactDetailButton.translatesAutoresizingMaskIntoConstraints = false
22 | contactDetailButton.addTarget(self, action: #selector(contactDetail), for: .touchUpInside)
23 |
24 | view.addSubview(emptyTransitionButton)
25 | emptyTransitionButton.setTitleColor(.black, for: .normal)
26 | emptyTransitionButton.setTitle("Empty transition", for: .normal)
27 | emptyTransitionButton.translatesAutoresizingMaskIntoConstraints = false
28 | emptyTransitionButton.addTarget(self, action: #selector(emptyTransition), for: .touchUpInside)
29 |
30 |
31 | NSLayoutConstraint.activate([
32 | contactDetailButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
33 | contactDetailButton.trailingAnchor.constraint(equalTo: emptyTransitionButton.leadingAnchor, constant: -20.0),
34 | contactDetailButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
35 | contactDetailButton.widthAnchor.constraint(equalTo: emptyTransitionButton.widthAnchor),
36 | emptyTransitionButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
37 | emptyTransitionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
38 | ])
39 | }
40 |
41 |
42 | @objc func contactDetail() {
43 | guard let url = URL(string: Router.urlScheme + ContactsModuleTransitionKey.contactsDetail.rawValue) else { return }
44 | UIApplication.shared.open(url, options: [:], completionHandler: nil)
45 | }
46 |
47 | @objc func emptyTransition() {
48 | guard let url = URL(string: Router.urlScheme + "qwerty") else { return }
49 | UIApplication.shared.open(url, options: [:], completionHandler: nil)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Deeplink-example/Module1/BookmarksViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarksViewController.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | final class BookmarksViewController: UIViewController {
11 | let bookmarkDetailButton = UIButton()
12 | let unavailableTransitionButton = UIButton()
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | view.backgroundColor = .white
16 | view.addSubview(bookmarkDetailButton)
17 | bookmarkDetailButton.setTitleColor(.black, for: .normal)
18 | bookmarkDetailButton.setTitle("Detail", for: .normal)
19 | bookmarkDetailButton.translatesAutoresizingMaskIntoConstraints = false
20 | bookmarkDetailButton.addTarget(self, action: #selector(bookmarkDetail), for: .touchUpInside)
21 |
22 | view.addSubview(unavailableTransitionButton)
23 | unavailableTransitionButton.setTitleColor(.black, for: .normal)
24 | unavailableTransitionButton.setTitle("Unavailable", for: .normal)
25 | unavailableTransitionButton.translatesAutoresizingMaskIntoConstraints = false
26 | unavailableTransitionButton.addTarget(self, action: #selector(unavailableTransition), for: .touchUpInside)
27 |
28 |
29 | NSLayoutConstraint.activate([
30 | bookmarkDetailButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
31 | bookmarkDetailButton.trailingAnchor.constraint(equalTo: unavailableTransitionButton.leadingAnchor, constant: -20.0),
32 | bookmarkDetailButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
33 | bookmarkDetailButton.widthAnchor.constraint(equalTo: unavailableTransitionButton.widthAnchor),
34 | unavailableTransitionButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
35 | unavailableTransitionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
36 | ])
37 | }
38 |
39 |
40 | @objc func bookmarkDetail() {
41 | guard let url = URL(string: Router.urlScheme + BookmarksModuleTransitionKey.bookmarkDetail.rawValue) else { return }
42 | UIApplication.shared.open(url, options: [:], completionHandler: nil)
43 | }
44 |
45 | @objc func unavailableTransition() {
46 | guard let url = URL(string: Router.urlScheme + BookmarksModuleTransitionKey.bookmarkDetail2.rawValue) else { return }
47 | UIApplication.shared.open(url, options: [:], completionHandler: nil)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Deeplink-example/Core/RoutingProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoutingProvider.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | /// Protocol with main logic for router
12 | protocol RoutingProvider {
13 |
14 | /// Appends new assembly for transition, in our case module is assembly, but it can be any other object conforming to RoutingAssembly protocol
15 | /// - Parameter assembly: new assembly
16 | func append(assembly: RoutingAssembly)
17 |
18 | /// Start transition with specified parameters
19 | /// - Parameters:
20 | /// - key: transition key, for example transition1
21 | /// - options: options received within method openURL in AppDelegate
22 | func startTransition(key: String, options: [UIApplication.OpenURLOptionsKey : Any])
23 |
24 | /// If you want your code be clean, you can pass navigation controller to router so endpoints could use it
25 | /// - Parameter navigation: navigation controller that should open new screen
26 | func set(navigation: UINavigationController)
27 |
28 | /// Error handler to show error during transition, cleans up router from error handling
29 | func set(errorHandler: RoutingErrorHandlerProtocol)
30 | }
31 |
32 |
33 | final class Router {
34 | static let urlScheme = "deeplink-example://"
35 | private var assemblies = [RoutingAssembly]()
36 | private weak var navigationController: UINavigationController?
37 | private var errorHandler: RoutingErrorHandlerProtocol?
38 | }
39 |
40 | extension Router: RoutingProvider {
41 | func append(assembly: RoutingAssembly) {
42 | assemblies.append(assembly)
43 | }
44 |
45 | func startTransition(key: String, options: [UIApplication.OpenURLOptionsKey : Any]) {
46 |
47 | /// Pass througth all assemblies to find needed endpoint to perform transition
48 | for assembly in assemblies {
49 | guard let endpointType = assembly.endpoint(for: key) else { continue }
50 | let endpoint = endpointType.init()
51 |
52 | if endpoint.isAvailable {
53 | endpoint.startTransition(in: navigationController)
54 | } else {
55 | errorHandler?.handleUnavailableTransition()
56 | }
57 |
58 | return
59 | }
60 |
61 | errorHandler?.handleUnsupportedTransition()
62 | }
63 |
64 | func set(navigation: UINavigationController) {
65 | navigationController = navigation
66 | }
67 |
68 | func set(errorHandler: RoutingErrorHandlerProtocol) {
69 | self.errorHandler = errorHandler
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/Deeplink-example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Deeplink-example
4 | //
5 | // Created by Nikita Belopotapov on 29.05.2021.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 | var window: UIWindow?
13 | private lazy var tabbarController = UITabBarController()
14 | private lazy var router = Router()
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 |
18 | // First module configuration
19 | let bookmarksNavigationController = UINavigationController()
20 | let bookmarksViewController = BookmarksViewController()
21 | bookmarksNavigationController.setViewControllers([bookmarksViewController], animated: false)
22 | bookmarksViewController.tabBarItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 0)
23 |
24 | // Second module configuration
25 | let contactsNavigationController = UINavigationController()
26 | let contastViewController = ContactsViewController()
27 | contactsNavigationController.setViewControllers([contastViewController], animated: false)
28 | contastViewController.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 1)
29 |
30 | // Tabbar configuration
31 | tabbarController.setViewControllers([bookmarksNavigationController, contactsNavigationController], animated: true)
32 |
33 |
34 | // Window configuration
35 | window = UIWindow()
36 | window?.rootViewController = tabbarController
37 | window?.makeKeyAndVisible()
38 |
39 |
40 | // Initializing error handler - error handler abstraction for router
41 | let errorHandler = RoutingErrorHandler()
42 | router.set(errorHandler: errorHandler)
43 |
44 | // Adding modules as routing assemblies for router
45 | if let bookmarksModule = BookmarksModule.module as? RoutingAssembly {
46 | router.append(assembly: bookmarksModule)
47 | }
48 |
49 | if let contactsModule = ContactsModule.module as? RoutingAssembly {
50 | router.append(assembly: contactsModule)
51 | }
52 | return true
53 | }
54 |
55 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
56 | /*
57 | Parsing received URL to get transition key
58 | for example we receive URL deeplink-example://transition1?param1=qwe¶m2=rty
59 | to start transition, all we need is transition1 string
60 | so we truncate url scheme by separating string by "://"
61 | and then truncate right part of link by separating string by ? sign
62 | */
63 | if let endpointPath = url.absoluteString.components(separatedBy: "://").last?.components(separatedBy: "?").first {
64 | if let navigation = tabbarController.selectedViewController as? UINavigationController {
65 | router.set(navigation: navigation)
66 | router.startTransition(key: endpointPath, options: options)
67 | return true
68 | }
69 | }
70 |
71 | return false
72 | }
73 |
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/Deeplink-example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0E5AD9A12662523A006E87FB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9A02662523A006E87FB /* AppDelegate.swift */; };
11 | 0E5AD9AA2662523B006E87FB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E5AD9A92662523B006E87FB /* Assets.xcassets */; };
12 | 0E5AD9AD2662523B006E87FB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E5AD9AB2662523B006E87FB /* LaunchScreen.storyboard */; };
13 | 0E5AD9B526625275006E87FB /* BookmarksModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9B426625275006E87FB /* BookmarksModule.swift */; };
14 | 0E5AD9B72662533A006E87FB /* RoutingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9B62662533A006E87FB /* RoutingProvider.swift */; };
15 | 0E5AD9B926625369006E87FB /* RoutingEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9B826625369006E87FB /* RoutingEndpoint.swift */; };
16 | 0E5AD9BE26625C91006E87FB /* BookmarksEndpointDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9BD26625C91006E87FB /* BookmarksEndpointDetail.swift */; };
17 | 0E5AD9C026625DE6006E87FB /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9BF26625DE6006E87FB /* Module.swift */; };
18 | 0E5AD9C2266276F9006E87FB /* ContactsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9C1266276F9006E87FB /* ContactsModule.swift */; };
19 | 0E5AD9C42663A8D2006E87FB /* BookmarksEndpointUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9C32663A8D2006E87FB /* BookmarksEndpointUnavailable.swift */; };
20 | 0E5AD9C62663A9EA006E87FB /* ContactsEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9C52663A9EA006E87FB /* ContactsEndpoint.swift */; };
21 | 0E5AD9CA2663AA14006E87FB /* RoutingAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9C92663AA14006E87FB /* RoutingAssembly.swift */; };
22 | 0E5AD9CC2663AAD4006E87FB /* BookmarksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9CB2663AAD4006E87FB /* BookmarksViewController.swift */; };
23 | 0E5AD9CE2663AADF006E87FB /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9CD2663AADF006E87FB /* ContactsViewController.swift */; };
24 | 0E5AD9D02663AAFA006E87FB /* ContactsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9CF2663AAF9006E87FB /* ContactsDetailViewController.swift */; };
25 | 0E5AD9D22663AB01006E87FB /* BookmarksDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9D12663AB01006E87FB /* BookmarksDetailViewController.swift */; };
26 | 0E5AD9D42663BBD6006E87FB /* BookmarksDetailViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9D32663BBD6006E87FB /* BookmarksDetailViewController2.swift */; };
27 | 0E5AD9D82663C3B9006E87FB /* RoutingErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5AD9D72663C3B9006E87FB /* RoutingErrorHandler.swift */; };
28 | /* End PBXBuildFile section */
29 |
30 | /* Begin PBXFileReference section */
31 | 0E5AD99D2662523A006E87FB /* Deeplink-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Deeplink-example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
32 | 0E5AD9A02662523A006E87FB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
33 | 0E5AD9A92662523B006E87FB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
34 | 0E5AD9AC2662523B006E87FB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
35 | 0E5AD9AE2662523B006E87FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
36 | 0E5AD9B426625275006E87FB /* BookmarksModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksModule.swift; sourceTree = ""; };
37 | 0E5AD9B62662533A006E87FB /* RoutingProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingProvider.swift; sourceTree = ""; };
38 | 0E5AD9B826625369006E87FB /* RoutingEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingEndpoint.swift; sourceTree = ""; };
39 | 0E5AD9BD26625C91006E87FB /* BookmarksEndpointDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksEndpointDetail.swift; sourceTree = ""; };
40 | 0E5AD9BF26625DE6006E87FB /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = ""; };
41 | 0E5AD9C1266276F9006E87FB /* ContactsModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsModule.swift; sourceTree = ""; };
42 | 0E5AD9C32663A8D2006E87FB /* BookmarksEndpointUnavailable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksEndpointUnavailable.swift; sourceTree = ""; };
43 | 0E5AD9C52663A9EA006E87FB /* ContactsEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsEndpoint.swift; sourceTree = ""; };
44 | 0E5AD9C92663AA14006E87FB /* RoutingAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingAssembly.swift; sourceTree = ""; };
45 | 0E5AD9CB2663AAD4006E87FB /* BookmarksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksViewController.swift; sourceTree = ""; };
46 | 0E5AD9CD2663AADF006E87FB /* ContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewController.swift; sourceTree = ""; };
47 | 0E5AD9CF2663AAF9006E87FB /* ContactsDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsDetailViewController.swift; sourceTree = ""; };
48 | 0E5AD9D12663AB01006E87FB /* BookmarksDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksDetailViewController.swift; sourceTree = ""; };
49 | 0E5AD9D32663BBD6006E87FB /* BookmarksDetailViewController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksDetailViewController2.swift; sourceTree = ""; };
50 | 0E5AD9D72663C3B9006E87FB /* RoutingErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingErrorHandler.swift; sourceTree = ""; };
51 | /* End PBXFileReference section */
52 |
53 | /* Begin PBXFrameworksBuildPhase section */
54 | 0E5AD99A2662523A006E87FB /* Frameworks */ = {
55 | isa = PBXFrameworksBuildPhase;
56 | buildActionMask = 2147483647;
57 | files = (
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | /* End PBXFrameworksBuildPhase section */
62 |
63 | /* Begin PBXGroup section */
64 | 0E5AD9942662523A006E87FB = {
65 | isa = PBXGroup;
66 | children = (
67 | 0E5AD99F2662523A006E87FB /* Deeplink-example */,
68 | 0E5AD99E2662523A006E87FB /* Products */,
69 | );
70 | sourceTree = "";
71 | };
72 | 0E5AD99E2662523A006E87FB /* Products */ = {
73 | isa = PBXGroup;
74 | children = (
75 | 0E5AD99D2662523A006E87FB /* Deeplink-example.app */,
76 | );
77 | name = Products;
78 | sourceTree = "";
79 | };
80 | 0E5AD99F2662523A006E87FB /* Deeplink-example */ = {
81 | isa = PBXGroup;
82 | children = (
83 | 0E5AD9A02662523A006E87FB /* AppDelegate.swift */,
84 | 0E5AD9BC266259DB006E87FB /* Core */,
85 | 0E5AD9BA266259C7006E87FB /* Module1 */,
86 | 0E5AD9BB266259D0006E87FB /* Module2 */,
87 | 0E5AD9A92662523B006E87FB /* Assets.xcassets */,
88 | 0E5AD9AB2662523B006E87FB /* LaunchScreen.storyboard */,
89 | 0E5AD9AE2662523B006E87FB /* Info.plist */,
90 | );
91 | path = "Deeplink-example";
92 | sourceTree = "";
93 | };
94 | 0E5AD9BA266259C7006E87FB /* Module1 */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 0E5AD9B426625275006E87FB /* BookmarksModule.swift */,
98 | 0E5AD9BD26625C91006E87FB /* BookmarksEndpointDetail.swift */,
99 | 0E5AD9C32663A8D2006E87FB /* BookmarksEndpointUnavailable.swift */,
100 | 0E5AD9CB2663AAD4006E87FB /* BookmarksViewController.swift */,
101 | 0E5AD9D12663AB01006E87FB /* BookmarksDetailViewController.swift */,
102 | 0E5AD9D32663BBD6006E87FB /* BookmarksDetailViewController2.swift */,
103 | );
104 | path = Module1;
105 | sourceTree = "";
106 | };
107 | 0E5AD9BB266259D0006E87FB /* Module2 */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 0E5AD9C1266276F9006E87FB /* ContactsModule.swift */,
111 | 0E5AD9C52663A9EA006E87FB /* ContactsEndpoint.swift */,
112 | 0E5AD9CD2663AADF006E87FB /* ContactsViewController.swift */,
113 | 0E5AD9CF2663AAF9006E87FB /* ContactsDetailViewController.swift */,
114 | );
115 | path = Module2;
116 | sourceTree = "";
117 | };
118 | 0E5AD9BC266259DB006E87FB /* Core */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 0E5AD9B62662533A006E87FB /* RoutingProvider.swift */,
122 | 0E5AD9D72663C3B9006E87FB /* RoutingErrorHandler.swift */,
123 | 0E5AD9C92663AA14006E87FB /* RoutingAssembly.swift */,
124 | 0E5AD9B826625369006E87FB /* RoutingEndpoint.swift */,
125 | 0E5AD9BF26625DE6006E87FB /* Module.swift */,
126 | );
127 | path = Core;
128 | sourceTree = "";
129 | };
130 | /* End PBXGroup section */
131 |
132 | /* Begin PBXNativeTarget section */
133 | 0E5AD99C2662523A006E87FB /* Deeplink-example */ = {
134 | isa = PBXNativeTarget;
135 | buildConfigurationList = 0E5AD9B12662523B006E87FB /* Build configuration list for PBXNativeTarget "Deeplink-example" */;
136 | buildPhases = (
137 | 0E5AD9992662523A006E87FB /* Sources */,
138 | 0E5AD99A2662523A006E87FB /* Frameworks */,
139 | 0E5AD99B2662523A006E87FB /* Resources */,
140 | );
141 | buildRules = (
142 | );
143 | dependencies = (
144 | );
145 | name = "Deeplink-example";
146 | productName = "Deeplink-example";
147 | productReference = 0E5AD99D2662523A006E87FB /* Deeplink-example.app */;
148 | productType = "com.apple.product-type.application";
149 | };
150 | /* End PBXNativeTarget section */
151 |
152 | /* Begin PBXProject section */
153 | 0E5AD9952662523A006E87FB /* Project object */ = {
154 | isa = PBXProject;
155 | attributes = {
156 | LastSwiftUpdateCheck = 1250;
157 | LastUpgradeCheck = 1250;
158 | TargetAttributes = {
159 | 0E5AD99C2662523A006E87FB = {
160 | CreatedOnToolsVersion = 12.5;
161 | };
162 | };
163 | };
164 | buildConfigurationList = 0E5AD9982662523A006E87FB /* Build configuration list for PBXProject "Deeplink-example" */;
165 | compatibilityVersion = "Xcode 9.3";
166 | developmentRegion = en;
167 | hasScannedForEncodings = 0;
168 | knownRegions = (
169 | en,
170 | Base,
171 | );
172 | mainGroup = 0E5AD9942662523A006E87FB;
173 | productRefGroup = 0E5AD99E2662523A006E87FB /* Products */;
174 | projectDirPath = "";
175 | projectRoot = "";
176 | targets = (
177 | 0E5AD99C2662523A006E87FB /* Deeplink-example */,
178 | );
179 | };
180 | /* End PBXProject section */
181 |
182 | /* Begin PBXResourcesBuildPhase section */
183 | 0E5AD99B2662523A006E87FB /* Resources */ = {
184 | isa = PBXResourcesBuildPhase;
185 | buildActionMask = 2147483647;
186 | files = (
187 | 0E5AD9AD2662523B006E87FB /* LaunchScreen.storyboard in Resources */,
188 | 0E5AD9AA2662523B006E87FB /* Assets.xcassets in Resources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXResourcesBuildPhase section */
193 |
194 | /* Begin PBXSourcesBuildPhase section */
195 | 0E5AD9992662523A006E87FB /* Sources */ = {
196 | isa = PBXSourcesBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | 0E5AD9CC2663AAD4006E87FB /* BookmarksViewController.swift in Sources */,
200 | 0E5AD9C2266276F9006E87FB /* ContactsModule.swift in Sources */,
201 | 0E5AD9C62663A9EA006E87FB /* ContactsEndpoint.swift in Sources */,
202 | 0E5AD9D42663BBD6006E87FB /* BookmarksDetailViewController2.swift in Sources */,
203 | 0E5AD9D82663C3B9006E87FB /* RoutingErrorHandler.swift in Sources */,
204 | 0E5AD9BE26625C91006E87FB /* BookmarksEndpointDetail.swift in Sources */,
205 | 0E5AD9B926625369006E87FB /* RoutingEndpoint.swift in Sources */,
206 | 0E5AD9B526625275006E87FB /* BookmarksModule.swift in Sources */,
207 | 0E5AD9A12662523A006E87FB /* AppDelegate.swift in Sources */,
208 | 0E5AD9C42663A8D2006E87FB /* BookmarksEndpointUnavailable.swift in Sources */,
209 | 0E5AD9D02663AAFA006E87FB /* ContactsDetailViewController.swift in Sources */,
210 | 0E5AD9CA2663AA14006E87FB /* RoutingAssembly.swift in Sources */,
211 | 0E5AD9C026625DE6006E87FB /* Module.swift in Sources */,
212 | 0E5AD9CE2663AADF006E87FB /* ContactsViewController.swift in Sources */,
213 | 0E5AD9B72662533A006E87FB /* RoutingProvider.swift in Sources */,
214 | 0E5AD9D22663AB01006E87FB /* BookmarksDetailViewController.swift in Sources */,
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | /* End PBXSourcesBuildPhase section */
219 |
220 | /* Begin PBXVariantGroup section */
221 | 0E5AD9AB2662523B006E87FB /* LaunchScreen.storyboard */ = {
222 | isa = PBXVariantGroup;
223 | children = (
224 | 0E5AD9AC2662523B006E87FB /* Base */,
225 | );
226 | name = LaunchScreen.storyboard;
227 | sourceTree = "";
228 | };
229 | /* End PBXVariantGroup section */
230 |
231 | /* Begin XCBuildConfiguration section */
232 | 0E5AD9AF2662523B006E87FB /* Debug */ = {
233 | isa = XCBuildConfiguration;
234 | buildSettings = {
235 | ALWAYS_SEARCH_USER_PATHS = NO;
236 | CLANG_ANALYZER_NONNULL = YES;
237 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
238 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
239 | CLANG_CXX_LIBRARY = "libc++";
240 | CLANG_ENABLE_MODULES = YES;
241 | CLANG_ENABLE_OBJC_ARC = YES;
242 | CLANG_ENABLE_OBJC_WEAK = YES;
243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
244 | CLANG_WARN_BOOL_CONVERSION = YES;
245 | CLANG_WARN_COMMA = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
249 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
250 | CLANG_WARN_EMPTY_BODY = YES;
251 | CLANG_WARN_ENUM_CONVERSION = YES;
252 | CLANG_WARN_INFINITE_RECURSION = YES;
253 | CLANG_WARN_INT_CONVERSION = YES;
254 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
255 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
256 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
257 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
258 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
260 | CLANG_WARN_STRICT_PROTOTYPES = YES;
261 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
262 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
263 | CLANG_WARN_UNREACHABLE_CODE = YES;
264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
265 | COPY_PHASE_STRIP = NO;
266 | DEBUG_INFORMATION_FORMAT = dwarf;
267 | ENABLE_STRICT_OBJC_MSGSEND = YES;
268 | ENABLE_TESTABILITY = YES;
269 | GCC_C_LANGUAGE_STANDARD = gnu11;
270 | GCC_DYNAMIC_NO_PIC = NO;
271 | GCC_NO_COMMON_BLOCKS = YES;
272 | GCC_OPTIMIZATION_LEVEL = 0;
273 | GCC_PREPROCESSOR_DEFINITIONS = (
274 | "DEBUG=1",
275 | "$(inherited)",
276 | );
277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
279 | GCC_WARN_UNDECLARED_SELECTOR = YES;
280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
281 | GCC_WARN_UNUSED_FUNCTION = YES;
282 | GCC_WARN_UNUSED_VARIABLE = YES;
283 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
284 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
285 | MTL_FAST_MATH = YES;
286 | ONLY_ACTIVE_ARCH = YES;
287 | SDKROOT = iphoneos;
288 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
290 | };
291 | name = Debug;
292 | };
293 | 0E5AD9B02662523B006E87FB /* Release */ = {
294 | isa = XCBuildConfiguration;
295 | buildSettings = {
296 | ALWAYS_SEARCH_USER_PATHS = NO;
297 | CLANG_ANALYZER_NONNULL = YES;
298 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
300 | CLANG_CXX_LIBRARY = "libc++";
301 | CLANG_ENABLE_MODULES = YES;
302 | CLANG_ENABLE_OBJC_ARC = YES;
303 | CLANG_ENABLE_OBJC_WEAK = YES;
304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
305 | CLANG_WARN_BOOL_CONVERSION = YES;
306 | CLANG_WARN_COMMA = YES;
307 | CLANG_WARN_CONSTANT_CONVERSION = YES;
308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
310 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
311 | CLANG_WARN_EMPTY_BODY = YES;
312 | CLANG_WARN_ENUM_CONVERSION = YES;
313 | CLANG_WARN_INFINITE_RECURSION = YES;
314 | CLANG_WARN_INT_CONVERSION = YES;
315 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
316 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
317 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
319 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
320 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
321 | CLANG_WARN_STRICT_PROTOTYPES = YES;
322 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
323 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
324 | CLANG_WARN_UNREACHABLE_CODE = YES;
325 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
326 | COPY_PHASE_STRIP = NO;
327 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
328 | ENABLE_NS_ASSERTIONS = NO;
329 | ENABLE_STRICT_OBJC_MSGSEND = YES;
330 | GCC_C_LANGUAGE_STANDARD = gnu11;
331 | GCC_NO_COMMON_BLOCKS = YES;
332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
334 | GCC_WARN_UNDECLARED_SELECTOR = YES;
335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
336 | GCC_WARN_UNUSED_FUNCTION = YES;
337 | GCC_WARN_UNUSED_VARIABLE = YES;
338 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
339 | MTL_ENABLE_DEBUG_INFO = NO;
340 | MTL_FAST_MATH = YES;
341 | SDKROOT = iphoneos;
342 | SWIFT_COMPILATION_MODE = wholemodule;
343 | SWIFT_OPTIMIZATION_LEVEL = "-O";
344 | VALIDATE_PRODUCT = YES;
345 | };
346 | name = Release;
347 | };
348 | 0E5AD9B22662523B006E87FB /* Debug */ = {
349 | isa = XCBuildConfiguration;
350 | buildSettings = {
351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
352 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
353 | CODE_SIGN_STYLE = Automatic;
354 | INFOPLIST_FILE = "Deeplink-example/Info.plist";
355 | LD_RUNPATH_SEARCH_PATHS = (
356 | "$(inherited)",
357 | "@executable_path/Frameworks",
358 | );
359 | PRODUCT_BUNDLE_IDENTIFIER = "com.nbelopotapov.Deeplink-example";
360 | PRODUCT_NAME = "$(TARGET_NAME)";
361 | SWIFT_VERSION = 5.0;
362 | TARGETED_DEVICE_FAMILY = "1,2";
363 | };
364 | name = Debug;
365 | };
366 | 0E5AD9B32662523B006E87FB /* Release */ = {
367 | isa = XCBuildConfiguration;
368 | buildSettings = {
369 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
370 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
371 | CODE_SIGN_STYLE = Automatic;
372 | INFOPLIST_FILE = "Deeplink-example/Info.plist";
373 | LD_RUNPATH_SEARCH_PATHS = (
374 | "$(inherited)",
375 | "@executable_path/Frameworks",
376 | );
377 | PRODUCT_BUNDLE_IDENTIFIER = "com.nbelopotapov.Deeplink-example";
378 | PRODUCT_NAME = "$(TARGET_NAME)";
379 | SWIFT_VERSION = 5.0;
380 | TARGETED_DEVICE_FAMILY = "1,2";
381 | };
382 | name = Release;
383 | };
384 | /* End XCBuildConfiguration section */
385 |
386 | /* Begin XCConfigurationList section */
387 | 0E5AD9982662523A006E87FB /* Build configuration list for PBXProject "Deeplink-example" */ = {
388 | isa = XCConfigurationList;
389 | buildConfigurations = (
390 | 0E5AD9AF2662523B006E87FB /* Debug */,
391 | 0E5AD9B02662523B006E87FB /* Release */,
392 | );
393 | defaultConfigurationIsVisible = 0;
394 | defaultConfigurationName = Release;
395 | };
396 | 0E5AD9B12662523B006E87FB /* Build configuration list for PBXNativeTarget "Deeplink-example" */ = {
397 | isa = XCConfigurationList;
398 | buildConfigurations = (
399 | 0E5AD9B22662523B006E87FB /* Debug */,
400 | 0E5AD9B32662523B006E87FB /* Release */,
401 | );
402 | defaultConfigurationIsVisible = 0;
403 | defaultConfigurationName = Release;
404 | };
405 | /* End XCConfigurationList section */
406 | };
407 | rootObject = 0E5AD9952662523A006E87FB /* Project object */;
408 | }
409 |
--------------------------------------------------------------------------------