@@ -23,14 +23,20 @@ import WorkflowUI
23
23
public protocol SwiftUIScreen : Screen {
24
24
associatedtype Content : View
25
25
26
+ var sizingOptions : SwiftUIScreenSizingOptions { get }
27
+
26
28
@ViewBuilder
27
29
static func makeView( model: ObservableValue < Self > ) -> Content
28
30
29
31
static var isEquivalent : ( ( Self , Self ) -> Bool ) ? { get }
30
32
}
31
33
32
34
public extension SwiftUIScreen {
33
- static var isEquivalent : ( ( Self , Self ) -> Bool ) ? { return nil }
35
+ var sizingOptions : SwiftUIScreenSizingOptions { [ ] }
36
+ }
37
+
38
+ public extension SwiftUIScreen {
39
+ static var isEquivalent : ( ( Self , Self ) -> Bool ) ? { nil }
34
40
}
35
41
36
42
public extension SwiftUIScreen where Self: Equatable {
@@ -53,17 +59,29 @@ public extension SwiftUIScreen {
53
59
viewEnvironment: viewEnvironment,
54
60
content: Self . makeView ( model: model)
55
61
)
56
- } )
62
+ } ) ,
63
+ swiftUIScreenSizingOptions: sizingOptions
57
64
)
58
65
} ,
59
66
update: {
60
67
$0. modelSink. send ( self )
61
68
$0. viewEnvironmentSink. send ( environment)
69
+ $0. swiftUIScreenSizingOptions = sizingOptions
62
70
}
63
71
)
64
72
}
65
73
}
66
74
75
+ public struct SwiftUIScreenSizingOptions : OptionSet {
76
+ public let rawValue : Int
77
+
78
+ public init ( rawValue: Int ) {
79
+ self . rawValue = rawValue
80
+ }
81
+
82
+ public static let preferredContentSize : SwiftUIScreenSizingOptions = . init( rawValue: 1 << 0 )
83
+ }
84
+
67
85
private struct EnvironmentInjectingView < Content: View > : View {
68
86
@ObservedObject var viewEnvironment : ObservableValue < ViewEnvironment >
69
87
let content : Content
@@ -77,17 +95,104 @@ private struct EnvironmentInjectingView<Content: View>: View {
77
95
private final class ModeledHostingController < Model, Content: View > : UIHostingController < Content > {
78
96
let modelSink : Sink < Model >
79
97
let viewEnvironmentSink : Sink < ViewEnvironment >
98
+ var swiftUIScreenSizingOptions : SwiftUIScreenSizingOptions {
99
+ didSet {
100
+ updateSizingOptionsIfNeeded ( )
80
101
81
- init ( modelSink: Sink < Model > , viewEnvironmentSink: Sink < ViewEnvironment > , rootView: Content ) {
102
+ if !hasLaidOutOnce {
103
+ setNeedsLayoutBeforeFirstLayoutIfNeeded ( )
104
+ }
105
+ }
106
+ }
107
+
108
+ private var hasLaidOutOnce = false
109
+
110
+ init (
111
+ modelSink: Sink < Model > ,
112
+ viewEnvironmentSink: Sink < ViewEnvironment > ,
113
+ rootView: Content ,
114
+ swiftUIScreenSizingOptions: SwiftUIScreenSizingOptions
115
+ ) {
82
116
self . modelSink = modelSink
83
117
self . viewEnvironmentSink = viewEnvironmentSink
118
+ self . swiftUIScreenSizingOptions = swiftUIScreenSizingOptions
84
119
85
120
super. init ( rootView: rootView)
121
+
122
+ updateSizingOptionsIfNeeded ( )
86
123
}
87
124
88
125
required init ? ( coder aDecoder: NSCoder ) {
89
126
fatalError ( " not implemented " )
90
127
}
128
+
129
+ override func viewDidLoad( ) {
130
+ super. viewDidLoad ( )
131
+
132
+ view. backgroundColor = . clear
133
+
134
+ setNeedsLayoutBeforeFirstLayoutIfNeeded ( )
135
+ }
136
+
137
+ override func viewDidLayoutSubviews( ) {
138
+ super. viewDidLayoutSubviews ( )
139
+
140
+ defer { hasLaidOutOnce = true }
141
+
142
+ if #available( iOS 16 . 0 , * ) {
143
+ // Handled in initializer, but set it on first layout to resolve a bug where the PCS is
144
+ // not updated appropriately after the first layout.
145
+ // UI-5797
146
+ if !hasLaidOutOnce,
147
+ swiftUIScreenSizingOptions. contains ( . preferredContentSize) {
148
+ let size = view. sizeThatFits ( view. frame. size)
149
+
150
+ if preferredContentSize != size {
151
+ preferredContentSize = size
152
+ }
153
+ }
154
+ } else if swiftUIScreenSizingOptions. contains ( . preferredContentSize) {
155
+ let size = view. sizeThatFits ( view. frame. size)
156
+
157
+ if preferredContentSize != size {
158
+ preferredContentSize = size
159
+ }
160
+ }
161
+ }
162
+
163
+ private func updateSizingOptionsIfNeeded( ) {
164
+ if #available( iOS 16 . 0 , * ) {
165
+ self . sizingOptions = swiftUIScreenSizingOptions. uiHostingControllerSizingOptions
166
+ }
167
+
168
+ if !swiftUIScreenSizingOptions. contains ( . preferredContentSize) ,
169
+ preferredContentSize != . zero {
170
+ preferredContentSize = . zero
171
+ }
172
+ }
173
+
174
+ private func setNeedsLayoutBeforeFirstLayoutIfNeeded( ) {
175
+ if swiftUIScreenSizingOptions. contains ( . preferredContentSize) {
176
+ // Without manually calling setNeedsLayout here it was observed that a call to
177
+ // layoutIfNeeded() immediately after loading the view would not perform a layout, and
178
+ // therefore would not update the preferredContentSize in viewDidLayoutSubviews().
179
+ // UI-5797
180
+ view. setNeedsLayout ( )
181
+ }
182
+ }
183
+ }
184
+
185
+ extension SwiftUIScreenSizingOptions {
186
+ @available ( iOS 16 . 0 , * )
187
+ fileprivate var uiHostingControllerSizingOptions : UIHostingControllerSizingOptions {
188
+ var options = UIHostingControllerSizingOptions ( )
189
+
190
+ if contains ( . preferredContentSize) {
191
+ options. insert ( . preferredContentSize)
192
+ }
193
+
194
+ return options
195
+ }
91
196
}
92
197
93
198
#endif
0 commit comments