From 7807a926c9ae2a1181c5a9d185cd1ac3ea045b60 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:39:16 +0100 Subject: [PATCH 1/4] WIP --- Tests/Tests/ViewTypes/ViewTests.swift | 32 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Tests/Tests/ViewTypes/ViewTests.swift b/Tests/Tests/ViewTypes/ViewTests.swift index 884d444e..bb6f86c8 100644 --- a/Tests/Tests/ViewTypes/ViewTests.swift +++ b/Tests/Tests/ViewTypes/ViewTests.swift @@ -8,17 +8,16 @@ final class ViewTests: XCTestCase { XCTAssertViewIntrospection(of: PlatformView.self) { spies in let spy0 = spies[0] let spy1 = spies[1] - let spy2 = spies[2] VStack(spacing: 10) { - Image(systemName: "scribble").resizable().frame(height: 30) + SUTView().frame(height: 30) #if os(iOS) || os(tvOS) || os(visionOS) .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26), .visionOS(.v1, .v2, .v26), customize: spy0) #elseif os(macOS) .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15, .v26), customize: spy0) #endif - Text("Text").frame(height: 40) + SUTView().frame(height: 40) #if os(iOS) || os(tvOS) || os(visionOS) .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26), .visionOS(.v1, .v2, .v26), customize: spy1) #elseif os(macOS) @@ -26,15 +25,30 @@ final class ViewTests: XCTestCase { #endif } .padding(10) - #if os(iOS) || os(tvOS) || os(visionOS) - .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26), .visionOS(.v1, .v2, .v26), customize: spy2) - #elseif os(macOS) - .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15, .v26), customize: spy2) - #endif } extraAssertions: { XCTAssertEqual($0[safe: 0]?.frame.height, 30) XCTAssertEqual($0[safe: 1]?.frame.height, 40) - XCTAssertEqual($0[safe: 2]?.frame.height, 100) } } } + +struct SUTView: UIViewRepresentable { + func makeUIView(context: Context) -> some UIView { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + + let widthConstraint = view.widthAnchor.constraint(greaterThanOrEqualToConstant: .greatestFiniteMagnitude) + widthConstraint.priority = .defaultLow + + let heightConstraint = view.heightAnchor.constraint(greaterThanOrEqualToConstant: .greatestFiniteMagnitude) + heightConstraint.priority = .defaultLow + + NSLayoutConstraint.activate([widthConstraint, heightConstraint]) + + return view + } + + func updateUIView(_ uiView: UIViewType, context: Context) { + // NO-OP + } +} From 8a68a1a846c603b50c0a832b5a3fc04de071617a Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:46:22 +0100 Subject: [PATCH 2/4] WIP --- Sources/ViewTypes/View.swift | 49 ++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/Sources/ViewTypes/View.swift b/Sources/ViewTypes/View.swift index ff3946a6..52d85884 100644 --- a/Sources/ViewTypes/View.swift +++ b/Sources/ViewTypes/View.swift @@ -1,18 +1,20 @@ #if !os(watchOS) /// An abstract representation of a generic SwiftUI view type. /// +/// Note: prior to iOS 26, `Text`, `Image`, and `Button` were drawn inside a subclass of +/// `UIView` called `_UIGraphicsView` which was introspectable via `.introspect(.view)`, +/// however starting iOS 26 this is no longer the case and all SwiftUI primitives +/// seem to somehow be drawn without an underlying `UIView` vessel. +/// /// ### iOS /// /// ```swift /// struct ContentView: View { /// var body: some View { -/// HStack { -/// Image(systemName: "scribble") -/// Text("Some text") -/// } -/// .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26)) { -/// print(type(of: $0)) // some subclass of UIView -/// } +/// ExampleUIViewRepresentable() +/// .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26)) { +/// print(type(of: $0)) // some subclass of UIView +/// } /// } /// } /// ``` @@ -22,13 +24,10 @@ /// ```swift /// struct ContentView: View { /// var body: some View { -/// HStack { -/// Image(systemName: "scribble") -/// Text("Some text") -/// } -/// .introspect(.view, on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26)) { -/// print(type(of: $0)) // some subclass of UIView -/// } +/// ExampleUIViewRepresentable() +/// .introspect(.view, on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18, .v26)) { +/// print(type(of: $0)) // some subclass of UIView +/// } /// } /// } /// ``` @@ -38,13 +37,10 @@ /// ```swift /// struct ContentView: View { /// var body: some View { -/// HStack { -/// Image(systemName: "scribble") -/// Text("Some text") -/// } -/// .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15, .v26)) { -/// print(type(of: $0)) // some subclass of NSView -/// } +/// ExampleUIViewRepresentable() +/// .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15, .v26)) { +/// print(type(of: $0)) // some subclass of NSView +/// } /// } /// } /// ``` @@ -54,13 +50,10 @@ /// ```swift /// struct ContentView: View { /// var body: some View { -/// HStack { -/// Image(systemName: "scribble") -/// Text("Some text") -/// } -/// .introspect(.view, on: .visionOS(.v1, .v2, .v26)) { -/// print(type(of: $0)) // some subclass of UIView -/// } +/// ExampleUIViewRepresentable() +/// .introspect(.view, on: .visionOS(.v1, .v2, .v26)) { +/// print(type(of: $0)) // some subclass of UIView +/// } /// } /// } /// ``` From 779b6ddea6136acc8fd84ad3f6dd76854294cd30 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:55:31 +0100 Subject: [PATCH 3/4] WIP --- Sources/ViewTypes/View.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/ViewTypes/View.swift b/Sources/ViewTypes/View.swift index 52d85884..b9277ebc 100644 --- a/Sources/ViewTypes/View.swift +++ b/Sources/ViewTypes/View.swift @@ -1,10 +1,11 @@ #if !os(watchOS) /// An abstract representation of a generic SwiftUI view type. /// -/// Note: prior to iOS 26, `Text`, `Image`, and `Button` were drawn inside a subclass of -/// `UIView` called `_UIGraphicsView` which was introspectable via `.introspect(.view)`, -/// however starting iOS 26 this is no longer the case and all SwiftUI primitives -/// seem to somehow be drawn without an underlying `UIView` vessel. +/// Note: prior to iOS 26, primitive views like `Text`, `Image`, `Button`, and layout +/// stacks were drawn inside a subclass of `UIView` called `_UIGraphicsView` which was +/// introspectable via `.introspect(.view)`, however starting iOS 26 this is no longer the +/// case and all SwiftUI primitives seem to somehow be drawn without an underlying +/// `UIView` vessel. /// /// ### iOS /// From 2edf3fd8708b14ba013abc008576530f6e5e4c30 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 9 Jul 2025 12:03:30 +0100 Subject: [PATCH 4/4] WIP --- Tests/Tests/ViewTypes/ViewTests.swift | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Tests/Tests/ViewTypes/ViewTests.swift b/Tests/Tests/ViewTypes/ViewTests.swift index bb6f86c8..b5a475d1 100644 --- a/Tests/Tests/ViewTypes/ViewTests.swift +++ b/Tests/Tests/ViewTypes/ViewTests.swift @@ -1,5 +1,5 @@ import SwiftUI -import SwiftUIIntrospect +@testable import SwiftUIIntrospect import XCTest @MainActor @@ -32,23 +32,33 @@ final class ViewTests: XCTestCase { } } -struct SUTView: UIViewRepresentable { - func makeUIView(context: Context) -> some UIView { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false +struct SUTView: PlatformViewControllerRepresentable { + #if canImport(UIKit) + typealias UIViewControllerType = PlatformViewController + #elseif canImport(AppKit) + typealias NSViewControllerType = PlatformViewController + #endif - let widthConstraint = view.widthAnchor.constraint(greaterThanOrEqualToConstant: .greatestFiniteMagnitude) + func makePlatformViewController(context: Context) -> PlatformViewController { + let controller = PlatformViewController(nibName: nil, bundle: nil) + controller.view.translatesAutoresizingMaskIntoConstraints = false + + let widthConstraint = controller.view.widthAnchor.constraint(greaterThanOrEqualToConstant: .greatestFiniteMagnitude) widthConstraint.priority = .defaultLow - let heightConstraint = view.heightAnchor.constraint(greaterThanOrEqualToConstant: .greatestFiniteMagnitude) + let heightConstraint = controller.view.heightAnchor.constraint(greaterThanOrEqualToConstant: .greatestFiniteMagnitude) heightConstraint.priority = .defaultLow NSLayoutConstraint.activate([widthConstraint, heightConstraint]) - return view + return controller + } + + func updatePlatformViewController(_ controller: PlatformViewController, context: Context) { + // NO-OP } - func updateUIView(_ uiView: UIViewType, context: Context) { + static func dismantlePlatformViewController(_ controller: PlatformViewController, coordinator: Coordinator) { // NO-OP } }