Slather logo

Coverage for "ContainerActionBox.swift" : 68.06%

(49 of 72 relevant lines covered)

RouteComposer/Classes/Router/Type Erasure/Boxes/ContainerActionBox.swift

1
//
2
// RouteComposer
3
// ContainerActionBox.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 Foundation
14
import UIKit
15
16
struct ContainerActionBox<A: ContainerAction>: AnyAction, AnyActionBox, CustomStringConvertible, MainThreadChecking {
17
18
    let action: A
19
20
    init(_ action: A) {
126x
21
        self.action = action
126x
22
    }
126x
23
24
    func perform(with viewController: UIViewController,
25
                 on existingController: UIViewController,
26
                 with postponedIntegrationHandler: PostponedActionIntegrationHandler,
27
                 nextAction: AnyAction?,
28
                 animated: Bool,
29
                 completion: @escaping (RoutingResult) -> Void) {
30x
30
        assertIfNotMainThread()
30x
31
        if let postponedController = postponedIntegrationHandler.containerViewController {
30x
32
            guard postponedController is A.ViewController else {
1x
33
                postponedIntegrationHandler.purge(animated: animated, completion: { result in
!
34
                    guard result.isSuccessful else {
!
35
                        completion(result)
!
36
                        return
!
37
                    }
!
38
                    self.perform(with: viewController,
!
39
                                 on: existingController,
!
40
                                 with: postponedIntegrationHandler,
!
41
                                 nextAction: nextAction,
!
42
                                 animated: animated,
!
43
                                 completion: completion)
!
44
                })
!
45
                return
!
46
            }
1x
47
            embed(viewController: viewController, with: postponedIntegrationHandler, completion: completion)
1x
48
        } else {
30x
49
            guard let containerController: A.ViewController = UIViewController.findContainer(of: existingController) else {
29x
50
                completion(.failure(RoutingError.typeMismatch(type: type(of: existingController),
!
51
                                                              expectedType: ActionType.ViewController.self,
!
52
                                                              .init("Container of \(String(describing: ActionType.ViewController.self)) type cannot be found in the parents of " +
!
53
                                                                  "\(String(describing: existingController)) to perform \(action)"))))
!
54
                return
!
55
            }
29x
56
            let shouldDelayPerforming = nextAction?.isEmbeddable(to: A.ViewController.self) ?? false
29x
57
            if shouldDelayPerforming {
29x
58
                postponedIntegrationHandler.update(containerViewController: containerController, animated: animated, completion: { result in
2x
59
                    guard result.isSuccessful else {
2x
60
                        completion(result)
!
61
                        return
!
62
                    }
2x
63
                    self.embed(viewController: viewController, with: postponedIntegrationHandler, completion: completion)
2x
64
                })
2x
65
            } else {
29x
66
                postponedIntegrationHandler.purge(animated: animated, completion: { result in
27x
67
                    guard result.isSuccessful else {
27x
68
                        completion(result)
!
69
                        return
!
70
                    }
27x
71
                    self.action.perform(with: viewController, on: containerController, animated: animated) { result in
27x
72
                        self.assertIfNotMainThread()
27x
73
                        completion(result)
27x
74
                    }
27x
75
                })
27x
76
            }
29x
77
        }
30x
78
    }
30x
79
80
    private func embed(viewController: UIViewController, with postponedIntegrationHandler: PostponedActionIntegrationHandler, completion: @escaping (RoutingResult) -> Void) {
3x
81
        do {
3x
82
            var postponedChildControllers = postponedIntegrationHandler.postponedViewControllers
3x
83
            try perform(embedding: viewController, in: &postponedChildControllers)
3x
84
            postponedIntegrationHandler.update(postponedViewControllers: postponedChildControllers)
3x
85
            completion(.success)
3x
86
        } catch {
3x
87
            completion(.failure(error))
!
88
        }
3x
89
    }
3x
90
91
    func perform(embedding viewController: UIViewController, in childViewControllers: inout [UIViewController]) throws {
67x
92
        try action.perform(embedding: viewController, in: &childViewControllers)
67x
93
    }
67x
94
95
    public var description: String {
28x
96
        String(describing: action)
28x
97
    }
28x
98
99
    func isEmbeddable(to container: ContainerViewController.Type) -> Bool {
40x
100
        container is A.ViewController.Type
40x
101
    }
40x
102
103
}