@rdlabo/ionic-theme-ios27 1.1.0 → 1.2.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/css/components/ion-list.css +1 -1
- package/dist/css/components/ion-menu.css +1 -1
- package/dist/css/ionic-theme-ios27.css +1 -1
- package/dist/css/vertical-bars.css +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -8
- package/dist/native/components/index.d.ts +1 -0
- package/dist/native/components/index.d.ts.map +1 -1
- package/dist/native/components/index.js +8 -3
- package/dist/native/components/ion-back-button.d.ts.map +1 -1
- package/dist/native/components/ion-back-button.js +5 -2
- package/dist/native/components/ion-button.d.ts.map +1 -1
- package/dist/native/components/ion-button.js +10 -2
- package/dist/native/components/ion-buttons.d.ts.map +1 -1
- package/dist/native/components/ion-buttons.js +12 -4
- package/dist/native/components/ion-fab.js +4 -4
- package/dist/native/components/ion-menu-button.d.ts.map +1 -1
- package/dist/native/components/ion-menu-button.js +5 -4
- package/dist/native/components/searchable-tabs.d.ts.map +1 -1
- package/dist/native/components/searchable-tabs.js +4 -1
- package/dist/native/definitions.d.ts +29 -3
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/definitions.js +6 -1
- package/dist/native/index.d.ts +6 -2
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +154 -17
- package/dist/native/prehide.d.ts +5 -0
- package/dist/native/prehide.d.ts.map +1 -0
- package/dist/native/prehide.js +246 -0
- package/dist/native/runtime.d.ts +1 -1
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +162 -28
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +7 -3
- package/dist/native/shared/dom.d.ts +33 -2
- package/dist/native/shared/dom.d.ts.map +1 -1
- package/dist/native/shared/dom.js +171 -10
- package/dist/native/vertical-bars-web.d.ts +3 -0
- package/dist/native/vertical-bars-web.d.ts.map +1 -0
- package/dist/native/vertical-bars-web.js +341 -0
- package/dist/native-integration/index.d.ts +3 -2
- package/dist/native-integration/index.d.ts.map +1 -1
- package/dist/native-integration/index.js +11 -5
- package/dist/transition/index.d.ts +2 -2
- package/dist/transition/index.d.ts.map +1 -1
- package/dist/transition/index.js +1 -1
- package/dist/transition/ios.transition.d.ts.map +1 -1
- package/dist/transition/ios.transition.js +1 -0
- package/dist/utils.d.ts +3 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/vertical-bars.d.ts +4 -0
- package/dist/vertical-bars.d.ts.map +1 -0
- package/dist/vertical-bars.js +2 -0
- package/docs/iphone-duo.md +255 -0
- package/docs/migration.md +2 -2
- package/docs/native-ui-shell.md +15 -3
- package/docs/special-markup.md +6 -0
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +15 -1
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +358 -0
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +215 -35
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +10 -1
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +29 -0
- package/package.json +6 -2
- package/src/index.ts +15 -8
- package/src/native/components/index.ts +12 -3
- package/src/native/components/ion-back-button.ts +8 -2
- package/src/native/components/ion-button.ts +12 -2
- package/src/native/components/ion-buttons.ts +9 -4
- package/src/native/components/ion-fab.ts +4 -4
- package/src/native/components/ion-menu-button.ts +5 -4
- package/src/native/components/searchable-tabs.ts +9 -1
- package/src/native/definitions.ts +40 -3
- package/src/native/index.ts +188 -20
- package/src/native/prehide.ts +255 -0
- package/src/native/runtime.ts +173 -27
- package/src/native/shared/candidate.ts +11 -3
- package/src/native/shared/dom.ts +212 -11
- package/src/native/vertical-bars-web.ts +374 -0
- package/src/native-integration/index.ts +13 -8
- package/src/styles/components/ion-list.scss +2 -2
- package/src/styles/components/ion-menu.scss +2 -2
- package/src/styles/ionic-theme-ios27.scss +1 -0
- package/src/styles/vertical-bars.scss +342 -0
- package/src/transition/index.ts +11 -6
- package/src/transition/ios.transition.ts +1 -0
- package/src/utils.ts +8 -8
- package/src/vertical-bars.ts +3 -0
- package/dist/native/lifecycle.d.ts +0 -4
- package/dist/native/lifecycle.d.ts.map +0 -1
- package/dist/native/lifecycle.js +0 -34
- package/src/native/lifecycle.ts +0 -38
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import Combine
|
|
2
|
+
import SwiftUI
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
protocol ShellVerticalBarsControlling: AnyObject {
|
|
6
|
+
var view: UIView { get }
|
|
7
|
+
func attach(to owner: UIViewController, in parent: UIView)
|
|
8
|
+
func apply(_ controls: [ShellControl], rendering: ShellRendering, edge: String)
|
|
9
|
+
func detach()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@available(iOS 26.0, *)
|
|
13
|
+
final class ShellVerticalBarsModel: ObservableObject {
|
|
14
|
+
struct Item: Identifiable {
|
|
15
|
+
let id: String
|
|
16
|
+
let label: String
|
|
17
|
+
let accessibilityLabel: String
|
|
18
|
+
let image: UIImage?
|
|
19
|
+
let badge: ShellBadge?
|
|
20
|
+
let disabled: Bool
|
|
21
|
+
var selected: Bool
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
struct Group: Identifiable {
|
|
25
|
+
let id: String
|
|
26
|
+
let items: [Item]
|
|
27
|
+
let slot: ShellControl.ToolbarSlot?
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Published var back: Item?
|
|
31
|
+
@Published var groups: [Group] = []
|
|
32
|
+
@Published var tabs: [Item] = []
|
|
33
|
+
@Published var selection = ""
|
|
34
|
+
var activate: (String) -> Void = { _ in }
|
|
35
|
+
private var domSelection = ""
|
|
36
|
+
private var pendingSelection: ShellTabBar.PendingSelection?
|
|
37
|
+
private var pendingExpiryWork: DispatchWorkItem?
|
|
38
|
+
|
|
39
|
+
func apply(_ controls: [ShellControl], rendering: ShellRendering,
|
|
40
|
+
now: CFAbsoluteTime = CFAbsoluteTimeGetCurrent()) {
|
|
41
|
+
func item(_ source: ShellItem) -> Item {
|
|
42
|
+
Item(id: source.id, label: source.content.label,
|
|
43
|
+
accessibilityLabel: source.content.accessibilityLabel,
|
|
44
|
+
image: rendering.image(source.content), badge: source.content.badge, disabled: source.content.disabled,
|
|
45
|
+
selected: source.content.selected)
|
|
46
|
+
}
|
|
47
|
+
withAnimation(.smooth(duration: 0.3)) {
|
|
48
|
+
back = controls.first(where: { $0.kind == .backButton })?.items.first.map(item)
|
|
49
|
+
groups = controls.compactMap { control in
|
|
50
|
+
guard [.button, .buttons, .menuButton].contains(control.kind) else { return nil }
|
|
51
|
+
return Group(id: control.id, items: control.items.map(item), slot: control.toolbarSlot)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
tabs = controls.first(where: { $0.kind == .tabBar })?.items.map(item) ?? []
|
|
55
|
+
domSelection = tabs.first(where: \.selected)?.id ?? ""
|
|
56
|
+
reconcileSelection(now: now)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func select(_ id: String, now: CFAbsoluteTime = CFAbsoluteTimeGetCurrent(), ttl: CFTimeInterval = 1) {
|
|
60
|
+
guard let item = tabs.first(where: { $0.id == id }), !item.disabled else { return }
|
|
61
|
+
pendingSelection = .init(id: id, until: now + ttl)
|
|
62
|
+
selection = id
|
|
63
|
+
pendingExpiryWork?.cancel()
|
|
64
|
+
let work = DispatchWorkItem { [weak self] in self?.reconcileSelection() }
|
|
65
|
+
pendingExpiryWork = work
|
|
66
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + ttl + 0.02, execute: work)
|
|
67
|
+
activate(id)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private func reconcileSelection(now: CFAbsoluteTime = CFAbsoluteTimeGetCurrent()) {
|
|
71
|
+
guard let pending = pendingSelection else { selection = domSelection; return }
|
|
72
|
+
let item = tabs.first(where: { $0.id == pending.id })
|
|
73
|
+
if item == nil || item!.disabled || now >= pending.until || domSelection == pending.id {
|
|
74
|
+
pendingSelection = nil
|
|
75
|
+
pendingExpiryWork?.cancel()
|
|
76
|
+
pendingExpiryWork = nil
|
|
77
|
+
selection = domSelection
|
|
78
|
+
} else {
|
|
79
|
+
selection = pending.id
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@available(iOS 26.0, *)
|
|
85
|
+
private struct ShellVerticalBarsLabel: View {
|
|
86
|
+
let item: ShellVerticalBarsModel.Item
|
|
87
|
+
|
|
88
|
+
var body: some View {
|
|
89
|
+
if let image = item.image {
|
|
90
|
+
Label {
|
|
91
|
+
Text(item.label.isEmpty ? item.accessibilityLabel : item.label)
|
|
92
|
+
} icon: {
|
|
93
|
+
Image(uiImage: image)
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
Text(item.label)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@available(iOS 26.0, *)
|
|
102
|
+
private func verticalBarsButton(_ item: ShellVerticalBarsModel.Item, model: ShellVerticalBarsModel) -> some View {
|
|
103
|
+
Button { model.activate(item.id) } label: { ShellVerticalBarsLabel(item: item) }
|
|
104
|
+
.disabled(item.disabled)
|
|
105
|
+
.accessibilityLabel(item.accessibilityLabel)
|
|
106
|
+
.accessibilityIdentifier(item.id)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@available(iOS 26.0, *)
|
|
110
|
+
private func verticalBarsBackButton(_ item: ShellVerticalBarsModel.Item, model: ShellVerticalBarsModel) -> some View {
|
|
111
|
+
Button { model.activate(item.id) } label: { Image(systemName: "chevron.backward") }
|
|
112
|
+
.disabled(item.disabled)
|
|
113
|
+
.accessibilityLabel(item.accessibilityLabel)
|
|
114
|
+
.accessibilityIdentifier("BackButton")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@available(iOS 26.0, *)
|
|
118
|
+
private struct ShellVerticalBarsBadge: ViewModifier {
|
|
119
|
+
let badge: ShellBadge?
|
|
120
|
+
|
|
121
|
+
@ViewBuilder func body(content: Content) -> some View {
|
|
122
|
+
if let badge { content.badge(badge.value) }
|
|
123
|
+
else { content }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@available(iOS 26.0, *)
|
|
128
|
+
private struct ShellVerticalBarsView: View {
|
|
129
|
+
@ObservedObject var model: ShellVerticalBarsModel
|
|
130
|
+
|
|
131
|
+
var body: some View {
|
|
132
|
+
Group {
|
|
133
|
+
if model.tabs.isEmpty {
|
|
134
|
+
ShellVerticalBarsPage(model: model)
|
|
135
|
+
} else {
|
|
136
|
+
TabView(selection: Binding(get: { model.selection }, set: { model.select($0) })) {
|
|
137
|
+
ForEach(model.tabs) { item in
|
|
138
|
+
ShellVerticalBarsPage(model: model)
|
|
139
|
+
.tag(item.id)
|
|
140
|
+
.tabItem { ShellVerticalBarsLabel(item: item) }
|
|
141
|
+
.modifier(ShellVerticalBarsBadge(badge: item.badge))
|
|
142
|
+
.disabled(item.disabled)
|
|
143
|
+
.accessibilityLabel(item.accessibilityLabel)
|
|
144
|
+
.accessibilityIdentifier(item.id)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
.modifier(ShellVerticalBarsCompression())
|
|
150
|
+
.background(Color.clear)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@available(iOS 26.0, *)
|
|
155
|
+
private struct ShellVerticalBarsPage: View {
|
|
156
|
+
@ObservedObject var model: ShellVerticalBarsModel
|
|
157
|
+
|
|
158
|
+
var body: some View {
|
|
159
|
+
NavigationStack {
|
|
160
|
+
Color.clear.modifier(ShellVerticalBarsToolbarAdapter(model: model))
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@available(iOS 26.0, *)
|
|
166
|
+
private struct ShellVerticalBarsCompression: ViewModifier {
|
|
167
|
+
@ViewBuilder func body(content: Content) -> some View {
|
|
168
|
+
#if canImport(UIKit, _underlyingVersion: 9127.0.85) && !targetEnvironment(macCatalyst)
|
|
169
|
+
if #available(iOS 27.1, *) {
|
|
170
|
+
content.toolbarVerticalCompressionBehavior(.prefersToolbarItems)
|
|
171
|
+
} else {
|
|
172
|
+
content
|
|
173
|
+
}
|
|
174
|
+
#else
|
|
175
|
+
content
|
|
176
|
+
#endif
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@available(iOS 26.0, *)
|
|
181
|
+
private struct ShellVerticalBarsToolbarAdapter: ViewModifier {
|
|
182
|
+
@ObservedObject var model: ShellVerticalBarsModel
|
|
183
|
+
|
|
184
|
+
@ViewBuilder func body(content: Content) -> some View {
|
|
185
|
+
#if canImport(UIKit, _underlyingVersion: 9127.0.85) && !targetEnvironment(macCatalyst)
|
|
186
|
+
if #available(iOS 27.1, *) {
|
|
187
|
+
content.modifier(ShellVerticalBarsToolbar(model: model))
|
|
188
|
+
} else {
|
|
189
|
+
content.modifier(ShellVerticalBarsLegacyToolbar(model: model))
|
|
190
|
+
}
|
|
191
|
+
#else
|
|
192
|
+
content.modifier(ShellVerticalBarsLegacyToolbar(model: model))
|
|
193
|
+
#endif
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#if canImport(UIKit, _underlyingVersion: 9127.0.85) && !targetEnvironment(macCatalyst)
|
|
198
|
+
@available(iOS 27.1, *)
|
|
199
|
+
private struct ShellVerticalBarsToolbar: ViewModifier {
|
|
200
|
+
@ObservedObject var model: ShellVerticalBarsModel
|
|
201
|
+
|
|
202
|
+
func body(content: Content) -> some View {
|
|
203
|
+
content.toolbar {
|
|
204
|
+
if let back = model.back {
|
|
205
|
+
ToolbarItem(placement: .navigation) {
|
|
206
|
+
verticalBarsBackButton(back, model: model)
|
|
207
|
+
}
|
|
208
|
+
.axisBehavior(.verticalPreferred)
|
|
209
|
+
}
|
|
210
|
+
ForEach(model.groups.filter { $0.slot == .start }) { group in
|
|
211
|
+
ToolbarItemGroup(placement: .topBarLeading) {
|
|
212
|
+
ForEach(group.items) { item in
|
|
213
|
+
verticalBarsButton(item, model: model)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
.axisBehavior(.verticalPreferred)
|
|
217
|
+
}
|
|
218
|
+
ForEach(model.groups.filter { $0.slot != .start }) { group in
|
|
219
|
+
ToolbarItemGroup(placement: .topBarTrailing) {
|
|
220
|
+
ForEach(group.items) { item in
|
|
221
|
+
verticalBarsButton(item, model: model)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
.axisBehavior(.verticalPreferred)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
#endif
|
|
230
|
+
|
|
231
|
+
@available(iOS 26.0, *)
|
|
232
|
+
private struct ShellVerticalBarsLegacyToolbar: ViewModifier {
|
|
233
|
+
@ObservedObject var model: ShellVerticalBarsModel
|
|
234
|
+
|
|
235
|
+
func body(content: Content) -> some View {
|
|
236
|
+
content.toolbar {
|
|
237
|
+
if let back = model.back {
|
|
238
|
+
ToolbarItem(placement: .navigation) {
|
|
239
|
+
verticalBarsBackButton(back, model: model)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
ForEach(model.groups) { group in
|
|
243
|
+
ToolbarItemGroup(placement: .primaryAction) {
|
|
244
|
+
ForEach(group.items) { item in
|
|
245
|
+
verticalBarsButton(item, model: model)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@available(iOS 26.0, *)
|
|
254
|
+
final class ShellVerticalBarsController: ShellVerticalBarsControlling {
|
|
255
|
+
private final class TransparentHostingController<Content: View>: UIHostingController<Content> {
|
|
256
|
+
var railEdge = "right"
|
|
257
|
+
override func viewDidLayoutSubviews() {
|
|
258
|
+
super.viewDidLayoutSubviews()
|
|
259
|
+
makeFullSizeSurfacesTransparent(in: view)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private func makeFullSizeSurfacesTransparent(in surface: UIView) {
|
|
263
|
+
let frame = surface.convert(surface.bounds, to: view)
|
|
264
|
+
guard !(surface is UIVisualEffectView) else { return }
|
|
265
|
+
let coversHost = frame.insetBy(dx: -1, dy: -1).contains(view.bounds)
|
|
266
|
+
// SwiftUI may add an opaque backing behind the rail controls, sized to
|
|
267
|
+
// the rail or to an expanded tab bar. Keep that backing clear without
|
|
268
|
+
// touching the glass controls or materials.
|
|
269
|
+
let coversRail = frame.minY <= 1 && frame.maxY >= view.bounds.maxY - 1 &&
|
|
270
|
+
(railEdge == "left" ? frame.minX <= 1 : frame.maxX >= view.bounds.maxX - 1)
|
|
271
|
+
if coversHost || coversRail {
|
|
272
|
+
surface.backgroundColor = .clear
|
|
273
|
+
surface.isOpaque = false
|
|
274
|
+
}
|
|
275
|
+
surface.subviews.forEach(makeFullSizeSurfacesTransparent)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private final class RailContainer: UIView {
|
|
280
|
+
var railEdge = "right"
|
|
281
|
+
|
|
282
|
+
private var railWidth: CGFloat {
|
|
283
|
+
let inset = railEdge == "left" ? safeAreaInsets.left : safeAreaInsets.right
|
|
284
|
+
return inset > 0 ? inset : 80
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// The rail is not masked: expanded rail content (a widened tab bar) is
|
|
288
|
+
// allowed to draw over the WebView. Inside the base rail touches behave
|
|
289
|
+
// as before; beyond it touches are captured only where they land inside
|
|
290
|
+
// an actual bar surface so empty overlap still belongs to the WebView.
|
|
291
|
+
// The SwiftUI hosting scaffold reports a full-size hosting view for
|
|
292
|
+
// every point, so the hit result cannot tell bar content from empty
|
|
293
|
+
// space — the bar frames decide instead.
|
|
294
|
+
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
295
|
+
let hit = super.hitTest(point, with: event)
|
|
296
|
+
let inRail = railEdge == "left" ? point.x <= railWidth : point.x >= bounds.maxX - railWidth
|
|
297
|
+
if inRail { return hit }
|
|
298
|
+
guard hit != nil, containsBarSurface(at: point) else { return nil }
|
|
299
|
+
return hit
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func containsBarSurface(at point: CGPoint) -> Bool {
|
|
303
|
+
containsBarSurface(in: self, at: point)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private func containsBarSurface(in view: UIView, at point: CGPoint) -> Bool {
|
|
307
|
+
guard !view.isHidden, view.alpha > 0.05 else { return false }
|
|
308
|
+
let name = NSStringFromClass(type(of: view))
|
|
309
|
+
if (name.contains("TabBar") || name.contains("Platter") || name.contains("Pocket") || name.contains("Sidebar")),
|
|
310
|
+
convert(view.bounds, from: view).contains(point) { return true }
|
|
311
|
+
return view.subviews.contains { containsBarSurface(in: $0, at: point) }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private let model = ShellVerticalBarsModel()
|
|
316
|
+
private lazy var controller = TransparentHostingController(rootView: ShellVerticalBarsView(model: model))
|
|
317
|
+
private let container = RailContainer()
|
|
318
|
+
private weak var owner: UIViewController?
|
|
319
|
+
|
|
320
|
+
var view: UIView { container }
|
|
321
|
+
|
|
322
|
+
init(activate: @escaping (String) -> Void) {
|
|
323
|
+
model.activate = activate
|
|
324
|
+
container.backgroundColor = .clear
|
|
325
|
+
controller.view.backgroundColor = .clear
|
|
326
|
+
controller.view.isOpaque = false
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
func attach(to owner: UIViewController, in parent: UIView) {
|
|
330
|
+
guard self.owner !== owner || container.superview !== parent else { return }
|
|
331
|
+
detach()
|
|
332
|
+
self.owner = owner
|
|
333
|
+
owner.addChild(controller)
|
|
334
|
+
container.frame = parent.bounds
|
|
335
|
+
container.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
336
|
+
controller.view.frame = container.bounds
|
|
337
|
+
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
338
|
+
container.addSubview(controller.view)
|
|
339
|
+
parent.addSubview(container)
|
|
340
|
+
controller.didMove(toParent: owner)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
func apply(_ controls: [ShellControl], rendering: ShellRendering, edge: String) {
|
|
344
|
+
container.railEdge = edge
|
|
345
|
+
controller.railEdge = edge
|
|
346
|
+
model.apply(controls, rendering: rendering)
|
|
347
|
+
controller.overrideUserInterfaceStyle = controls.contains(where: \.dark) ? .dark : .light
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
func detach() {
|
|
351
|
+
guard controller.parent != nil else { return }
|
|
352
|
+
controller.willMove(toParent: nil)
|
|
353
|
+
container.removeFromSuperview()
|
|
354
|
+
controller.view.removeFromSuperview()
|
|
355
|
+
controller.removeFromParent()
|
|
356
|
+
owner = nil
|
|
357
|
+
}
|
|
358
|
+
}
|