Slather logo

Coverage for "StoryboardFactory.swift" : 82.50%

(33 of 40 relevant lines covered)

RouteComposer/Classes/Factories/StoryboardFactory.swift

1
//
2
// RouteComposer
3
// StoryboardFactory.swift
4
// https://github.com/ekazaev/route-composer
5
//
6
// Created by Eugene Kazaev in 2018-2022.
7
// Distributed under the MIT license.
8
//
9
// Become a sponsor:
10
// https://github.com/sponsors/ekazaev
11
//
12
13
import UIKit
14
15
/// The `Factory` that creates a `UIViewController` from a storyboard.
16
public struct StoryboardFactory<VC: UIViewController, C>: Factory {
17
18
    // MARK: Associated types
19
20
    public typealias ViewController = VC
21
22
    public typealias Context = C
23
24
    // MARK: Properties
25
26
    /// The name of a storyboard file
27
    public let name: String
28
29
    /// The `Bundle` instance
30
    public let bundle: Bundle?
31
32
    /// The `UIViewController` identifier in the storyboard. If it is not set, the `Factory` will try
33
    /// to create the storyboards initial `UIViewController`
34
    public let identifier: String?
35
36
    /// The additional configuration block
37
    public let configuration: ((_: VC) -> Void)?
38
39
    // MARK: Methods
40
41
    /// Constructor
42
    ///
43
    /// - Parameters:
44
    ///   - storyboardName: The name of a storyboard file
45
    ///   - bundle: The `Bundle` instance if needed
46
    ///   - identifier: The `UIViewController` identifier in the storyboard. If it is not set, the `Factory` will try
47
    ///     to create the storyboards initial `UIViewController`
48
    ///   - configuration: A block of code that will be used for the extended configuration of the created `UIViewController`. Can be used for
49
    ///                    a quick configuration instead of `ContextTask`.
50
    public init(name: String, bundle: Bundle? = nil, identifier: String? = nil, configuration: ((_: VC) -> Void)? = nil) {
102x
51
        self.name = name
102x
52
        self.bundle = bundle
102x
53
        self.identifier = identifier
102x
54
        self.configuration = configuration
102x
55
    }
102x
56
57
    public func build(with context: C) throws -> VC {
62x
58
        guard let viewControllerID = identifier else {
62x
59
            return try buildInitialViewController()
31x
60
        }
31x
61
        let storyboard = UIStoryboard(name: name, bundle: bundle)
31x
62
        let instantiatedViewController = storyboard.instantiateViewController(withIdentifier: viewControllerID)
31x
63
        guard let viewController = instantiatedViewController as? VC else {
31x
64
            throw RoutingError.typeMismatch(type: type(of: instantiatedViewController),
!
65
                                            expectedType: VC.self,
!
66
                                            .init("Unable to instantiate UIViewController with \(viewControllerID) identifier in \(name) storyboard " +
!
67
                                                "as \(String(describing: type(of: VC.self))), got \(String(describing: instantiatedViewController)) instead."))
!
68
        }
31x
69
        if let configuration {
31x
70
            configuration(viewController)
1x
71
        }
31x
72
        return viewController
31x
73
    }
31x
74
75
    private func buildInitialViewController() throws -> VC {
31x
76
        let storyboard = UIStoryboard(name: name, bundle: bundle)
31x
77
        guard let abstractViewController = storyboard.instantiateInitialViewController() else {
31x
78
            throw RoutingError.compositionFailed(.init("Unable to instantiate initial UIViewController " +
!
79
                    "in \(name) storyboard"))
!
80
        }
31x
81
        guard let viewController = abstractViewController as? VC else {
31x
82
            throw RoutingError.typeMismatch(type: type(of: abstractViewController),
2x
83
                                            expectedType: VC.self,
2x
84
                                            .init("Unable to instantiate the initial UIViewController in \(name) storyboard " +
2x
85
                                                "as \(String(describing: type(of: VC.self))), got \(String(describing: abstractViewController)) instead."))
2x
86
        }
29x
87
        if let configuration {
29x
88
            configuration(viewController)
!
89
        }
29x
90
        return viewController
29x
91
    }
29x
92
93
}