Skip to content

Commit e6300d9

Browse files
committed
Update UIHostingView
1 parent f04794e commit e6300d9

File tree

7 files changed

+456
-267
lines changed

7 files changed

+456
-267
lines changed

Sources/OpenSwiftUI/Integration/Hosting/UIKit/Controller/UIHostingController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ open class UIHostingController<Content>: UIViewController where Content : View {
9999
func _viewSafeAreaDidChange() {
100100
_openSwiftUIUnimplementedWarning()
101101
}
102+
103+
func didRender() {
104+
_openSwiftUIUnimplementedWarning()
105+
}
102106
}
103107

104108
@available(macOS, unavailable)

Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIViewControllerProvider.swift renamed to Sources/OpenSwiftUI/Integration/Hosting/UIKit/Controller/UIViewControllerProvider.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
// Status: Complete
77

88
#if os(iOS) || os(visionOS)
9+
public import UIKit
10+
import COpenSwiftUI
911

10-
import UIKit
12+
// MARK: - UIViewControllerProvider
1113

12-
protocol UIViewControllerProvider: AnyObject {
14+
package protocol UIViewControllerProvider: AnyObject {
1315
var uiViewController: UIViewController? { get }
1416
}
1517

1618
extension UIViewControllerProvider {
17-
var containingViewController: UIViewController? {
19+
package var containingViewController: UIViewController? {
1820
if let uiViewController {
1921
return uiViewController
2022
} else if let view = self as? UIView {
@@ -24,5 +26,4 @@ extension UIViewControllerProvider {
2426
}
2527
}
2628
}
27-
2829
#endif
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
//
2+
// UIHostingView+Render.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: WIP
7+
8+
#if os(iOS) || os(visionOS)
9+
public import UIKit
10+
@_spi(ForOpenSwiftUIOnly)
11+
@_spi(Private)
12+
public import OpenSwiftUICore
13+
14+
// MARK: - _UIHostingView + UIViewControllerProvider
15+
16+
extension _UIHostingView: UIViewControllerProvider {
17+
package var uiViewController: UIViewController? {
18+
viewController
19+
}
20+
}
21+
22+
// MARK: - _UIHostingView + UIHostingViewBaseDelegate
23+
24+
extension _UIHostingView: UIHostingViewBaseDelegate {
25+
package var shouldDisableUIKitAnimations: Bool {
26+
guard allowUIKitAnimations == 0,
27+
!base.allowUIKitAnimationsForNextUpdate,
28+
!isInSizeTransition,
29+
!isResizingSheet,
30+
!isRotatingWindow,
31+
!isTabSidebarMorphing
32+
else {
33+
return false
34+
}
35+
return true
36+
}
37+
38+
package func sceneActivationStateDidChange() {
39+
_openSwiftUIUnimplementedWarning()
40+
}
41+
}
42+
43+
// MARK: - _UIHostingView + HostingViewProtocol
44+
45+
@_spi(Private)
46+
extension _UIHostingView: HostingViewProtocol {
47+
public func convertAnchor<Value>(_ anchor: Anchor<Value>) -> Value {
48+
anchor.convert(to: viewGraph.transform)
49+
}
50+
}
51+
52+
// MARK: - _UIHostingView + TestHost [6.4.41]
53+
54+
extension _UIHostingView: TestHost {
55+
package func setTestSize(_ size: CGSize) {
56+
let newSize: CGSize
57+
if size == CGSize.deviceSize {
58+
let screenSize = UIDevice.current.screenSize
59+
let idiom = UIDevice.current.userInterfaceIdiom
60+
if idiom == .pad, screenSize.width < screenSize.height {
61+
newSize = CGSize(width: screenSize.height, height: screenSize.width)
62+
} else {
63+
if idiom == .phone, screenSize.height < screenSize.width {
64+
newSize = CGSize(width: screenSize.height, height: screenSize.width)
65+
} else {
66+
newSize = screenSize
67+
}
68+
}
69+
} else {
70+
newSize = size
71+
}
72+
if bounds.size != newSize {
73+
allowFrameChanges = true
74+
bounds.size = newSize
75+
allowFrameChanges = false
76+
}
77+
}
78+
79+
package func setTestSafeAreaInsets(_ insets: EdgeInsets) {
80+
explicitSafeAreaInsets = insets
81+
82+
}
83+
84+
package var testSize: CGSize { bounds.size }
85+
86+
package var viewCacheIsEmpty: Bool {
87+
Update.locked {
88+
renderer.viewCacheIsEmpty
89+
}
90+
}
91+
92+
package func forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) {
93+
let tree = preferenceValue(_IdentifiedViewsKey.self)
94+
tree.forEach { proxy in
95+
var proxy = proxy
96+
proxy.adjustment = { [weak self] rect in
97+
guard let self else { return }
98+
rect = convert(rect, from: nil)
99+
}
100+
body(proxy)
101+
}
102+
}
103+
104+
package func forEachDescendantHost(body: (any TestHost) -> Void) {
105+
forEachDescendantHost { (view: UIView) in
106+
if let testHost = view as? any TestHost {
107+
body(testHost)
108+
}
109+
}
110+
}
111+
112+
package func renderForTest(interval: Double) {
113+
_renderForTest(interval: interval)
114+
}
115+
116+
package var attributeCountInfo: AttributeCountTestInfo {
117+
preferenceValue(AttributeCountInfoKey.self)
118+
}
119+
120+
public func _renderForTest(interval: Double) {
121+
func shouldContinue() -> Bool {
122+
if propertiesNeedingUpdate == [], !CoreTesting.needsRender {
123+
false
124+
} else {
125+
times >= 0
126+
}
127+
}
128+
advanceTimeForTest(interval: interval)
129+
_base.canAdvanceTimeAutomatically = false
130+
var times = 16
131+
repeat {
132+
times -= 1
133+
CoreTesting.needsRender = false
134+
updateGraph { host in
135+
host.flushTransactions()
136+
}
137+
RunLoop.flushObservers()
138+
render(targetTimestamp: nil)
139+
CATransaction.flush()
140+
} while shouldContinue()
141+
CoreTesting.needsRender = false
142+
_base.canAdvanceTimeAutomatically = true
143+
}
144+
}
145+
146+
extension UIDevice {
147+
package var screenSize: CGSize {
148+
#if !os(visionOS) || OPENSWIFTUI_INTERNAL_XR_SDK
149+
let screenBounds = UIScreen.main.bounds
150+
let screenWidth = screenBounds.width
151+
let screenHeight = screenBounds.height
152+
let orientation = UIDevice.current.orientation
153+
let finalWidth: CGFloat
154+
let finalHeight: CGFloat
155+
switch orientation {
156+
case .landscapeLeft, .landscapeRight:
157+
// In landscape, swap dimensions to ensure width > height
158+
finalWidth = max(screenWidth, screenHeight)
159+
finalHeight = min(screenWidth, screenHeight)
160+
case .portrait, .portraitUpsideDown:
161+
// In portrait, keep original dimensions (height > width)
162+
finalWidth = screenWidth
163+
finalHeight = screenHeight
164+
default:
165+
// For other orientations, keep original dimensions
166+
finalWidth = screenWidth
167+
finalHeight = screenHeight
168+
}
169+
return CGSize(width: finalWidth, height: finalHeight)
170+
#else
171+
return .zero
172+
#endif
173+
}
174+
}
175+
176+
extension UIView {
177+
func forEachDescendantHost(body: (UIView) -> Void) {
178+
body(self)
179+
for view in subviews {
180+
view.forEachDescendantHost(body: body)
181+
}
182+
}
183+
}
184+
185+
// MARK: - _UIHostingView + ViewRendererHost [WIP]
186+
187+
extension _UIHostingView: ViewRendererHost {
188+
// MARK: - GraphDelegate conformance
189+
190+
@_spi(ForOpenSwiftUIOnly)
191+
public func preferencesDidChange() {
192+
_openSwiftUIUnimplementedWarning()
193+
}
194+
195+
@_spi(ForOpenSwiftUIOnly)
196+
@available(OpenSwiftUI_v6_0, *)
197+
public func beginTransaction() {
198+
onMainThread { [weak self] in
199+
// TODO: UIKitUpdateCycle
200+
}
201+
}
202+
203+
// MARK: - ViewGraphDelegate conforamnce
204+
205+
package func `as`<T>(_ type: T.Type) -> T? {
206+
guard let value = base.as(type) else {
207+
// TODO: FocuHost, PlatformItemListHost
208+
if UIViewControllerProvider.self == type {
209+
return unsafeBitCast(self as any UIViewControllerProvider, to: T.self)
210+
// TODO: PointerHost, WindowLayoutHost,
211+
} else if UIView.self == type {
212+
return unsafeBitCast(self as UIView, to: T.self)
213+
// TODO: CurrentEventProvider, FallbackResponderProvider, ContainerBackgroundHost, RootTransformUpdater
214+
} else if ViewRendererHost.self == type {
215+
return unsafeBitCast(self as any ViewRendererHost, to: T.self)
216+
// TODO: ViewGraphRenderObserver, ToolbarInputFeatureDelegate
217+
} else {
218+
return nil
219+
}
220+
}
221+
return value
222+
}
223+
224+
package func requestUpdate(after delay: Double) {
225+
base.requestUpdate(after: delay)
226+
}
227+
228+
// MARK: - ViewRendererHost conformance
229+
230+
package func updateRootView() {
231+
let rootView = makeRootView()
232+
viewGraph.setRootView(rootView)
233+
}
234+
235+
package func updateEnvironment() {
236+
var environment = base.startUpdateEnvironment()
237+
// WIP
238+
_openSwiftUIUnimplementedWarning()
239+
environment.displayScale = traitCollection.displayScale
240+
if let displayGamut = DisplayGamut(rawValue: traitCollection.displayGamut.rawValue) {
241+
environment.displayGamut = displayGamut
242+
}
243+
environment.feedbackCache = feedbackCache
244+
viewGraph.setEnvironment(environment)
245+
}
246+
247+
package func updateTransform() {
248+
_openSwiftUIUnimplementedWarning()
249+
}
250+
251+
package func updateSize() {
252+
base.updateSize()
253+
}
254+
255+
package func updateSafeArea() {
256+
let changed = viewGraph.setSafeAreaInsets(hostSafeAreaElements)
257+
if changed {
258+
invalidateIntrinsicContentSize()
259+
}
260+
}
261+
262+
package func updateContainerSize() {
263+
base.updateContainerSize()
264+
}
265+
266+
package func updateFocusStore() {
267+
_openSwiftUIUnimplementedWarning()
268+
}
269+
270+
package func updateFocusedItem() {
271+
_openSwiftUIUnimplementedWarning()
272+
}
273+
274+
package func updateFocusedValues() {
275+
_openSwiftUIUnimplementedWarning()
276+
}
277+
278+
package func updateAccessibilityEnvironment() {
279+
_openSwiftUIUnimplementedWarning()
280+
}
281+
}
282+
283+
// MARK: - _UIHostingView + EventGraphHost
284+
285+
extension _UIHostingView: EventGraphHost {
286+
package var eventBindingManager: EventBindingManager {
287+
base.eventBindingManager
288+
}
289+
290+
package var focusedResponder: ResponderNode? {
291+
responderNode
292+
}
293+
}
294+
295+
// MARK: - _UIHostingView + ViewGraphRenderObserver
296+
297+
extension _UIHostingView: ViewGraphRenderObserver {
298+
package func didRender() {
299+
viewController?.didRender()
300+
}
301+
}
302+
303+
// MARK: - _UIHostingView + UIKitAnimationCooperating
304+
305+
package protocol UIKitAnimationCooperating {
306+
func beginAllowUIKitAnimations()
307+
func endAllowUIKitAnimations()
308+
}
309+
310+
extension _UIHostingView {
311+
package func beginAllowUIKitAnimations() {
312+
allowUIKitAnimations &+= 1
313+
}
314+
315+
package func endAllowUIKitAnimations() {
316+
allowUIKitAnimations = max(allowUIKitAnimations - 1, 0)
317+
}
318+
}
319+
320+
// MARK: - _UIHostingView + RootTransformProvider [WIP]
321+
322+
extension _UIHostingView: RootTransformProvider {
323+
package func rootTransform() -> ViewTransform {
324+
_openSwiftUIUnimplementedWarning()
325+
return .init()
326+
}
327+
}
328+
329+
// MARK: - _UIHostingView + Alignment
330+
331+
extension _UIHostingView {
332+
333+
@_spi(Private)
334+
@available(OpenSwiftUI_v6_0, *)
335+
@available(macOS, unavailable)
336+
public func horizontalAlignment(_ guide: HorizontalAlignment) -> CGFloat {
337+
alignment(of: guide, at: bounds.size)
338+
}
339+
340+
@_spi(Private)
341+
@available(OpenSwiftUI_v6_0, *)
342+
@available(macOS, unavailable)
343+
public func verticalAlignment(_ guide: VerticalAlignment) -> CGFloat {
344+
alignment(of: guide, at: bounds.size)
345+
}
346+
}
347+
348+
// MARK: - _makeUIHostingView
349+
350+
@available(OpenSwiftUI_v2_0, *)
351+
@available(iOS, unavailable)
352+
@available(macOS, unavailable)
353+
@available(tvOS, unavailable)
354+
@available(visionOS, unavailable)
355+
public func _makeUIHostingView<Content>(_ view: Content) -> NSObject where Content: View {
356+
_UIHostingView(rootView: view)
357+
}
358+
359+
#endif
360+

0 commit comments

Comments
 (0)