-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathModuleViewController.swift
More file actions
130 lines (101 loc) · 5.54 KB
/
Copy pathModuleViewController.swift
File metadata and controls
130 lines (101 loc) · 5.54 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import SwiftUI
import UIKit
/// Контроллер модуля: собирает живой экран по описанию `ViewBody`
/// и приводит его к новому описанию, когда менеджер пересчитал `view.body`.
///
/// В монорепозитории контроллер строит UIKit-иерархию напрямую:
/// навигационная панель и список — свои надстройки над UIKit,
/// обновление контента идёт через дифф коллекции. Здесь каждый слот
/// отдаётся своему `UIHostingController` — базовый слой остаётся
/// на UIKit, компоненты пишутся на SwiftUI, как и в проде.
@MainActor
public final class ModuleViewController<View: ModuleView>: UIViewController {
private var manager: ModuleViewManager<View>?
private let navigationBarController = UIHostingController(rootView: AnyView(EmptyView()))
private let contentController = UIHostingController(rootView: AnyView(EmptyView()))
private let accessoryBoxController = UIHostingController(rootView: AnyView(EmptyView()))
private var accessoryKeyboardConstraint: NSLayoutConstraint?
private var accessoryBottomConstraint: NSLayoutConstraint?
private var isFirstAppearance = true
public func associate(with view: View) {
manager = ModuleViewManager(view: view) { [weak self] viewBody in
self?.update(with: viewBody)
}
}
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
embedSlotControllers()
manager?.updateViewBodyWithObservation()
}
public override func viewIsAppearing(_ animated: Bool) {
super.viewIsAppearing(animated)
if isFirstAppearance {
isFirstAppearance = false
(manager?.view.model as? ModuleViewObserver)?.onFirstViewAppear()
}
}
private func embedSlotControllers() {
for controller in [navigationBarController, contentController, accessoryBoxController] {
addChild(controller)
view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
controller.view.backgroundColor = .clear
controller.didMove(toParent: self)
}
navigationBarController.sizingOptions = .intrinsicContentSize
accessoryBoxController.sizingOptions = .intrinsicContentSize
NSLayoutConstraint.activate([
navigationBarController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
navigationBarController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
navigationBarController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
contentController.view.topAnchor.constraint(equalTo: navigationBarController.view.bottomAnchor),
contentController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
contentController.view.bottomAnchor.constraint(equalTo: accessoryBoxController.view.topAnchor),
accessoryBoxController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
accessoryBoxController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
// Плашка либо прилипает к клавиатуре (без клавиатуры направляющая
// лежит на нижней границе экрана), либо остаётся у нижней границы —
// выбор делает `AccessoryBox.followsKeyboard` при каждом обновлении
accessoryKeyboardConstraint = accessoryBoxController.view.bottomAnchor.constraint(
equalTo: view.keyboardLayoutGuide.topAnchor
)
accessoryBottomConstraint = accessoryBoxController.view.bottomAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.bottomAnchor
)
}
private func update(with viewBody: View.Body) {
if let navigationBar = viewBody.navigationBar {
navigationBarController.rootView = AnyView(NavigationBarView(navigationBar: navigationBar))
} else {
navigationBarController.rootView = AnyView(EmptyView())
}
contentController.rootView = viewBody.content
if let accessoryBox = viewBody.accessoryBox {
accessoryBoxController.rootView = accessoryBox.content
accessoryBoxController.view.isHidden = false
} else {
accessoryBoxController.rootView = AnyView(EmptyView())
accessoryBoxController.view.isHidden = true
}
let followsKeyboard = viewBody.accessoryBox?.followsKeyboard ?? false
accessoryKeyboardConstraint?.isActive = followsKeyboard
accessoryBottomConstraint?.isActive = !followsKeyboard
if let accessibilityKey = viewBody.accessibilityKey {
view.accessibilityIdentifier = String(describing: accessibilityKey)
}
applyBottomSheetSettings(viewBody.bottomSheetSettings)
}
private func applyBottomSheetSettings(_ settings: BottomSheetSettings) {
sheetPresentationController?.detents = settings.detents.map { detent in
switch detent {
case .medium:
.medium()
case .large:
.large()
}
}
}
}