Skip to content

Commit 99f747f

Browse files
authored
Add window introspection (#269)
1 parent f1c20d6 commit 99f747f

File tree

9 files changed

+170
-6
lines changed

9 files changed

+170
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Changelog
33

44
## master
55

6+
- Added: window introspection (#269)
67
- Added: `.sheet` introspection (#268)
78
- Added: `.fullScreenCover` introspection (#268)
89
- Added: `.popover` introspection (#268)

Examples/Showcase/Showcase.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99
/* Begin PBXBuildFile section */
1010
D53071F729983CEF00F1936C /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53071F629983CEF00F1936C /* App.swift */; };
11-
D53071F929983CEF00F1936C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53071F829983CEF00F1936C /* ContentView.swift */; };
11+
D53071F929983CEF00F1936C /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53071F829983CEF00F1936C /* AppView.swift */; };
1212
D5B829752999738200920EBD /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B829742999738200920EBD /* Helpers.swift */; };
1313
D5E3180329C132B6005847DC /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D5E3180229C132B6005847DC /* SwiftUIIntrospect */; };
1414
/* End PBXBuildFile section */
1515

1616
/* Begin PBXFileReference section */
1717
D53071F329983CEF00F1936C /* Showcase.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Showcase.app; sourceTree = BUILT_PRODUCTS_DIR; };
1818
D53071F629983CEF00F1936C /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
19-
D53071F829983CEF00F1936C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
19+
D53071F829983CEF00F1936C /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = "<group>"; };
2020
D530720429983D9300F1936C /* Showcase.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Showcase.entitlements; sourceTree = "<group>"; };
2121
D5B829742999738200920EBD /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
2222
/* End PBXFileReference section */
@@ -55,7 +55,7 @@
5555
children = (
5656
D530720429983D9300F1936C /* Showcase.entitlements */,
5757
D53071F629983CEF00F1936C /* App.swift */,
58-
D53071F829983CEF00F1936C /* ContentView.swift */,
58+
D53071F829983CEF00F1936C /* AppView.swift */,
5959
D5B829742999738200920EBD /* Helpers.swift */,
6060
);
6161
path = Showcase;
@@ -139,7 +139,7 @@
139139
isa = PBXSourcesBuildPhase;
140140
buildActionMask = 2147483647;
141141
files = (
142-
D53071F929983CEF00F1936C /* ContentView.swift in Sources */,
142+
D53071F929983CEF00F1936C /* AppView.swift in Sources */,
143143
D5B829752999738200920EBD /* Helpers.swift in Sources */,
144144
D53071F729983CEF00F1936C /* App.swift in Sources */,
145145
);

Examples/Showcase/Showcase/App.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
88

99
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
1010
window = UIWindow(frame: UIScreen.main.bounds)
11-
window?.rootViewController = UIHostingController(rootView: ContentView())
11+
window?.rootViewController = UIHostingController(rootView: AppView())
1212
window?.makeKeyAndVisible()
1313
return true
1414
}
@@ -18,7 +18,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
1818
struct App: SwiftUI.App {
1919
var body: some Scene {
2020
WindowGroup {
21-
ContentView()
21+
AppView()
2222
}
2323
}
2424
}

Examples/Showcase/Showcase/ContentView.swift renamed to Examples/Showcase/Showcase/AppView.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import SwiftUI
22
import SwiftUIIntrospect
33

4+
struct AppView: View {
5+
var body: some View {
6+
ContentView()
7+
#if os(iOS) || os(tvOS)
8+
.introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17)) { window in
9+
window.backgroundColor = .brown
10+
}
11+
#elseif os(macOS)
12+
.introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { window in
13+
window.backgroundColor = .lightGray
14+
}
15+
#endif
16+
}
17+
}
18+
419
struct ContentView: View {
520
@State var selection = 0
621

Sources/Introspect.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,15 @@ extension UIPresentationController: PlatformEntity {
178178
@_spi(Internals)
179179
public func isDescendant(of other: UIPresentationController) -> Bool { false }
180180
}
181+
#elseif canImport(AppKit)
182+
extension NSWindow: PlatformEntity {
183+
@_spi(Internals)
184+
public var ancestor: NSWindow? { nil }
185+
186+
@_spi(Internals)
187+
public var descendants: [NSWindow] { [] }
188+
189+
@_spi(Internals)
190+
public func isDescendant(of other: NSWindow) -> Bool { false }
191+
}
181192
#endif

Sources/ViewTypes/Window.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import SwiftUI
2+
3+
/// An abstract representation of a view's window in SwiftUI.
4+
///
5+
/// ### iOS
6+
///
7+
/// ```swift
8+
/// struct ContentView: View {
9+
/// var body: some View {
10+
/// Text("Content")
11+
/// .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17)) {
12+
/// print(type(of: $0)) // UIWindow
13+
/// }
14+
/// }
15+
/// }
16+
/// ```
17+
///
18+
/// ### tvOS
19+
///
20+
/// ```swift
21+
/// struct ContentView: View {
22+
/// var body: some View {
23+
/// Text("Content")
24+
/// .introspect(.window, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) {
25+
/// print(type(of: $0)) // UIWindow
26+
/// }
27+
/// }
28+
/// }
29+
/// ```
30+
///
31+
/// ### macOS
32+
///
33+
/// ```swift
34+
/// struct ContentView: View {
35+
/// var body: some View {
36+
/// Text("Content")
37+
/// .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) {
38+
/// print(type(of: $0)) // NSWindow
39+
/// }
40+
/// }
41+
/// }
42+
/// ```
43+
///
44+
public struct WindowType: IntrospectableViewType {}
45+
46+
extension IntrospectableViewType where Self == WindowType {
47+
public static var window: Self { .init() }
48+
}
49+
50+
#if canImport(UIKit)
51+
extension iOSViewVersion<WindowType, UIWindow> {
52+
public static let v13 = Self(for: .v13, selector: selector)
53+
public static let v14 = Self(for: .v14, selector: selector)
54+
public static let v15 = Self(for: .v15, selector: selector)
55+
public static let v16 = Self(for: .v16, selector: selector)
56+
public static let v17 = Self(for: .v17, selector: selector)
57+
58+
private static var selector: IntrospectionSelector<UIWindow> {
59+
.from(UIView.self, selector: \.window)
60+
}
61+
}
62+
63+
extension tvOSViewVersion<WindowType, UIWindow> {
64+
public static let v13 = Self(for: .v13, selector: selector)
65+
public static let v14 = Self(for: .v14, selector: selector)
66+
public static let v15 = Self(for: .v15, selector: selector)
67+
public static let v16 = Self(for: .v16, selector: selector)
68+
public static let v17 = Self(for: .v17, selector: selector)
69+
70+
private static var selector: IntrospectionSelector<UIWindow> {
71+
.from(UIView.self, selector: \.window)
72+
}
73+
}
74+
#elseif canImport(AppKit)
75+
extension macOSViewVersion<WindowType, NSWindow> {
76+
public static let v10_15 = Self(for: .v10_15, selector: selector)
77+
public static let v11 = Self(for: .v11, selector: selector)
78+
public static let v12 = Self(for: .v12, selector: selector)
79+
public static let v13 = Self(for: .v13, selector: selector)
80+
public static let v14 = Self(for: .v14, selector: selector)
81+
82+
private static var selector: IntrospectionSelector<NSWindow> {
83+
.from(NSView.self, selector: \.window)
84+
}
85+
}
86+
#endif

Tests/Tests.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
D50E2F892A2B9F6600BAFB03 /* TextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B67B832A0D318F007D5D9B /* TextFieldTests.swift */; };
5656
D50E2F8B2A2B9F6600BAFB03 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D50E2F5C2A2B9F6600BAFB03 /* SwiftUIIntrospect */; };
5757
D50FFE8E2A17E2A400C32641 /* ScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50FFE8D2A17E2A400C32641 /* ScrollViewTests.swift */; };
58+
D534D4DC2A4A596200218BFB /* WindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534D4DB2A4A596200218BFB /* WindowTests.swift */; };
59+
D534D4DD2A4A596200218BFB /* WindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534D4DB2A4A596200218BFB /* WindowTests.swift */; };
5860
D55F448D2A1FF209003381E4 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55F448C2A1FF209003381E4 /* ListTests.swift */; };
5961
D568532C2A49DBB10039A99F /* SignInWithAppleButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D568532B2A49DBB10039A99F /* SignInWithAppleButtonTests.swift */; };
6062
D57506782A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506772A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift */; };
@@ -142,6 +144,7 @@
142144
D50E2F572A2B9EFB00BAFB03 /* LegacyTestsHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyTestsHostApp.swift; sourceTree = "<group>"; };
143145
D50E2F902A2B9F6600BAFB03 /* LegacyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LegacyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
144146
D50FFE8D2A17E2A400C32641 /* ScrollViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewTests.swift; sourceTree = "<group>"; };
147+
D534D4DB2A4A596200218BFB /* WindowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowTests.swift; sourceTree = "<group>"; };
145148
D55F448C2A1FF209003381E4 /* ListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTests.swift; sourceTree = "<group>"; };
146149
D568532B2A49DBB10039A99F /* SignInWithAppleButtonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInWithAppleButtonTests.swift; sourceTree = "<group>"; };
147150
D57506772A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerWithSegmentedStyleTests.swift; sourceTree = "<group>"; };
@@ -288,6 +291,7 @@
288291
D575068B2A27D40500A628E4 /* ToggleWithSwitchStyleTests.swift */,
289292
D503B2AB2A49BFE300027F5F /* VideoPlayerTests.swift */,
290293
D58119C52A227E930081F853 /* ViewTests.swift */,
294+
D534D4DB2A4A596200218BFB /* WindowTests.swift */,
291295
);
292296
path = ViewTypes;
293297
sourceTree = "<group>";
@@ -516,6 +520,7 @@
516520
D50E2F632A2B9F6600BAFB03 /* TabViewTests.swift in Sources */,
517521
D50E2F642A2B9F6600BAFB03 /* ListWithInsetStyleTests.swift in Sources */,
518522
D50E2F652A2B9F6600BAFB03 /* PickerWithMenuStyleTests.swift in Sources */,
523+
D534D4DD2A4A596200218BFB /* WindowTests.swift in Sources */,
519524
D50E2F662A2B9F6600BAFB03 /* DatePickerWithWheelStyleTests.swift in Sources */,
520525
D50E2F672A2B9F6600BAFB03 /* ListWithInsetGroupedStyleTests.swift in Sources */,
521526
D5ADFAD32A4A4649009494FD /* SignInWithAppleButtonTests.swift in Sources */,
@@ -585,6 +590,7 @@
585590
D568532C2A49DBB10039A99F /* SignInWithAppleButtonTests.swift in Sources */,
586591
D575068A2A27CE7900A628E4 /* FormWithGroupedStyleTests.swift in Sources */,
587592
D575067C2A27C24600A628E4 /* ListWithPlainStyleTests.swift in Sources */,
593+
D534D4DC2A4A596200218BFB /* WindowTests.swift in Sources */,
588594
D58119CA2A239BAC0081F853 /* TextEditorTests.swift in Sources */,
589595
D57506842A27C8D400A628E4 /* ListWithSidebarStyleTests.swift in Sources */,
590596
D575069E2A27F80E00A628E4 /* ProgressViewWithLinearStyleTests.swift in Sources */,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import SwiftUI
2+
import SwiftUIIntrospect
3+
import XCTest
4+
5+
final class WindowTests: XCTestCase {
6+
#if canImport(UIKit)
7+
typealias PlatformWindow = UIWindow
8+
#elseif canImport(AppKit)
9+
typealias PlatformWindow = NSWindow
10+
#endif
11+
12+
func testWindow() {
13+
XCTAssertViewIntrospection(of: PlatformWindow.self) { spies in
14+
let spy0 = spies[0]
15+
let spy1 = spies[1]
16+
let spy2 = spies[2]
17+
18+
VStack {
19+
Image(systemName: "scribble")
20+
#if os(iOS) || os(tvOS)
21+
.introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), customize: spy0)
22+
#elseif os(macOS)
23+
.introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0)
24+
#endif
25+
26+
Text("Text")
27+
#if os(iOS) || os(tvOS)
28+
.introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), customize: spy1)
29+
#elseif os(macOS)
30+
.introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1)
31+
#endif
32+
}
33+
#if os(iOS) || os(tvOS)
34+
.introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), customize: spy2)
35+
#elseif os(macOS)
36+
.introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2)
37+
#endif
38+
} extraAssertions: {
39+
XCTAssertIdentical($0[safe: 0], $0[safe: 1])
40+
XCTAssertIdentical($0[safe: 0], $0[safe: 2])
41+
XCTAssertIdentical($0[safe: 1], $0[safe: 2])
42+
}
43+
}
44+
}

docs/SwiftUIIntrospect.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ Introspection
131131
- [`Toggle` with `switch` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/togglewithswitchstyletype)
132132
- [`VideoPlayer`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/videoplayertype)
133133
- [`View`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/viewtype)
134+
- [`Window`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/windowtype)
134135

135136
**Missing an element?** Please [create an issue](https://github.com/timbersoftware/SwiftUI-Introspect/issues). As a temporary solution, you can [implement your own introspectable view type](#implement-your-own-view-type).
136137

0 commit comments

Comments
 (0)