├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ ├── cd.yml │ └── ci.yml ├── .gitignore ├── .spi.yml ├── Examples ├── .swiftpm │ └── xcode │ │ └── package.xcworkspace │ │ └── contents.xcworkspacedata ├── Package.swift └── Showcase │ ├── Showcase.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Showcase.xcscheme │ └── Showcase │ ├── App.swift │ ├── AppView.swift │ ├── Helpers.swift │ └── Showcase.entitlements ├── LICENSE ├── Package.swift ├── Package@swift-6.0.swift ├── README.md ├── Sources ├── Introspect.swift ├── IntrospectableViewType.swift ├── IntrospectionSelector.swift ├── IntrospectionView.swift ├── PlatformVersion.swift ├── PlatformView.swift ├── PlatformViewVersion.swift ├── Utils.swift ├── ViewTypes │ ├── Button.swift │ ├── ColorPicker.swift │ ├── DatePicker.swift │ ├── DatePickerWithCompactStyle.swift │ ├── DatePickerWithFieldStyle.swift │ ├── DatePickerWithGraphicalStyle.swift │ ├── DatePickerWithStepperFieldStyle.swift │ ├── DatePickerWithWheelStyle.swift │ ├── Form.swift │ ├── FormWithGroupedStyle.swift │ ├── FullScreenCover.swift │ ├── List.swift │ ├── ListCell.swift │ ├── ListWithBorderedStyle.swift │ ├── ListWithGroupedStyle.swift │ ├── ListWithInsetGroupedStyle.swift │ ├── ListWithInsetStyle.swift │ ├── ListWithSidebarStyle.swift │ ├── Map.swift │ ├── NavigationSplitView.swift │ ├── NavigationStack.swift │ ├── NavigationViewWithColumnsStyle.swift │ ├── NavigationViewWithStackStyle.swift │ ├── PageControl.swift │ ├── PickerWithMenuStyle.swift │ ├── PickerWithSegmentedStyle.swift │ ├── PickerWithWheelStyle.swift │ ├── Popover.swift │ ├── ProgressViewWithCircularStyle.swift │ ├── ProgressViewWithLinearStyle.swift │ ├── ScrollView.swift │ ├── SearchField.swift │ ├── SecureField.swift │ ├── Sheet.swift │ ├── SignInWithAppleButton.swift │ ├── Slider.swift │ ├── Stepper.swift │ ├── TabView.swift │ ├── TabViewWithPageStyle.swift │ ├── Table.swift │ ├── TextEditor.swift │ ├── TextField.swift │ ├── TextFieldWithVerticalAxis.swift │ ├── Toggle.swift │ ├── ToggleWithButtonStyle.swift │ ├── ToggleWithCheckboxStyle.swift │ ├── ToggleWithSwitchStyle.swift │ ├── VideoPlayer.swift │ ├── View.swift │ ├── ViewController.swift │ └── Window.swift └── Weak.swift ├── SwiftUIIntrospect.podspec ├── SwiftUIIntrospect.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ ├── WorkspaceSettings.xcsettings │ ├── swiftpm │ └── Package.resolved │ └── xcschemes │ └── SwiftUIIntrospect.xcscheme ├── Tests ├── LegacyTestsHostApp │ ├── LaunchScreen.storyboard │ └── LegacyTestsHostApp.swift ├── Package.swift ├── Tests.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── LegacySwiftUIIntrospectTests.xcscheme │ │ ├── SwiftUIIntrospectTests.xcscheme │ │ └── SwiftUIIntrospectUITests.xcscheme ├── Tests │ ├── PlatformVersionTests.swift │ ├── TestUtils.swift │ ├── ViewTypes │ │ ├── ButtonTests.swift │ │ ├── ColorPickerTests.swift │ │ ├── DatePickerTests.swift │ │ ├── DatePickerWithCompactFieldStyleTests.swift │ │ ├── DatePickerWithFieldStyleTests.swift │ │ ├── DatePickerWithGraphicalStyleTests.swift │ │ ├── DatePickerWithStepperFieldStyleTests.swift │ │ ├── DatePickerWithWheelStyleTests.swift │ │ ├── FormTests.swift │ │ ├── FormWithGroupedStyleTests.swift │ │ ├── FullScreenCoverTests.swift │ │ ├── ListCellTests.swift │ │ ├── ListTests.swift │ │ ├── ListWithBorderedStyleTests.swift │ │ ├── ListWithGroupedStyleTests.swift │ │ ├── ListWithInsetGroupedStyleTests.swift │ │ ├── ListWithInsetStyleTests.swift │ │ ├── ListWithPlainStyleTests.swift │ │ ├── ListWithSidebarStyleTests.swift │ │ ├── MapTests.swift │ │ ├── NavigationSplitViewTests.swift │ │ ├── NavigationStackTests.swift │ │ ├── NavigationViewWithColumnsStyleTests.swift │ │ ├── NavigationViewWithStackStyleTests.swift │ │ ├── PageControlTests.swift │ │ ├── PickerWithMenuStyleTests.swift │ │ ├── PickerWithSegmentedStyleTests.swift │ │ ├── PickerWithWheelStyleTests.swift │ │ ├── PopoverTests.swift │ │ ├── ProgressViewWithCircularStyleTests.swift │ │ ├── ProgressViewWithLinearStyleTests.swift │ │ ├── ScrollViewTests.swift │ │ ├── SearchFieldTests.swift │ │ ├── SecureFieldTests.swift │ │ ├── SheetTests.swift │ │ ├── SliderTests.swift │ │ ├── StepperTests.swift │ │ ├── TabViewTests.swift │ │ ├── TabViewWithPageStyleTests.swift │ │ ├── TableTests.swift │ │ ├── TextEditorTests.swift │ │ ├── TextFieldTests.swift │ │ ├── TextFieldWithVerticalAxisTests.swift │ │ ├── ToggleTests.swift │ │ ├── ToggleWithButtonStyleTests.swift │ │ ├── ToggleWithCheckboxStyleTests.swift │ │ ├── ToggleWithSwitchStyleTests.swift │ │ ├── VideoPlayerTests.swift │ │ ├── ViewControllerTests.swift │ │ ├── ViewTests.swift │ │ └── WindowTests.swift │ └── WeakTests.swift ├── TestsHostApp │ └── TestsHostApp.swift ├── UITests │ ├── StatusBarStyleUITests.swift │ ├── UITestCase.swift │ ├── UITests.xctestplan │ └── __Snapshots__ │ │ └── StatusBarStyleUITests │ │ ├── test.ipad-ios-13-screenshot-1.png │ │ ├── test.ipad-ios-14-screenshot-1.png │ │ ├── test.ipad-ios-15-screenshot-1.png │ │ ├── test.ipad-ios-16-screenshot-1.png │ │ ├── test.iphone-ios-13-screenshot-1.png │ │ ├── test.iphone-ios-14-screenshot-1.png │ │ ├── test.iphone-ios-15-screenshot-1.png │ │ └── test.iphone-ios-16-screenshot-1.png └── UITestsHostApp │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── StatusBarStyle │ ├── HostingController.swift │ ├── NavigationView.swift │ └── RootView.swift │ ├── TestCases.swift │ └── UITestsHostApp.swift └── fastlane └── Fastfile /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Something isn't working as expected 3 | labels: [bug] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for contributing to SwiftUI Introspect! 9 | 10 | Before you submit your issue, please complete each text area below with the relevant details for your bug, and complete the steps in the checklist. 11 | - type: textarea 12 | attributes: 13 | label: Description 14 | description: | 15 | A short description of the incorrect behavior. 16 | 17 | If you think this issue has been recently introduced and did not occur in an earlier version, please note that. If possible, include the last version that the behavior was correct in addition to your current version. 18 | validations: 19 | required: true 20 | - type: checkboxes 21 | attributes: 22 | label: Checklist 23 | options: 24 | - label: I have read the [README](https://github.com/siteline/swiftui-introspect#swiftui-introspect) before submitting this report. 25 | required: true 26 | - label: This issue hasn't been addressed in an [existing GitHub issue](https://github.com/siteline/swiftui-introspect/issues) or [discussion](https://github.com/siteline/swiftui-introspect/discussions). 27 | required: true 28 | - type: textarea 29 | attributes: 30 | label: Expected behavior 31 | description: Describe what you expected to happen. 32 | validations: 33 | required: false 34 | - type: textarea 35 | attributes: 36 | label: Actual behavior 37 | description: Describe or copy/paste the behavior you observe. 38 | validations: 39 | required: false 40 | - type: textarea 41 | attributes: 42 | label: Steps to reproduce 43 | description: | 44 | Explanation of how to reproduce the incorrect behavior. 45 | 46 | This could include an attached project or link to code that is exhibiting the issue, and/or a screen recording. 47 | 48 | Please make sure the code you are sharing is minimal and can be run independently of your project, otherwise it may be difficult to debug and will take longer to fix. 49 | placeholder: | 50 | 1. ... 51 | validations: 52 | required: false 53 | - type: input 54 | attributes: 55 | label: Version information 56 | description: The version of SwiftUIIntrospect used to reproduce this issue. 57 | placeholder: "'0.11.0' for example, or a commit hash" 58 | - type: input 59 | attributes: 60 | label: Destination operating system 61 | description: The OS running the SwiftUIIntrospect module. 62 | placeholder: "'iOS 17' for example" 63 | - type: input 64 | attributes: 65 | label: Xcode version information 66 | description: The version of Xcode used to reproduce this issue. 67 | placeholder: "The version displayed from 'Xcode 〉About Xcode'" 68 | - type: textarea 69 | attributes: 70 | label: Swift Compiler version information 71 | description: The version of Swift used to reproduce this issue. 72 | placeholder: Output from 'xcrun swiftc --version' 73 | render: shell 74 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Project Discussion 5 | url: https://github.com/siteline/swiftui-introspect/discussions 6 | about: Q&A, ideas, and more 7 | - name: Documentation 8 | url: https://github.com/siteline/swiftui-introspect#swiftui-introspect 9 | about: Read SwiftUI Introspect's documentation 10 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | deploy: 10 | name: Deploy to CocoaPods Trunk 11 | runs-on: macos-14 12 | steps: 13 | - name: Git Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 # required to be able to find Git tags 17 | 18 | - name: Set up pkgx environment 19 | uses: pkgxdev/setup@v1 20 | with: 21 | +: pod xcodes 22 | 23 | - name: Select Xcode version 24 | run: sudo xcodes select 15.4 25 | 26 | - name: Deploy to CocoaPods Trunk 27 | run: | 28 | set -eo pipefail 29 | export LIB_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) 30 | pod lib lint SwiftUIIntrospect.podspec --allow-warnings 31 | pod trunk push SwiftUIIntrospect.podspec --allow-warnings 32 | env: 33 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /.swiftpm 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | DerivedData/ 8 | .netrc 9 | 10 | fastlane/report.xml 11 | fastlane/Preview.html 12 | fastlane/screenshots/**/*.png 13 | fastlane/test_output 14 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [SwiftUIIntrospect] 5 | custom_documentation_parameters: [--include-extended-types] 6 | -------------------------------------------------------------------------------- /Examples/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.4 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Examples", 7 | products: [], 8 | targets: [] 9 | ) 10 | -------------------------------------------------------------------------------- /Examples/Showcase/Showcase.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Showcase/Showcase.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Showcase/Showcase.xcodeproj/xcshareddata/xcschemes/Showcase.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Examples/Showcase/Showcase/App.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | #if os(iOS) || os(tvOS) 4 | @main 5 | final class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | window = UIWindow(frame: UIScreen.main.bounds) 11 | window?.rootViewController = UIHostingController(rootView: AppView()) 12 | window?.makeKeyAndVisible() 13 | return true 14 | } 15 | } 16 | #elseif os(macOS) || os(visionOS) 17 | @main 18 | struct App: SwiftUI.App { 19 | var body: some Scene { 20 | WindowGroup { 21 | AppView() 22 | } 23 | } 24 | } 25 | #endif 26 | 27 | #if swift(>=5.9) 28 | #Preview { 29 | AppView() 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Examples/Showcase/Showcase/Helpers.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension View { 4 | /// Modify a view with a `ViewBuilder` closure. 5 | /// 6 | /// This represents a streamlining of the 7 | /// [`modifier`](https://developer.apple.com/documentation/swiftui/view/modifier(_:)) + 8 | /// [`ViewModifier`](https://developer.apple.com/documentation/swiftui/viewmodifier) pattern. 9 | /// 10 | /// - Note: Useful only when you don't need to reuse the closure. 11 | /// If you do, turn the closure into a proper modifier. 12 | public func modifier( 13 | @ViewBuilder _ modifier: (Self) -> ModifiedContent 14 | ) -> ModifiedContent { 15 | modifier(self) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/Showcase/Showcase/Showcase.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Timber Software 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swiftui-introspect", 7 | platforms: [ 8 | .iOS(.v13), 9 | .tvOS(.v13), 10 | .macOS(.v10_15), 11 | ], 12 | products: [ 13 | .library(name: "SwiftUIIntrospect", targets: ["SwiftUIIntrospect"]), 14 | .library(name: "SwiftUIIntrospect-Static", type: .static, targets: ["SwiftUIIntrospect"]), 15 | .library(name: "SwiftUIIntrospect-Dynamic", type: .dynamic, targets: ["SwiftUIIntrospect"]), 16 | ], 17 | targets: [ 18 | .target( 19 | name: "SwiftUIIntrospect", 20 | path: "Sources" 21 | ), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /Package@swift-6.0.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swiftui-introspect", 7 | platforms: [ 8 | .iOS(.v13), 9 | .tvOS(.v13), 10 | .macOS(.v10_15), 11 | ], 12 | products: [ 13 | .library(name: "SwiftUIIntrospect", targets: ["SwiftUIIntrospect"]), 14 | .library(name: "SwiftUIIntrospect-Static", type: .static, targets: ["SwiftUIIntrospect"]), 15 | .library(name: "SwiftUIIntrospect-Dynamic", type: .dynamic, targets: ["SwiftUIIntrospect"]), 16 | ], 17 | targets: [ 18 | .target( 19 | name: "SwiftUIIntrospect", 20 | path: "Sources" 21 | ), 22 | ], 23 | swiftLanguageVersions: [.v6] 24 | ) 25 | -------------------------------------------------------------------------------- /Sources/IntrospectableViewType.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | @MainActor 3 | public protocol IntrospectableViewType { 4 | /// The scope of introspection for this particular view type, i.e. where introspect 5 | /// should look to find the desired target view relative to the applied 6 | /// `.introspect(...)` modifier. 7 | /// 8 | /// While the scope can be overridden by the user in their `.introspect(...)` call, 9 | /// most of the time it's preferable to defer to the view type's own scope, 10 | /// as it guarantees introspection is working as intended by the vendor. 11 | /// 12 | /// Defaults to `.receiver` if left unimplemented, which is a sensible one in 13 | /// most cases if you're looking to implement your own view type. 14 | var scope: IntrospectionScope { get } 15 | } 16 | 17 | extension IntrospectableViewType { 18 | public var scope: IntrospectionScope { .receiver } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/IntrospectionSelector.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | @_spi(Advanced) 3 | 4 | @MainActor 5 | public struct IntrospectionSelector { 6 | @_spi(Advanced) 7 | public static var `default`: Self { .from(Target.self, selector: { $0 }) } 8 | 9 | @_spi(Advanced) 10 | public static func from(_ entryType: Entry.Type, selector: @MainActor @escaping (Entry) -> Target?) -> Self { 11 | .init( 12 | receiverSelector: { controller in 13 | controller.as(Entry.Base.self)?.receiver(ofType: Entry.self).flatMap(selector) 14 | }, 15 | ancestorSelector: { controller in 16 | controller.as(Entry.Base.self)?.ancestor(ofType: Entry.self).flatMap(selector) 17 | } 18 | ) 19 | } 20 | 21 | private var receiverSelector: @MainActor (IntrospectionPlatformViewController) -> Target? 22 | private var ancestorSelector: @MainActor (IntrospectionPlatformViewController) -> Target? 23 | 24 | private init( 25 | receiverSelector: @MainActor @escaping (IntrospectionPlatformViewController) -> Target?, 26 | ancestorSelector: @MainActor @escaping (IntrospectionPlatformViewController) -> Target? 27 | ) { 28 | self.receiverSelector = receiverSelector 29 | self.ancestorSelector = ancestorSelector 30 | } 31 | 32 | @_spi(Advanced) 33 | public func withReceiverSelector(_ selector: @MainActor @escaping (PlatformViewController) -> Target?) -> Self { 34 | var copy = self 35 | copy.receiverSelector = selector 36 | return copy 37 | } 38 | 39 | @_spi(Advanced) 40 | public func withAncestorSelector(_ selector: @MainActor @escaping (PlatformViewController) -> Target?) -> Self { 41 | var copy = self 42 | copy.ancestorSelector = selector 43 | return copy 44 | } 45 | 46 | func callAsFunction(_ controller: IntrospectionPlatformViewController, _ scope: IntrospectionScope) -> Target? { 47 | if 48 | scope.contains(.receiver), 49 | let target = receiverSelector(controller) 50 | { 51 | return target 52 | } 53 | if 54 | scope.contains(.ancestor), 55 | let target = ancestorSelector(controller) 56 | { 57 | return target 58 | } 59 | return nil 60 | } 61 | } 62 | 63 | extension PlatformViewController { 64 | func `as`(_ baseType: Base.Type) -> (any PlatformEntity)? { 65 | if Base.self == PlatformView.self { 66 | #if canImport(UIKit) 67 | return viewIfLoaded 68 | #elseif canImport(AppKit) 69 | return isViewLoaded ? view : nil 70 | #endif 71 | } else if Base.self == PlatformViewController.self { 72 | return self 73 | } 74 | return nil 75 | } 76 | } 77 | #endif 78 | -------------------------------------------------------------------------------- /Sources/PlatformView.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | #if canImport(UIKit) 5 | public typealias PlatformView = UIView 6 | #elseif canImport(AppKit) 7 | public typealias PlatformView = NSView 8 | #endif 9 | 10 | #if canImport(UIKit) 11 | public typealias PlatformViewController = UIViewController 12 | #elseif canImport(AppKit) 13 | public typealias PlatformViewController = NSViewController 14 | #endif 15 | 16 | #if canImport(UIKit) 17 | typealias _PlatformViewControllerRepresentable = UIViewControllerRepresentable 18 | #elseif canImport(AppKit) 19 | typealias _PlatformViewControllerRepresentable = NSViewControllerRepresentable 20 | #endif 21 | 22 | @MainActor 23 | protocol PlatformViewControllerRepresentable: _PlatformViewControllerRepresentable { 24 | #if canImport(UIKit) 25 | typealias ViewController = UIViewControllerType 26 | #elseif canImport(AppKit) 27 | typealias ViewController = NSViewControllerType 28 | #endif 29 | 30 | func makePlatformViewController(context: Context) -> ViewController 31 | func updatePlatformViewController(_ controller: ViewController, context: Context) 32 | static func dismantlePlatformViewController(_ controller: ViewController, coordinator: Coordinator) 33 | } 34 | 35 | extension PlatformViewControllerRepresentable { 36 | #if canImport(UIKit) 37 | func makeUIViewController(context: Context) -> ViewController { 38 | makePlatformViewController(context: context) 39 | } 40 | func updateUIViewController(_ controller: ViewController, context: Context) { 41 | updatePlatformViewController(controller, context: context) 42 | } 43 | static func dismantleUIViewController(_ controller: ViewController, coordinator: Coordinator) { 44 | dismantlePlatformViewController(controller, coordinator: coordinator) 45 | } 46 | #elseif canImport(AppKit) 47 | func makeNSViewController(context: Context) -> ViewController { 48 | makePlatformViewController(context: context) 49 | } 50 | func updateNSViewController(_ controller: ViewController, context: Context) { 51 | updatePlatformViewController(controller, context: context) 52 | } 53 | static func dismantleNSViewController(_ controller: ViewController, coordinator: Coordinator) { 54 | dismantlePlatformViewController(controller, coordinator: coordinator) 55 | } 56 | #endif 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /Sources/Utils.swift: -------------------------------------------------------------------------------- 1 | postfix operator ~ 2 | 3 | postfix func ~ (lhs: LHS) -> T { 4 | lhs as! T 5 | } 6 | 7 | postfix func ~ (lhs: LHS?) -> T? { 8 | lhs as? T 9 | } 10 | 11 | func recursiveSequence(_ sequence: S, children: @escaping (S.Element) -> S) -> AnySequence { 12 | AnySequence { 13 | var mainIterator = sequence.makeIterator() 14 | // Current iterator, or `nil` if all sequences are exhausted: 15 | var iterator: AnyIterator? 16 | 17 | return AnyIterator { 18 | guard let iterator, let element = iterator.next() else { 19 | if let element = mainIterator.next() { 20 | iterator = recursiveSequence(children(element), children: children).makeIterator() 21 | return element 22 | } 23 | return nil 24 | } 25 | return element 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ViewTypes/Button.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Button` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// Not available. 9 | /// 10 | /// ### tvOS 11 | /// 12 | /// Not available. 13 | /// 14 | /// ### macOS 15 | /// 16 | /// ```swift 17 | /// struct ContentView: View { 18 | /// var body: some View { 19 | /// Button("Action", action: {}) 20 | /// .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 21 | /// print(type(of: $0)) // NSButton 22 | /// } 23 | /// } 24 | /// } 25 | /// ``` 26 | /// 27 | /// ### visionOS 28 | /// 29 | /// Not available. 30 | public struct ButtonType: IntrospectableViewType {} 31 | 32 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 33 | extension IntrospectableViewType where Self == ButtonType { 34 | public static var button: Self { .init() } 35 | } 36 | 37 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 38 | extension macOSViewVersion { 39 | public static let v10_15 = Self(for: .v10_15) 40 | public static let v11 = Self(for: .v11) 41 | public static let v12 = Self(for: .v12) 42 | public static let v13 = Self(for: .v13) 43 | public static let v14 = Self(for: .v14) 44 | public static let v15 = Self(for: .v15) 45 | } 46 | #endif 47 | #endif 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ColorPicker.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `ColorPicker` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var color = Color.red 11 | /// 12 | /// var body: some View { 13 | /// ColorPicker("Pick a color", selection: $color) 14 | /// .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UIColorPicker 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// Not available. 24 | /// 25 | /// ### macOS 26 | /// 27 | /// ```swift 28 | /// struct ContentView: View { 29 | /// @State var color = Color.red 30 | /// 31 | /// var body: some View { 32 | /// ColorPicker("Pick a color", selection: $color) 33 | /// .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14, .v15)) { 34 | /// print(type(of: $0)) // NSColorPicker 35 | /// } 36 | /// } 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### visionOS 41 | /// 42 | /// ```swift 43 | /// struct ContentView: View { 44 | /// @State var color = Color.red 45 | /// 46 | /// var body: some View { 47 | /// ColorPicker("Pick a color", selection: $color) 48 | /// .introspect(.colorPicker, on: .visionOS(.v1, .v2)) { 49 | /// print(type(of: $0)) // UIColorPicker 50 | /// } 51 | /// } 52 | /// } 53 | /// ``` 54 | public struct ColorPickerType: IntrospectableViewType {} 55 | 56 | #if !os(tvOS) 57 | extension IntrospectableViewType where Self == ColorPickerType { 58 | public static var colorPicker: Self { .init() } 59 | } 60 | 61 | #if canImport(UIKit) 62 | @available(iOS 14, *) 63 | extension iOSViewVersion { 64 | @available(*, unavailable, message: "ColorPicker isn't available on iOS 13") 65 | public static let v13 = Self.unavailable() 66 | public static let v14 = Self(for: .v14) 67 | public static let v15 = Self(for: .v15) 68 | public static let v16 = Self(for: .v16) 69 | public static let v17 = Self(for: .v17) 70 | public static let v18 = Self(for: .v18) 71 | } 72 | 73 | @available(iOS 14, *) 74 | extension visionOSViewVersion { 75 | public static let v1 = Self(for: .v1) 76 | public static let v2 = Self(for: .v2) 77 | } 78 | #elseif canImport(AppKit) 79 | @available(macOS 11, *) 80 | extension macOSViewVersion { 81 | @available(*, unavailable, message: "ColorPicker isn't available on macOS 10.15") 82 | public static let v10_15 = Self.unavailable() 83 | public static let v11 = Self(for: .v11) 84 | public static let v12 = Self(for: .v12) 85 | public static let v13 = Self(for: .v13) 86 | public static let v14 = Self(for: .v14) 87 | public static let v15 = Self(for: .v15) 88 | } 89 | #endif 90 | #endif 91 | #endif 92 | -------------------------------------------------------------------------------- /Sources/ViewTypes/DatePicker.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `DatePicker` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var date = Date() 11 | /// 12 | /// var body: some View { 13 | /// DatePicker("Pick a date", selection: $date) 14 | /// .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UIDatePicker 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// Not available. 24 | /// 25 | /// ```swift 26 | /// struct ContentView: View { 27 | /// @State var date = Date() 28 | /// 29 | /// var body: some View { 30 | /// DatePicker("Pick a date", selection: $date) 31 | /// .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 32 | /// print(type(of: $0)) // NSDatePicker 33 | /// } 34 | /// } 35 | /// } 36 | /// ``` 37 | /// 38 | /// ### visionOS 39 | /// 40 | /// ```swift 41 | /// struct ContentView: View { 42 | /// @State var date = Date() 43 | /// 44 | /// var body: some View { 45 | /// DatePicker("Pick a date", selection: $date) 46 | /// .introspect(.datePicker, on: .visionOS(.v1, .v2)) { 47 | /// print(type(of: $0)) // UIDatePicker 48 | /// } 49 | /// } 50 | /// } 51 | /// ``` 52 | public struct DatePickerType: IntrospectableViewType {} 53 | 54 | #if !os(tvOS) 55 | extension IntrospectableViewType where Self == DatePickerType { 56 | public static var datePicker: Self { .init() } 57 | } 58 | 59 | #if canImport(UIKit) 60 | extension iOSViewVersion { 61 | public static let v13 = Self(for: .v13) 62 | public static let v14 = Self(for: .v14) 63 | public static let v15 = Self(for: .v15) 64 | public static let v16 = Self(for: .v16) 65 | public static let v17 = Self(for: .v17) 66 | public static let v18 = Self(for: .v18) 67 | } 68 | 69 | extension visionOSViewVersion { 70 | public static let v1 = Self(for: .v1) 71 | public static let v2 = Self(for: .v2) 72 | } 73 | #elseif canImport(AppKit) 74 | extension macOSViewVersion { 75 | public static let v10_15 = Self(for: .v10_15) 76 | public static let v11 = Self(for: .v11) 77 | public static let v12 = Self(for: .v12) 78 | public static let v13 = Self(for: .v13) 79 | public static let v14 = Self(for: .v14) 80 | public static let v15 = Self(for: .v15) 81 | } 82 | #endif 83 | #endif 84 | #endif 85 | -------------------------------------------------------------------------------- /Sources/ViewTypes/DatePickerWithCompactStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `DatePicker` type in SwiftUI, with `.compact` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var date = Date() 11 | /// 12 | /// var body: some View { 13 | /// DatePicker("Pick a date", selection: $date) 14 | /// .datePickerStyle(.compact) 15 | /// .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17, .v18)) { 16 | /// print(type(of: $0)) // UIDatePicker 17 | /// } 18 | /// } 19 | /// } 20 | /// ``` 21 | /// 22 | /// ### tvOS 23 | /// 24 | /// Not available. 25 | /// 26 | /// ### macOS 27 | /// 28 | /// ```swift 29 | /// struct ContentView: View { 30 | /// @State var date = Date() 31 | /// 32 | /// var body: some View { 33 | /// DatePicker("Pick a date", selection: $date) 34 | /// .datePickerStyle(.compact) 35 | /// .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14, .v15)) { 36 | /// print(type(of: $0)) // NSDatePicker 37 | /// } 38 | /// } 39 | /// } 40 | /// ``` 41 | /// 42 | /// ### visionOS 43 | /// 44 | /// ```swift 45 | /// struct ContentView: View { 46 | /// @State var date = Date() 47 | /// 48 | /// var body: some View { 49 | /// DatePicker("Pick a date", selection: $date) 50 | /// .datePickerStyle(.compact) 51 | /// .introspect(.datePicker(style: .compact), on: .visionOS(.v1, .v2)) { 52 | /// print(type(of: $0)) // UIDatePicker 53 | /// } 54 | /// } 55 | /// } 56 | /// ``` 57 | public struct DatePickerWithCompactStyleType: IntrospectableViewType { 58 | public enum Style: Sendable { 59 | case compact 60 | } 61 | } 62 | 63 | #if !os(tvOS) 64 | extension IntrospectableViewType where Self == DatePickerWithCompactStyleType { 65 | public static func datePicker(style: Self.Style) -> Self { .init() } 66 | } 67 | 68 | #if canImport(UIKit) 69 | extension iOSViewVersion { 70 | @available(*, unavailable, message: ".datePickerStyle(.compact) isn't available on iOS 13") 71 | public static let v13 = Self(for: .v13) 72 | public static let v14 = Self(for: .v14) 73 | public static let v15 = Self(for: .v15) 74 | public static let v16 = Self(for: .v16) 75 | public static let v17 = Self(for: .v17) 76 | public static let v18 = Self(for: .v18) 77 | } 78 | 79 | extension visionOSViewVersion { 80 | public static let v1 = Self(for: .v1) 81 | public static let v2 = Self(for: .v2) 82 | } 83 | #elseif canImport(AppKit) && !targetEnvironment(macCatalyst) 84 | extension macOSViewVersion { 85 | @available(*, unavailable, message: ".datePickerStyle(.compact) isn't available on macOS 10.15") 86 | public static let v10_15 = Self(for: .v10_15) 87 | public static let v10_15_4 = Self(for: .v10_15_4) 88 | public static let v11 = Self(for: .v11) 89 | public static let v12 = Self(for: .v12) 90 | public static let v13 = Self(for: .v13) 91 | public static let v14 = Self(for: .v14) 92 | public static let v15 = Self(for: .v15) 93 | } 94 | #endif 95 | #endif 96 | #endif 97 | -------------------------------------------------------------------------------- /Sources/ViewTypes/DatePickerWithFieldStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `DatePicker` type in SwiftUI, with `.field` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// Not available. 9 | /// 10 | /// ### tvOS 11 | /// 12 | /// Not available. 13 | /// 14 | /// ### macOS 15 | /// 16 | /// ```swift 17 | /// struct ContentView: View { 18 | /// @State var date = Date() 19 | /// 20 | /// var body: some View { 21 | /// DatePicker("Pick a date", selection: $date) 22 | /// .datePickerStyle(.field) 23 | /// .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 24 | /// print(type(of: $0)) // NSDatePicker 25 | /// } 26 | /// } 27 | /// } 28 | /// ``` 29 | /// 30 | /// ### visionOS 31 | /// 32 | /// Not available. 33 | public struct DatePickerWithFieldStyleType: IntrospectableViewType { 34 | public enum Style: Sendable { 35 | case field 36 | } 37 | } 38 | 39 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 40 | extension IntrospectableViewType where Self == DatePickerWithFieldStyleType { 41 | public static func datePicker(style: Self.Style) -> Self { .init() } 42 | } 43 | 44 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 45 | extension macOSViewVersion { 46 | public static let v10_15 = Self(for: .v10_15) 47 | public static let v11 = Self(for: .v11) 48 | public static let v12 = Self(for: .v12) 49 | public static let v13 = Self(for: .v13) 50 | public static let v14 = Self(for: .v14) 51 | public static let v15 = Self(for: .v15) 52 | } 53 | #endif 54 | #endif 55 | #endif 56 | -------------------------------------------------------------------------------- /Sources/ViewTypes/DatePickerWithGraphicalStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `DatePicker` type in SwiftUI, with `.graphical` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var date = Date() 11 | /// 12 | /// var body: some View { 13 | /// DatePicker("Pick a date", selection: $date) 14 | /// .datePickerStyle(.graphical) 15 | /// .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17, .v18)) { 16 | /// print(type(of: $0)) // UIDatePicker 17 | /// } 18 | /// } 19 | /// } 20 | /// ``` 21 | /// 22 | /// ### tvOS 23 | /// 24 | /// Not available. 25 | /// 26 | /// ### macOS 27 | /// 28 | /// ```swift 29 | /// struct ContentView: View { 30 | /// @State var date = Date() 31 | /// 32 | /// var body: some View { 33 | /// DatePicker("Pick a date", selection: $date) 34 | /// .datePickerStyle(.graphical) 35 | /// .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 36 | /// print(type(of: $0)) // NSDatePicker 37 | /// } 38 | /// } 39 | /// } 40 | /// ``` 41 | /// 42 | /// ### visionOS 43 | /// 44 | /// ```swift 45 | /// struct ContentView: View { 46 | /// @State var date = Date() 47 | /// 48 | /// var body: some View { 49 | /// DatePicker("Pick a date", selection: $date) 50 | /// .datePickerStyle(.graphical) 51 | /// .introspect(.datePicker(style: .graphical), on: .visionOS(.v1, .v2)) { 52 | /// print(type(of: $0)) // UIDatePicker 53 | /// } 54 | /// } 55 | /// } 56 | /// ``` 57 | public struct DatePickerWithGraphicalStyleType: IntrospectableViewType { 58 | public enum Style: Sendable { 59 | case graphical 60 | } 61 | } 62 | 63 | #if !os(tvOS) 64 | extension IntrospectableViewType where Self == DatePickerWithGraphicalStyleType { 65 | public static func datePicker(style: Self.Style) -> Self { .init() } 66 | } 67 | 68 | #if canImport(UIKit) 69 | extension iOSViewVersion { 70 | @available(*, unavailable, message: ".datePickerStyle(.graphical) isn't available on iOS 13") 71 | public static let v13 = Self(for: .v13) 72 | public static let v14 = Self(for: .v14) 73 | public static let v15 = Self(for: .v15) 74 | public static let v16 = Self(for: .v16) 75 | public static let v17 = Self(for: .v17) 76 | public static let v18 = Self(for: .v18) 77 | } 78 | 79 | extension visionOSViewVersion { 80 | public static let v1 = Self(for: .v1) 81 | public static let v2 = Self(for: .v2) 82 | } 83 | #elseif canImport(AppKit) && !targetEnvironment(macCatalyst) 84 | extension macOSViewVersion { 85 | public static let v10_15 = Self(for: .v10_15) 86 | public static let v11 = Self(for: .v11) 87 | public static let v12 = Self(for: .v12) 88 | public static let v13 = Self(for: .v13) 89 | public static let v14 = Self(for: .v14) 90 | public static let v15 = Self(for: .v15) 91 | } 92 | #endif 93 | #endif 94 | #endif 95 | -------------------------------------------------------------------------------- /Sources/ViewTypes/DatePickerWithStepperFieldStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `DatePicker` type in SwiftUI, with `.stepperField` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// Not available. 9 | /// 10 | /// ### tvOS 11 | /// 12 | /// Not available. 13 | /// 14 | /// ### macOS 15 | /// 16 | /// ```swift 17 | /// struct ContentView: View { 18 | /// @State var date = Date() 19 | /// 20 | /// var body: some View { 21 | /// DatePicker("Pick a date", selection: $date) 22 | /// .datePickerStyle(.stepperField) 23 | /// .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 24 | /// print(type(of: $0)) // NSDatePicker 25 | /// } 26 | /// } 27 | /// } 28 | /// ``` 29 | /// 30 | /// ### visionOS 31 | /// 32 | /// Not available. 33 | public struct DatePickerWithStepperFieldStyleType: IntrospectableViewType { 34 | public enum Style: Sendable { 35 | case stepperField 36 | } 37 | } 38 | 39 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 40 | extension IntrospectableViewType where Self == DatePickerWithStepperFieldStyleType { 41 | public static func datePicker(style: Self.Style) -> Self { .init() } 42 | } 43 | 44 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 45 | extension macOSViewVersion { 46 | public static let v10_15 = Self(for: .v10_15) 47 | public static let v11 = Self(for: .v11) 48 | public static let v12 = Self(for: .v12) 49 | public static let v13 = Self(for: .v13) 50 | public static let v14 = Self(for: .v14) 51 | public static let v15 = Self(for: .v15) 52 | } 53 | #endif 54 | #endif 55 | #endif 56 | -------------------------------------------------------------------------------- /Sources/ViewTypes/DatePickerWithWheelStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `DatePicker` type in SwiftUI, with `.wheel` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var date = Date() 11 | /// 12 | /// var body: some View { 13 | /// DatePicker("Pick a date", selection: $date) 14 | /// .datePickerStyle(.wheel) 15 | /// .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 16 | /// print(type(of: $0)) // UIDatePicker 17 | /// } 18 | /// } 19 | /// } 20 | /// ``` 21 | /// 22 | /// ### tvOS 23 | /// 24 | /// Not available. 25 | /// 26 | /// ### macOS 27 | /// 28 | /// Not available. 29 | /// 30 | /// ### visionOS 31 | /// 32 | /// ```swift 33 | /// struct ContentView: View { 34 | /// @State var date = Date() 35 | /// 36 | /// var body: some View { 37 | /// DatePicker("Pick a date", selection: $date) 38 | /// .datePickerStyle(.wheel) 39 | /// .introspect(.datePicker(style: .wheel), on: .visionOS(.v1, .v2)) { 40 | /// print(type(of: $0)) // UIDatePicker 41 | /// } 42 | /// } 43 | /// } 44 | /// ``` 45 | public struct DatePickerWithWheelStyleType: IntrospectableViewType { 46 | public enum Style: Sendable { 47 | case wheel 48 | } 49 | } 50 | 51 | #if !os(tvOS) && !os(macOS) 52 | extension IntrospectableViewType where Self == DatePickerWithWheelStyleType { 53 | public static func datePicker(style: Self.Style) -> Self { .init() } 54 | } 55 | 56 | #if canImport(UIKit) 57 | extension iOSViewVersion { 58 | public static let v13 = Self(for: .v13) 59 | public static let v14 = Self(for: .v14) 60 | public static let v15 = Self(for: .v15) 61 | public static let v16 = Self(for: .v16) 62 | public static let v17 = Self(for: .v17) 63 | public static let v18 = Self(for: .v18) 64 | } 65 | 66 | extension visionOSViewVersion { 67 | public static let v1 = Self(for: .v1) 68 | public static let v2 = Self(for: .v2) 69 | } 70 | #endif 71 | #endif 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/ViewTypes/Form.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Form` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// var body: some View { 11 | /// Form { 12 | /// Text("Item 1") 13 | /// Text("Item 2") 14 | /// Text("Item 3") 15 | /// } 16 | /// .introspect(.form, on: .iOS(.v13, .v14, .v15)) { 17 | /// print(type(of: $0)) // UITableView 18 | /// } 19 | /// .introspect(.form, on: .iOS(.v16, .v17, .v18)) { 20 | /// print(type(of: $0)) // UICollectionView 21 | /// } 22 | /// } 23 | /// } 24 | /// ``` 25 | /// 26 | /// ### tvOS 27 | /// 28 | /// ```swift 29 | /// struct ContentView: View { 30 | /// var body: some View { 31 | /// Form { 32 | /// Text("Item 1") 33 | /// Text("Item 2") 34 | /// Text("Item 3") 35 | /// } 36 | /// .introspect(.form, on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 37 | /// print(type(of: $0)) // UITableView 38 | /// } 39 | /// } 40 | /// } 41 | /// ``` 42 | /// 43 | /// ### macOS 44 | /// 45 | /// Not available. 46 | /// 47 | /// ### visionOS 48 | /// 49 | /// ```swift 50 | /// struct ContentView: View { 51 | /// var body: some View { 52 | /// Form { 53 | /// Text("Item 1") 54 | /// Text("Item 2") 55 | /// Text("Item 3") 56 | /// } 57 | /// .introspect(.form, on: .visionOS(.v1, .v2)) { 58 | /// print(type(of: $0)) // UICollectionView 59 | /// } 60 | /// } 61 | /// } 62 | /// ``` 63 | public struct FormType: IntrospectableViewType {} 64 | 65 | #if !os(macOS) 66 | extension IntrospectableViewType where Self == FormType { 67 | public static var form: Self { .init() } 68 | } 69 | 70 | #if canImport(UIKit) 71 | extension iOSViewVersion { 72 | public static let v13 = Self(for: .v13) 73 | public static let v14 = Self(for: .v14) 74 | public static let v15 = Self(for: .v15) 75 | } 76 | 77 | extension iOSViewVersion { 78 | public static let v16 = Self(for: .v16) 79 | public static let v17 = Self(for: .v17) 80 | public static let v18 = Self(for: .v18) 81 | } 82 | 83 | extension tvOSViewVersion { 84 | public static let v13 = Self(for: .v13) 85 | public static let v14 = Self(for: .v14) 86 | public static let v15 = Self(for: .v15) 87 | public static let v16 = Self(for: .v16) 88 | public static let v17 = Self(for: .v17) 89 | public static let v18 = Self(for: .v18) 90 | } 91 | 92 | extension visionOSViewVersion { 93 | public static let v1 = Self(for: .v1) 94 | public static let v2 = Self(for: .v2) 95 | } 96 | #endif 97 | #endif 98 | #endif 99 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ListWithBorderedStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `List` type in SwiftUI, with `.bordered` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// Not available. 9 | /// 10 | /// ### tvOS 11 | /// 12 | /// Not available. 13 | /// 14 | /// ### macOS 15 | /// 16 | /// ```swift 17 | /// struct ContentView: View { 18 | /// var body: some View { 19 | /// List { 20 | /// Text("Item 1") 21 | /// Text("Item 2") 22 | /// Text("Item 3") 23 | /// } 24 | /// .listStyle(.bordered) 25 | /// .introspect(.list(style: .bordered), on: .macOS(.v12, .v13, .v14, .v15)) { 26 | /// print(type(of: $0)) // NSTableView 27 | /// } 28 | /// } 29 | /// } 30 | /// ``` 31 | /// 32 | /// ### visionOS 33 | /// 34 | /// Not available. 35 | public struct ListWithBorderedStyleType: IntrospectableViewType { 36 | public enum Style: Sendable { 37 | case bordered 38 | } 39 | } 40 | 41 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 42 | extension IntrospectableViewType where Self == ListWithBorderedStyleType { 43 | public static func list(style: Self.Style) -> Self { .init() } 44 | } 45 | 46 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 47 | extension macOSViewVersion { 48 | @available(*, unavailable, message: ".listStyle(.insetGrouped) isn't available on macOS 10.15") 49 | public static let v10_15 = Self.unavailable() 50 | @available(*, unavailable, message: ".listStyle(.insetGrouped) isn't available on macOS 11") 51 | public static let v11 = Self.unavailable() 52 | public static let v12 = Self(for: .v12) 53 | public static let v13 = Self(for: .v13) 54 | public static let v14 = Self(for: .v14) 55 | public static let v15 = Self(for: .v15) 56 | } 57 | #endif 58 | #endif 59 | #endif 60 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ListWithGroupedStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `List` type in SwiftUI, with `.grouped` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// var body: some View { 11 | /// List { 12 | /// Text("Item 1") 13 | /// Text("Item 2") 14 | /// Text("Item 3") 15 | /// } 16 | /// .listStyle(.grouped) 17 | /// .introspect(.list(style: .grouped), on: .iOS(.v13, .v14, .v15)) { 18 | /// print(type(of: $0)) // UITableView 19 | /// } 20 | /// .introspect(.list(style: .grouped), on: .iOS(.v16, .v17, .v18)) { 21 | /// print(type(of: $0)) // UICollectionView 22 | /// } 23 | /// } 24 | /// } 25 | /// ``` 26 | /// 27 | /// ### tvOS 28 | /// 29 | /// ```swift 30 | /// struct ContentView: View { 31 | /// var body: some View { 32 | /// List { 33 | /// Text("Item 1") 34 | /// Text("Item 2") 35 | /// Text("Item 3") 36 | /// } 37 | /// .listStyle(.grouped) 38 | /// .introspect(.list(style: .grouped), on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 39 | /// print(type(of: $0)) // UITableView 40 | /// } 41 | /// } 42 | /// } 43 | /// ``` 44 | /// 45 | /// ### macOS 46 | /// 47 | /// Not available. 48 | /// 49 | /// ### visionOS 50 | /// 51 | /// ```swift 52 | /// struct ContentView: View { 53 | /// var body: some View { 54 | /// List { 55 | /// Text("Item 1") 56 | /// Text("Item 2") 57 | /// Text("Item 3") 58 | /// } 59 | /// .listStyle(.grouped) 60 | /// .introspect(.list(style: .grouped), on: .visionOS(.v1, .v2)) { 61 | /// print(type(of: $0)) // UICollectionView 62 | /// } 63 | /// } 64 | /// } 65 | /// ``` 66 | public struct ListWithGroupedStyleType: IntrospectableViewType { 67 | public enum Style: Sendable { 68 | case grouped 69 | } 70 | } 71 | 72 | #if !os(macOS) 73 | extension IntrospectableViewType where Self == ListWithGroupedStyleType { 74 | public static func list(style: Self.Style) -> Self { .init() } 75 | } 76 | 77 | #if canImport(UIKit) 78 | extension iOSViewVersion { 79 | public static let v13 = Self(for: .v13) 80 | public static let v14 = Self(for: .v14) 81 | public static let v15 = Self(for: .v15) 82 | } 83 | 84 | extension iOSViewVersion { 85 | public static let v16 = Self(for: .v16) 86 | public static let v17 = Self(for: .v17) 87 | public static let v18 = Self(for: .v18) 88 | } 89 | 90 | extension tvOSViewVersion { 91 | public static let v13 = Self(for: .v13) 92 | public static let v14 = Self(for: .v14) 93 | public static let v15 = Self(for: .v15) 94 | public static let v16 = Self(for: .v16) 95 | public static let v17 = Self(for: .v17) 96 | public static let v18 = Self(for: .v18) 97 | } 98 | 99 | extension visionOSViewVersion { 100 | public static let v1 = Self(for: .v1) 101 | public static let v2 = Self(for: .v2) 102 | } 103 | #endif 104 | #endif 105 | #endif 106 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ListWithInsetGroupedStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `List` type in SwiftUI, with `.insetGrouped` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// var body: some View { 11 | /// List { 12 | /// Text("Item 1") 13 | /// Text("Item 2") 14 | /// Text("Item 3") 15 | /// } 16 | /// .listStyle(.insetGrouped) 17 | /// .introspect(.list(style: .insetGrouped), on: .iOS(.v14, .v15)) { 18 | /// print(type(of: $0)) // UITableView 19 | /// } 20 | /// .introspect(.list(style: .insetGrouped), on: .iOS(.v16, .v17, .v18)) { 21 | /// print(type(of: $0)) // UICollectionView 22 | /// } 23 | /// } 24 | /// } 25 | /// ``` 26 | /// 27 | /// ### tvOS 28 | /// 29 | /// Not available. 30 | /// 31 | /// ### macOS 32 | /// 33 | /// Not available. 34 | /// 35 | /// ### visionOS 36 | /// 37 | /// ```swift 38 | /// struct ContentView: View { 39 | /// var body: some View { 40 | /// List { 41 | /// Text("Item 1") 42 | /// Text("Item 2") 43 | /// Text("Item 3") 44 | /// } 45 | /// .listStyle(.insetGrouped) 46 | /// .introspect(.list(style: .insetGrouped), on: .visionOS(.v1, .v2)) { 47 | /// print(type(of: $0)) // UICollectionView 48 | /// } 49 | /// } 50 | /// } 51 | /// ``` 52 | public struct ListWithInsetGroupedStyleType: IntrospectableViewType { 53 | public enum Style: Sendable { 54 | case insetGrouped 55 | } 56 | } 57 | 58 | #if !os(tvOS) && !os(macOS) 59 | extension IntrospectableViewType where Self == ListWithInsetGroupedStyleType { 60 | public static func list(style: Self.Style) -> Self { .init() } 61 | } 62 | 63 | #if canImport(UIKit) 64 | extension iOSViewVersion { 65 | @available(*, unavailable, message: ".listStyle(.insetGrouped) isn't available on iOS 13") 66 | public static let v13 = Self(for: .v13) 67 | public static let v14 = Self(for: .v14) 68 | public static let v15 = Self(for: .v15) 69 | } 70 | 71 | extension iOSViewVersion { 72 | public static let v16 = Self(for: .v16) 73 | public static let v17 = Self(for: .v17) 74 | public static let v18 = Self(for: .v18) 75 | } 76 | 77 | extension visionOSViewVersion { 78 | public static let v1 = Self(for: .v1) 79 | public static let v2 = Self(for: .v2) 80 | } 81 | #endif 82 | #endif 83 | #endif 84 | -------------------------------------------------------------------------------- /Sources/ViewTypes/PageControl.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the page control type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// var body: some View { 11 | /// TabView { 12 | /// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) 13 | /// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) 14 | /// } 15 | /// .tabViewStyle(.page(indexDisplayMode: .always)) 16 | /// .introspect(.pageControl, on: .iOS(.v14, .v15, .v16, .v17, .v18)) { 17 | /// print(type(of: $0)) // UIPageControl 18 | /// } 19 | /// } 20 | /// } 21 | /// ``` 22 | /// 23 | /// ### tvOS 24 | /// 25 | /// ```swift 26 | /// struct ContentView: View { 27 | /// var body: some View { 28 | /// TabView { 29 | /// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) 30 | /// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) 31 | /// } 32 | /// .tabViewStyle(.page(indexDisplayMode: .always)) 33 | /// .introspect(.pageControl, on: .tvOS(.v14, .v15, .v16, .v17, .v18)) { 34 | /// print(type(of: $0)) // UIPageControl 35 | /// } 36 | /// } 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### macOS 41 | /// 42 | /// Not available. 43 | /// 44 | /// ### visionOS 45 | /// 46 | /// ```swift 47 | /// struct ContentView: View { 48 | /// var body: some View { 49 | /// TabView { 50 | /// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) 51 | /// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) 52 | /// } 53 | /// .tabViewStyle(.page(indexDisplayMode: .always)) 54 | /// .introspect(.pageControl, on: .visionOS(.v1, .v2)) { 55 | /// print(type(of: $0)) // UIPageControl 56 | /// } 57 | /// } 58 | /// } 59 | /// ``` 60 | public struct PageControlType: IntrospectableViewType {} 61 | 62 | extension IntrospectableViewType where Self == PageControlType { 63 | public static var pageControl: Self { .init() } 64 | } 65 | 66 | #if canImport(UIKit) 67 | extension iOSViewVersion { 68 | @available(*, unavailable, message: ".tabViewStyle(.page) isn't available on iOS 13") 69 | public static let v13 = Self(for: .v13) 70 | public static let v14 = Self(for: .v14) 71 | public static let v15 = Self(for: .v15) 72 | public static let v16 = Self(for: .v16) 73 | public static let v17 = Self(for: .v17) 74 | public static let v18 = Self(for: .v18) 75 | } 76 | 77 | extension tvOSViewVersion { 78 | @available(*, unavailable, message: ".tabViewStyle(.page) isn't available on tvOS 13") 79 | public static let v13 = Self(for: .v13) 80 | public static let v14 = Self(for: .v14) 81 | public static let v15 = Self(for: .v15) 82 | public static let v16 = Self(for: .v16) 83 | public static let v17 = Self(for: .v17) 84 | public static let v18 = Self(for: .v18) 85 | } 86 | 87 | extension visionOSViewVersion { 88 | public static let v1 = Self(for: .v1) 89 | public static let v2 = Self(for: .v2) 90 | } 91 | #endif 92 | #endif 93 | -------------------------------------------------------------------------------- /Sources/ViewTypes/PickerWithMenuStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Picker` type in SwiftUI, with `.menu` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// Not available. 9 | /// 10 | /// ### tvOS 11 | /// 12 | /// Not available. 13 | /// 14 | /// ### macOS 15 | /// 16 | /// ```swift 17 | /// struct ContentView: View { 18 | /// @State var selection = "1" 19 | /// 20 | /// var body: some View { 21 | /// Picker("Pick a number", selection: $selection) { 22 | /// Text("1").tag("1") 23 | /// Text("2").tag("2") 24 | /// Text("3").tag("3") 25 | /// } 26 | /// .pickerStyle(.menu) 27 | /// .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14, .v15)) { 28 | /// print(type(of: $0)) // NSPopUpButton 29 | /// } 30 | /// } 31 | /// } 32 | /// ``` 33 | /// 34 | /// ### visionOS 35 | /// 36 | /// Not available. 37 | public struct PickerWithMenuStyleType: IntrospectableViewType { 38 | public enum Style: Sendable { 39 | case menu 40 | } 41 | } 42 | 43 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 44 | extension IntrospectableViewType where Self == PickerWithMenuStyleType { 45 | public static func picker(style: Self.Style) -> Self { .init() } 46 | } 47 | 48 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 49 | extension macOSViewVersion { 50 | @available(*, unavailable, message: ".pickerStyle(.menu) isn't available on macOS 10.15") 51 | public static let v10_15 = Self.unavailable() 52 | public static let v11 = Self(for: .v11) 53 | public static let v12 = Self(for: .v12) 54 | public static let v13 = Self(for: .v13) 55 | public static let v14 = Self(for: .v14) 56 | public static let v15 = Self(for: .v15) 57 | } 58 | #endif 59 | #endif 60 | #endif 61 | -------------------------------------------------------------------------------- /Sources/ViewTypes/PickerWithWheelStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Picker` type in SwiftUI, with `.wheel` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var selection = "1" 11 | /// 12 | /// var body: some View { 13 | /// Picker("Pick a number", selection: $selection) { 14 | /// Text("1").tag("1") 15 | /// Text("2").tag("2") 16 | /// Text("3").tag("3") 17 | /// } 18 | /// .pickerStyle(.wheel) 19 | /// .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 20 | /// print(type(of: $0)) // UIPickerView 21 | /// } 22 | /// } 23 | /// } 24 | /// ``` 25 | /// 26 | /// ### tvOS 27 | /// 28 | /// Not available. 29 | /// 30 | /// ### macOS 31 | /// 32 | /// Not available. 33 | /// 34 | /// ### visionOS 35 | /// 36 | /// ```swift 37 | /// struct ContentView: View { 38 | /// @State var selection = "1" 39 | /// 40 | /// var body: some View { 41 | /// Picker("Pick a number", selection: $selection) { 42 | /// Text("1").tag("1") 43 | /// Text("2").tag("2") 44 | /// Text("3").tag("3") 45 | /// } 46 | /// .pickerStyle(.wheel) 47 | /// .introspect(.picker(style: .wheel), on: .visionOS(.v1, .v2)) { 48 | /// print(type(of: $0)) // UIPickerView 49 | /// } 50 | /// } 51 | /// } 52 | /// ``` 53 | public struct PickerWithWheelStyleType: IntrospectableViewType { 54 | public enum Style: Sendable { 55 | case wheel 56 | } 57 | } 58 | 59 | #if !os(tvOS) && !os(macOS) 60 | extension IntrospectableViewType where Self == PickerWithWheelStyleType { 61 | public static func picker(style: Self.Style) -> Self { .init() } 62 | } 63 | 64 | #if canImport(UIKit) 65 | extension iOSViewVersion { 66 | public static let v13 = Self(for: .v13) 67 | public static let v14 = Self(for: .v14) 68 | public static let v15 = Self(for: .v15) 69 | public static let v16 = Self(for: .v16) 70 | public static let v17 = Self(for: .v17) 71 | public static let v18 = Self(for: .v18) 72 | } 73 | 74 | extension visionOSViewVersion { 75 | public static let v1 = Self(for: .v1) 76 | public static let v2 = Self(for: .v2) 77 | } 78 | #endif 79 | #endif 80 | #endif 81 | -------------------------------------------------------------------------------- /Sources/ViewTypes/Popover.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of `.popover` in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var isPresented = false 11 | /// 12 | /// var body: some View { 13 | /// Button("Present", action: { isPresented = true }) 14 | /// .popover(isPresented: $isPresented) { 15 | /// Button("Dismiss", action: { isPresented = false }) 16 | /// .introspect(.popover, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 17 | /// print(type(of: $0)) // UIPopoverPresentationController 18 | /// } 19 | /// } 20 | /// } 21 | /// } 22 | /// ``` 23 | /// 24 | /// ### tvOS 25 | /// 26 | /// Not available. 27 | /// 28 | /// ### macOS 29 | /// 30 | /// Not available. 31 | /// 32 | /// ### visionOS 33 | /// 34 | /// ```swift 35 | /// struct ContentView: View { 36 | /// @State var isPresented = false 37 | /// 38 | /// var body: some View { 39 | /// Button("Present", action: { isPresented = true }) 40 | /// .popover(isPresented: $isPresented) { 41 | /// Button("Dismiss", action: { isPresented = false }) 42 | /// .introspect(.popover, on: .visionOS(.v1, .v2)) { 43 | /// print(type(of: $0)) // UIPopoverPresentationController 44 | /// } 45 | /// } 46 | /// } 47 | /// } 48 | /// ``` 49 | public struct PopoverType: IntrospectableViewType { 50 | public var scope: IntrospectionScope { .ancestor } 51 | } 52 | 53 | #if !os(tvOS) && !os(macOS) 54 | extension IntrospectableViewType where Self == PopoverType { 55 | public static var popover: Self { .init() } 56 | } 57 | 58 | #if canImport(UIKit) 59 | extension iOSViewVersion { 60 | public static let v13 = Self(for: .v13, selector: selector) 61 | public static let v14 = Self(for: .v14, selector: selector) 62 | public static let v15 = Self(for: .v15, selector: selector) 63 | public static let v16 = Self(for: .v16, selector: selector) 64 | public static let v17 = Self(for: .v17, selector: selector) 65 | public static let v18 = Self(for: .v18, selector: selector) 66 | 67 | private static var selector: IntrospectionSelector { 68 | .from(UIViewController.self, selector: { $0.popoverPresentationController }) 69 | } 70 | } 71 | 72 | extension visionOSViewVersion { 73 | public static let v1 = Self(for: .v1, selector: selector) 74 | public static let v2 = Self(for: .v2, selector: selector) 75 | 76 | private static var selector: IntrospectionSelector { 77 | .from(UIViewController.self, selector: { $0.popoverPresentationController }) 78 | } 79 | } 80 | #endif 81 | #endif 82 | #endif 83 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ScrollView.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `ScrollView` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// var body: some View { 11 | /// ScrollView { 12 | /// Text("Item") 13 | /// } 14 | /// .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UIScrollView 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// ```swift 24 | /// struct ContentView: View { 25 | /// var body: some View { 26 | /// ScrollView { 27 | /// Text("Item") 28 | /// } 29 | /// .introspect(.scrollView, on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 30 | /// print(type(of: $0)) // UIScrollView 31 | /// } 32 | /// } 33 | /// } 34 | /// ``` 35 | /// 36 | /// ### macOS 37 | /// 38 | /// ```swift 39 | /// struct ContentView: View { 40 | /// var body: some View { 41 | /// ScrollView { 42 | /// Text("Item") 43 | /// } 44 | /// .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 45 | /// print(type(of: $0)) // NSScrollView 46 | /// } 47 | /// } 48 | /// } 49 | /// ``` 50 | /// 51 | /// ### visionOS 52 | /// 53 | /// ```swift 54 | /// struct ContentView: View { 55 | /// var body: some View { 56 | /// ScrollView { 57 | /// Text("Item") 58 | /// } 59 | /// .introspect(.scrollView, on: .visionOS(.v1, .v2)) { 60 | /// print(type(of: $0)) // UIScrollView 61 | /// } 62 | /// } 63 | /// } 64 | /// ``` 65 | public struct ScrollViewType: IntrospectableViewType {} 66 | 67 | extension IntrospectableViewType where Self == ScrollViewType { 68 | public static var scrollView: Self { .init() } 69 | } 70 | 71 | #if canImport(UIKit) 72 | extension iOSViewVersion { 73 | public static let v13 = Self(for: .v13) 74 | public static let v14 = Self(for: .v14) 75 | public static let v15 = Self(for: .v15) 76 | public static let v16 = Self(for: .v16) 77 | public static let v17 = Self(for: .v17) 78 | public static let v18 = Self(for: .v18) 79 | } 80 | 81 | extension tvOSViewVersion { 82 | public static let v13 = Self(for: .v13) 83 | public static let v14 = Self(for: .v14) 84 | public static let v15 = Self(for: .v15) 85 | public static let v16 = Self(for: .v16) 86 | public static let v17 = Self(for: .v17) 87 | public static let v18 = Self(for: .v18) 88 | } 89 | 90 | extension visionOSViewVersion { 91 | public static let v1 = Self(for: .v1) 92 | public static let v2 = Self(for: .v2) 93 | } 94 | #elseif canImport(AppKit) 95 | extension macOSViewVersion { 96 | public static let v10_15 = Self(for: .v10_15) 97 | public static let v11 = Self(for: .v11) 98 | public static let v12 = Self(for: .v12) 99 | public static let v13 = Self(for: .v13) 100 | public static let v14 = Self(for: .v14) 101 | public static let v15 = Self(for: .v15) 102 | } 103 | #endif 104 | #endif 105 | -------------------------------------------------------------------------------- /Sources/ViewTypes/SignInWithAppleButton.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `SignInWithAppleButton` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// var body: some View { 11 | /// SignInWithAppleButton(.signIn) { request in 12 | /// request.requestedScopes = [.fullName, .email] 13 | /// } onCompletion: { result in 14 | /// // do something with result 15 | /// } 16 | /// .introspect(.signInWithAppleButton, on: .iOS(.v14, .v15, .v16, .v17, .v18)) { 17 | /// print(type(of: $0)) // ASAuthorizationAppleIDButton 18 | /// } 19 | /// } 20 | /// } 21 | /// ``` 22 | /// 23 | /// ### tvOS 24 | /// 25 | /// ```swift 26 | /// struct ContentView: View { 27 | /// var body: some View { 28 | /// SignInWithAppleButton(.signIn) { request in 29 | /// request.requestedScopes = [.fullName, .email] 30 | /// } onCompletion: { result in 31 | /// // do something with result 32 | /// } 33 | /// .introspect(.signInWithAppleButton, on: .tvOS(.v14, .v15, .v16, .v17, .v18)) { 34 | /// print(type(of: $0)) // ASAuthorizationAppleIDButton 35 | /// } 36 | /// } 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### macOS 41 | /// 42 | /// ```swift 43 | /// struct ContentView: View { 44 | /// var body: some View { 45 | /// SignInWithAppleButton(.signIn) { request in 46 | /// request.requestedScopes = [.fullName, .email] 47 | /// } onCompletion: { result in 48 | /// // do something with result 49 | /// } 50 | /// .introspect(.signInWithAppleButton, on: .macOS(.v11, .v12, .v13, .v14),) { 51 | /// print(type(of: $0)) // ASAuthorizationAppleIDButton 52 | /// } 53 | /// } 54 | /// } 55 | /// ``` 56 | /// 57 | /// ### visionOS 58 | /// 59 | /// ```swift 60 | /// struct ContentView: View { 61 | /// var body: some View { 62 | /// SignInWithAppleButton(.signIn) { request in 63 | /// request.requestedScopes = [.fullName, .email] 64 | /// } onCompletion: { result in 65 | /// // do something with result 66 | /// } 67 | /// .introspect(.signInWithAppleButton, on: .visionOS(.v1, .v2)) { 68 | /// print(type(of: $0)) // ASAuthorizationAppleIDButton 69 | /// } 70 | /// } 71 | /// } 72 | /// ``` 73 | public struct SignInWithAppleButtonType: IntrospectableViewType {} 74 | 75 | extension IntrospectableViewType where Self == SignInWithAppleButtonType { 76 | @available( 77 | *, 78 | unavailable, 79 | message: """ 80 | Due to a mysterious bug on Apple's part that may cause a complete 81 | app hang, the unfortunate decision has been made to remove support 82 | for `SignInWithAppleButton` introspection. 83 | 84 | We apologize for this inconvenience. 85 | 86 | More details can be found at https://github.com/siteline/swiftui-introspect/issues/400 87 | """ 88 | ) 89 | public static var signInWithAppleButton: Self { .init() } 90 | } 91 | #endif 92 | -------------------------------------------------------------------------------- /Sources/ViewTypes/Slider.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Slider` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var selection = 0.5 11 | /// 12 | /// var body: some View { 13 | /// Slider(value: $selection, in: 0...1) 14 | /// .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UISlider 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// Not available. 24 | /// 25 | /// ### macOS 26 | /// 27 | /// ```swift 28 | /// struct ContentView: View { 29 | /// @State var selection = 0.5 30 | /// 31 | /// var body: some View { 32 | /// Slider(value: $selection, in: 0...1) 33 | /// .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 34 | /// print(type(of: $0)) // NSSlider 35 | /// } 36 | /// } 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### visionOS 41 | /// 42 | /// Not available. 43 | public struct SliderType: IntrospectableViewType {} 44 | 45 | #if !os(tvOS) && !os(visionOS) 46 | extension IntrospectableViewType where Self == SliderType { 47 | public static var slider: Self { .init() } 48 | } 49 | 50 | #if canImport(UIKit) 51 | extension iOSViewVersion { 52 | public static let v13 = Self(for: .v13) 53 | public static let v14 = Self(for: .v14) 54 | public static let v15 = Self(for: .v15) 55 | public static let v16 = Self(for: .v16) 56 | public static let v17 = Self(for: .v17) 57 | public static let v18 = Self(for: .v18) 58 | } 59 | #elseif canImport(AppKit) 60 | extension macOSViewVersion { 61 | public static let v10_15 = Self(for: .v10_15) 62 | public static let v11 = Self(for: .v11) 63 | public static let v12 = Self(for: .v12) 64 | public static let v13 = Self(for: .v13) 65 | public static let v14 = Self(for: .v14) 66 | public static let v15 = Self(for: .v15) 67 | } 68 | #endif 69 | #endif 70 | #endif 71 | -------------------------------------------------------------------------------- /Sources/ViewTypes/Stepper.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Stepper` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var selection = 5 11 | /// 12 | /// var body: some View { 13 | /// Stepper("Select a number", value: $selection, in: 0...10) 14 | /// .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UIStepper 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// Not available. 24 | /// 25 | /// ### macOS 26 | /// 27 | /// ```swift 28 | /// struct ContentView: View { 29 | /// @State var selection = 5 30 | /// 31 | /// var body: some View { 32 | /// Stepper("Select a number", value: $selection, in: 0...10) 33 | /// .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 34 | /// print(type(of: $0)) // NSStepper 35 | /// } 36 | /// } 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### visionOS 41 | /// 42 | /// Not available. 43 | public struct StepperType: IntrospectableViewType {} 44 | 45 | #if !os(tvOS) && !os(visionOS) 46 | extension IntrospectableViewType where Self == StepperType { 47 | public static var stepper: Self { .init() } 48 | } 49 | 50 | #if canImport(UIKit) 51 | extension iOSViewVersion { 52 | public static let v13 = Self(for: .v13) 53 | public static let v14 = Self(for: .v14) 54 | public static let v15 = Self(for: .v15) 55 | public static let v16 = Self(for: .v16) 56 | public static let v17 = Self(for: .v17) 57 | public static let v18 = Self(for: .v18) 58 | } 59 | #elseif canImport(AppKit) 60 | extension macOSViewVersion { 61 | public static let v10_15 = Self(for: .v10_15) 62 | public static let v11 = Self(for: .v11) 63 | public static let v12 = Self(for: .v12) 64 | public static let v13 = Self(for: .v13) 65 | public static let v14 = Self(for: .v14) 66 | public static let v15 = Self(for: .v15) 67 | } 68 | #endif 69 | #endif 70 | #endif 71 | -------------------------------------------------------------------------------- /Sources/ViewTypes/TextEditor.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `TextEditor` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var text = "Lorem ipsum" 11 | /// 12 | /// var body: some View { 13 | /// TextEditor(text: $text) 14 | /// .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UITextView 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// Not available. 24 | /// 25 | /// ### macOS 26 | /// 27 | /// ```swift 28 | /// struct ContentView: View { 29 | /// @State var text = "Lorem ipsum" 30 | /// 31 | /// var body: some View { 32 | /// TextEditor(text: $text) 33 | /// .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14, .v15)) { 34 | /// print(type(of: $0)) // NSTextView 35 | /// } 36 | /// } 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### visionOS 41 | /// 42 | /// ```swift 43 | /// struct ContentView: View { 44 | /// @State var text = "Lorem ipsum" 45 | /// 46 | /// var body: some View { 47 | /// TextEditor(text: $text) 48 | /// .introspect(.textEditor, on: .visionOS(.v1, .v2)) { 49 | /// print(type(of: $0)) // UITextView 50 | /// } 51 | /// } 52 | /// } 53 | /// ``` 54 | public struct TextEditorType: IntrospectableViewType {} 55 | 56 | #if !os(tvOS) 57 | extension IntrospectableViewType where Self == TextEditorType { 58 | public static var textEditor: Self { .init() } 59 | } 60 | 61 | #if canImport(UIKit) 62 | extension iOSViewVersion { 63 | @available(*, unavailable, message: "TextEditor isn't available on iOS 13") 64 | public static let v13 = Self.unavailable() 65 | public static let v14 = Self(for: .v14) 66 | public static let v15 = Self(for: .v15) 67 | public static let v16 = Self(for: .v16) 68 | public static let v17 = Self(for: .v17) 69 | public static let v18 = Self(for: .v18) 70 | } 71 | 72 | extension visionOSViewVersion { 73 | public static let v1 = Self(for: .v1) 74 | public static let v2 = Self(for: .v2) 75 | } 76 | #elseif canImport(AppKit) 77 | extension macOSViewVersion { 78 | @available(*, unavailable, message: "TextEditor isn't available on macOS 10.15") 79 | public static let v10_15 = Self.unavailable() 80 | public static let v11 = Self(for: .v11) 81 | public static let v12 = Self(for: .v12) 82 | public static let v13 = Self(for: .v13) 83 | public static let v14 = Self(for: .v14) 84 | public static let v15 = Self(for: .v15) 85 | } 86 | #endif 87 | #endif 88 | #endif 89 | -------------------------------------------------------------------------------- /Sources/ViewTypes/TextField.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `TextField` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var text = "Lorem ipsum" 11 | /// 12 | /// var body: some View { 13 | /// TextField("Text Field", text: $text) 14 | /// .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UITextField 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// ```swift 24 | /// struct ContentView: View { 25 | /// @State var text = "Lorem ipsum" 26 | /// 27 | /// var body: some View { 28 | /// TextField("Text Field", text: $text) 29 | /// .introspect(.textField, on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 30 | /// print(type(of: $0)) // UITextField 31 | /// } 32 | /// } 33 | /// } 34 | /// ``` 35 | /// 36 | /// ### macOS 37 | /// 38 | /// ```swift 39 | /// struct ContentView: View { 40 | /// @State var text = "Lorem ipsum" 41 | /// 42 | /// var body: some View { 43 | /// TextField("Text Field", text: $text) 44 | /// .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 45 | /// print(type(of: $0)) // NSTextField 46 | /// } 47 | /// } 48 | /// } 49 | /// ``` 50 | /// 51 | /// ### visionOS 52 | /// 53 | /// ```swift 54 | /// struct ContentView: View { 55 | /// @State var text = "Lorem ipsum" 56 | /// 57 | /// var body: some View { 58 | /// TextField("Text Field", text: $text) 59 | /// .introspect(.textField, on: .visionOS(.v1, .v2)) { 60 | /// print(type(of: $0)) // UITextField 61 | /// } 62 | /// } 63 | /// } 64 | /// ``` 65 | public struct TextFieldType: IntrospectableViewType {} 66 | 67 | extension IntrospectableViewType where Self == TextFieldType { 68 | public static var textField: Self { .init() } 69 | } 70 | 71 | #if canImport(UIKit) 72 | extension iOSViewVersion { 73 | public static let v13 = Self(for: .v13) 74 | public static let v14 = Self(for: .v14) 75 | public static let v15 = Self(for: .v15) 76 | public static let v16 = Self(for: .v16) 77 | public static let v17 = Self(for: .v17) 78 | public static let v18 = Self(for: .v18) 79 | } 80 | 81 | extension tvOSViewVersion { 82 | public static let v13 = Self(for: .v13) 83 | public static let v14 = Self(for: .v14) 84 | public static let v15 = Self(for: .v15) 85 | public static let v16 = Self(for: .v16) 86 | public static let v17 = Self(for: .v17) 87 | public static let v18 = Self(for: .v18) 88 | } 89 | 90 | extension visionOSViewVersion { 91 | public static let v1 = Self(for: .v1) 92 | public static let v2 = Self(for: .v2) 93 | } 94 | #elseif canImport(AppKit) 95 | extension macOSViewVersion { 96 | public static let v10_15 = Self(for: .v10_15) 97 | public static let v11 = Self(for: .v11) 98 | public static let v12 = Self(for: .v12) 99 | public static let v13 = Self(for: .v13) 100 | public static let v14 = Self(for: .v14) 101 | public static let v15 = Self(for: .v15) 102 | } 103 | #endif 104 | #endif 105 | -------------------------------------------------------------------------------- /Sources/ViewTypes/Toggle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Toggle` type in SwiftUI. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var isOn = false 11 | /// 12 | /// var body: some View { 13 | /// Toggle("Toggle", isOn: $isOn) 14 | /// .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 15 | /// print(type(of: $0)) // UISwitch 16 | /// } 17 | /// } 18 | /// } 19 | /// ``` 20 | /// 21 | /// ### tvOS 22 | /// 23 | /// Not available. 24 | /// 25 | /// ### macOS 26 | /// 27 | /// ```swift 28 | /// struct ContentView: View { 29 | /// @State var isOn = false 30 | /// 31 | /// var body: some View { 32 | /// Toggle("Toggle", isOn: $isOn) 33 | /// .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 34 | /// print(type(of: $0)) // NSButton 35 | /// } 36 | /// } 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### visionOS 41 | /// 42 | /// Not available. 43 | public struct ToggleType: IntrospectableViewType {} 44 | 45 | #if !os(tvOS) && !os(visionOS) 46 | extension IntrospectableViewType where Self == ToggleType { 47 | public static var toggle: Self { .init() } 48 | } 49 | 50 | #if canImport(UIKit) 51 | extension iOSViewVersion { 52 | public static let v13 = Self(for: .v13) 53 | public static let v14 = Self(for: .v14) 54 | public static let v15 = Self(for: .v15) 55 | public static let v16 = Self(for: .v16) 56 | public static let v17 = Self(for: .v17) 57 | public static let v18 = Self(for: .v18) 58 | } 59 | #elseif canImport(AppKit) 60 | extension macOSViewVersion { 61 | public static let v10_15 = Self(for: .v10_15) 62 | public static let v11 = Self(for: .v11) 63 | public static let v12 = Self(for: .v12) 64 | public static let v13 = Self(for: .v13) 65 | public static let v14 = Self(for: .v14) 66 | public static let v15 = Self(for: .v15) 67 | } 68 | #endif 69 | #endif 70 | #endif 71 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ToggleWithButtonStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Toggle` type in SwiftUI, with `.button` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// Not available. 9 | /// 10 | /// ### tvOS 11 | /// 12 | /// Not available. 13 | /// 14 | /// ### macOS 15 | /// 16 | /// ```swift 17 | /// struct ContentView: View { 18 | /// @State var isOn = false 19 | /// 20 | /// var body: some View { 21 | /// Toggle("Toggle", isOn: $isOn) 22 | /// .toggleStyle(.button) 23 | /// .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14, .v15)) { 24 | /// print(type(of: $0)) // NSButton 25 | /// } 26 | /// } 27 | /// } 28 | /// ``` 29 | /// 30 | /// ### visionOS 31 | /// 32 | /// Not available. 33 | public struct ToggleWithButtonStyleType: IntrospectableViewType { 34 | public enum Style: Sendable { 35 | case button 36 | } 37 | } 38 | 39 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 40 | extension IntrospectableViewType where Self == ToggleWithButtonStyleType { 41 | public static func toggle(style: Self.Style) -> Self { .init() } 42 | } 43 | 44 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 45 | extension macOSViewVersion { 46 | @available(*, unavailable, message: ".toggleStyle(.button) isn't available on macOS 10.15") 47 | public static let v10_15 = Self.unavailable() 48 | @available(*, unavailable, message: ".toggleStyle(.button) isn't available on macOS 11") 49 | public static let v11 = Self.unavailable() 50 | public static let v12 = Self(for: .v12) 51 | public static let v13 = Self(for: .v13) 52 | public static let v14 = Self(for: .v14) 53 | public static let v15 = Self(for: .v15) 54 | } 55 | #endif 56 | #endif 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ToggleWithCheckboxStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Toggle` type in SwiftUI, with `.checkbox` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// Not available. 9 | /// 10 | /// ### tvOS 11 | /// 12 | /// Not available. 13 | /// 14 | /// ### macOS 15 | /// 16 | /// ```swift 17 | /// struct ContentView: View { 18 | /// @State var isOn = false 19 | /// 20 | /// var body: some View { 21 | /// Toggle("Checkbox", isOn: $isOn) 22 | /// .toggleStyle(.checkbox) 23 | /// .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 24 | /// print(type(of: $0)) // NSButton 25 | /// } 26 | /// } 27 | /// } 28 | /// ``` 29 | /// 30 | /// ### visionOS 31 | /// 32 | /// Not available. 33 | public struct ToggleWithCheckboxStyleType: IntrospectableViewType { 34 | public enum Style: Sendable { 35 | case checkbox 36 | } 37 | } 38 | 39 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 40 | extension IntrospectableViewType where Self == ToggleWithCheckboxStyleType { 41 | public static func toggle(style: Self.Style) -> Self { .init() } 42 | } 43 | 44 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 45 | extension macOSViewVersion { 46 | public static let v10_15 = Self(for: .v10_15) 47 | public static let v11 = Self(for: .v11) 48 | public static let v12 = Self(for: .v12) 49 | public static let v13 = Self(for: .v13) 50 | public static let v14 = Self(for: .v14) 51 | public static let v15 = Self(for: .v15) 52 | } 53 | #endif 54 | #endif 55 | #endif 56 | -------------------------------------------------------------------------------- /Sources/ViewTypes/ToggleWithSwitchStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of the `Toggle` type in SwiftUI, with `.switch` style. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// @State var isOn = false 11 | /// 12 | /// var body: some View { 13 | /// Toggle("Switch", isOn: $isOn) 14 | /// .toggleStyle(.switch) 15 | /// .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 16 | /// print(type(of: $0)) // UISwitch 17 | /// } 18 | /// } 19 | /// } 20 | /// ``` 21 | /// 22 | /// ### tvOS 23 | /// 24 | /// Not available. 25 | /// 26 | /// ### macOS 27 | /// 28 | /// ```swift 29 | /// struct ContentView: View { 30 | /// @State var isOn = false 31 | /// 32 | /// var body: some View { 33 | /// Toggle("Switch", isOn: $isOn) 34 | /// .toggleStyle(.switch) 35 | /// .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 36 | /// print(type(of: $0)) // NSSwitch 37 | /// } 38 | /// } 39 | /// } 40 | /// ``` 41 | /// 42 | /// ### visionOS 43 | /// 44 | /// Not available. 45 | public struct ToggleWithSwitchStyleType: IntrospectableViewType { 46 | public enum Style: Sendable { 47 | case `switch` 48 | } 49 | } 50 | 51 | #if !os(tvOS) && !os(visionOS) 52 | extension IntrospectableViewType where Self == ToggleWithSwitchStyleType { 53 | public static func toggle(style: Self.Style) -> Self { .init() } 54 | } 55 | 56 | #if canImport(UIKit) 57 | extension iOSViewVersion { 58 | public static let v13 = Self(for: .v13) 59 | public static let v14 = Self(for: .v14) 60 | public static let v15 = Self(for: .v15) 61 | public static let v16 = Self(for: .v16) 62 | public static let v17 = Self(for: .v17) 63 | public static let v18 = Self(for: .v18) 64 | } 65 | #elseif canImport(AppKit) 66 | extension macOSViewVersion { 67 | public static let v10_15 = Self(for: .v10_15) 68 | public static let v11 = Self(for: .v11) 69 | public static let v12 = Self(for: .v12) 70 | public static let v13 = Self(for: .v13) 71 | public static let v14 = Self(for: .v14) 72 | public static let v15 = Self(for: .v15) 73 | } 74 | #endif 75 | #endif 76 | #endif 77 | -------------------------------------------------------------------------------- /Sources/ViewTypes/View.swift: -------------------------------------------------------------------------------- 1 | #if !os(watchOS) 2 | import SwiftUI 3 | 4 | /// An abstract representation of a generic SwiftUI view type. 5 | /// 6 | /// ### iOS 7 | /// 8 | /// ```swift 9 | /// struct ContentView: View { 10 | /// var body: some View { 11 | /// HStack { 12 | /// Image(systemName: "scribble") 13 | /// Text("Some text") 14 | /// } 15 | /// .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 16 | /// print(type(of: $0)) // some subclass of UIView 17 | /// } 18 | /// } 19 | /// } 20 | /// ``` 21 | /// 22 | /// ### tvOS 23 | /// 24 | /// ```swift 25 | /// struct ContentView: View { 26 | /// var body: some View { 27 | /// HStack { 28 | /// Image(systemName: "scribble") 29 | /// Text("Some text") 30 | /// } 31 | /// .introspect(.view, on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 32 | /// print(type(of: $0)) // some subclass of UIView 33 | /// } 34 | /// } 35 | /// } 36 | /// ``` 37 | /// 38 | /// ### macOS 39 | /// 40 | /// ```swift 41 | /// struct ContentView: View { 42 | /// var body: some View { 43 | /// HStack { 44 | /// Image(systemName: "scribble") 45 | /// Text("Some text") 46 | /// } 47 | /// .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) { 48 | /// print(type(of: $0)) // some subclass of NSView 49 | /// } 50 | /// } 51 | /// } 52 | /// ``` 53 | /// 54 | /// ### visionOS 55 | /// 56 | /// ```swift 57 | /// struct ContentView: View { 58 | /// var body: some View { 59 | /// HStack { 60 | /// Image(systemName: "scribble") 61 | /// Text("Some text") 62 | /// } 63 | /// .introspect(.view, on: .visionOS(.v1, .v2)) { 64 | /// print(type(of: $0)) // some subclass of UIView 65 | /// } 66 | /// } 67 | /// } 68 | /// ``` 69 | public struct ViewType: IntrospectableViewType {} 70 | 71 | extension IntrospectableViewType where Self == ViewType { 72 | public static var view: Self { .init() } 73 | } 74 | 75 | #if canImport(UIKit) 76 | extension iOSViewVersion { 77 | public static let v13 = Self(for: .v13) 78 | public static let v14 = Self(for: .v14) 79 | public static let v15 = Self(for: .v15) 80 | public static let v16 = Self(for: .v16) 81 | public static let v17 = Self(for: .v17) 82 | public static let v18 = Self(for: .v18) 83 | } 84 | 85 | extension tvOSViewVersion { 86 | public static let v13 = Self(for: .v13) 87 | public static let v14 = Self(for: .v14) 88 | public static let v15 = Self(for: .v15) 89 | public static let v16 = Self(for: .v16) 90 | public static let v17 = Self(for: .v17) 91 | public static let v18 = Self(for: .v18) 92 | } 93 | 94 | extension visionOSViewVersion { 95 | public static let v1 = Self(for: .v1) 96 | public static let v2 = Self(for: .v2) 97 | } 98 | #elseif canImport(AppKit) 99 | extension macOSViewVersion { 100 | public static let v10_15 = Self(for: .v10_15) 101 | public static let v11 = Self(for: .v11) 102 | public static let v12 = Self(for: .v12) 103 | public static let v13 = Self(for: .v13) 104 | public static let v14 = Self(for: .v14) 105 | public static let v15 = Self(for: .v15) 106 | } 107 | #endif 108 | #endif 109 | -------------------------------------------------------------------------------- /Sources/Weak.swift: -------------------------------------------------------------------------------- 1 | @_spi(Advanced) 2 | @propertyWrapper 3 | public final class Weak { 4 | private weak var _wrappedValue: T? 5 | 6 | public var wrappedValue: T? { 7 | get { _wrappedValue } 8 | set { _wrappedValue = newValue } 9 | } 10 | 11 | public init(wrappedValue: T? = nil) { 12 | self._wrappedValue = wrappedValue 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SwiftUIIntrospect.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'SwiftUIIntrospect' 3 | spec.version = ENV['LIB_VERSION'] 4 | spec.license = { type: 'MIT' } 5 | spec.homepage = 'https://github.com/siteline/swiftui-introspect' 6 | spec.author = 'David Roman' 7 | spec.summary = 'Introspect underlying UIKit/AppKit components from SwiftUI.' 8 | spec.source = { 9 | git: 'https://github.com/siteline/swiftui-introspect.git', 10 | tag: spec.version 11 | } 12 | 13 | spec.source_files = 'Sources/**/*.swift' 14 | 15 | spec.swift_version = '5.7' 16 | spec.ios.deployment_target = '13.0' 17 | spec.tvos.deployment_target = '13.0' 18 | spec.osx.deployment_target = '10.15' 19 | spec.visionos.deployment_target = '1.0' 20 | end 21 | -------------------------------------------------------------------------------- /SwiftUIIntrospect.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /SwiftUIIntrospect.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUIIntrospect.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUIIntrospect.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-snapshot-testing", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", 7 | "state" : { 8 | "revision" : "dc46eeb3928a75390651fac6c1ef7f93ad59a73b", 9 | "version" : "1.11.1" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /SwiftUIIntrospect.xcworkspace/xcshareddata/xcschemes/SwiftUIIntrospect.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Tests/LegacyTestsHostApp/LegacyTestsHostApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | final class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | window = UIWindow(frame: UIScreen.main.bounds) 10 | window?.rootViewController = UIHostingController(rootView: EmptyView()) 11 | window?.makeKeyAndVisible() 12 | return true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Tests", 7 | products: [], 8 | targets: [] 9 | ) 10 | -------------------------------------------------------------------------------- /Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/Tests.xcodeproj/xcshareddata/xcschemes/LegacySwiftUIIntrospectTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 39 | 45 | 46 | 47 | 48 | 54 | 55 | 61 | 62 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Tests/Tests.xcodeproj/xcshareddata/xcschemes/SwiftUIIntrospectUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 36 | 37 | 47 | 49 | 55 | 56 | 57 | 58 | 64 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ButtonTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class ButtonTests: XCTestCase { 8 | #if canImport(AppKit) 9 | typealias PlatformButton = NSButton 10 | #endif 11 | 12 | func testButton() { 13 | XCTAssertViewIntrospection(of: PlatformButton.self) { spies in 14 | let spy0 = spies[0] 15 | let spy1 = spies[1] 16 | let spy2 = spies[2] 17 | let spy3 = spies[3] 18 | 19 | VStack { 20 | Button("Button 0", action: {}) 21 | .buttonStyle(.bordered) 22 | #if os(macOS) 23 | .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 24 | #endif 25 | 26 | Button("Button 1", action: {}) 27 | .buttonStyle(.borderless) 28 | #if os(macOS) 29 | .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 30 | #endif 31 | 32 | Button("Button 2", action: {}) 33 | .buttonStyle(.link) 34 | #if os(macOS) 35 | .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 36 | #endif 37 | 38 | Button("Button 3", action: {}) 39 | #if os(macOS) 40 | .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy3) 41 | #endif 42 | } 43 | } extraAssertions: { 44 | #if canImport(AppKit) 45 | XCTAssert(Set($0.map(ObjectIdentifier.init)).count == 4) 46 | #endif 47 | } 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ColorPickerTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, macOS 11, *) 7 | @MainActor 8 | final class ColorPickerTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformColor = UIColor 11 | typealias PlatformColorPicker = UIColorWell 12 | #elseif canImport(AppKit) 13 | typealias PlatformColor = NSColor 14 | typealias PlatformColorPicker = NSColorWell 15 | #endif 16 | 17 | func testColorPicker() throws { 18 | guard #available(iOS 14, macOS 11, *) else { 19 | throw XCTSkip() 20 | } 21 | 22 | XCTAssertViewIntrospection(of: PlatformColorPicker.self) { spies in 23 | let spy0 = spies[0] 24 | let spy1 = spies[1] 25 | let spy2 = spies[2] 26 | 27 | VStack { 28 | ColorPicker("", selection: .constant(PlatformColor.red.cgColor)) 29 | #if os(iOS) || os(visionOS) 30 | .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 31 | #elseif os(macOS) 32 | .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) 33 | #endif 34 | 35 | ColorPicker("", selection: .constant(PlatformColor.green.cgColor)) 36 | #if os(iOS) || os(visionOS) 37 | .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 38 | #elseif os(macOS) 39 | .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) 40 | #endif 41 | 42 | ColorPicker("", selection: .constant(PlatformColor.blue.cgColor)) 43 | #if os(iOS) || os(visionOS) 44 | .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 45 | #elseif os(macOS) 46 | .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) 47 | #endif 48 | } 49 | } extraAssertions: { 50 | #if canImport(UIKit) 51 | XCTAssertEqual($0[safe: 0]?.selectedColor, .red) 52 | XCTAssertEqual($0[safe: 1]?.selectedColor, .green) 53 | XCTAssertEqual($0[safe: 2]?.selectedColor, .blue) 54 | #elseif canImport(AppKit) 55 | XCTAssertEqual($0[safe: 0]?.color, .red) 56 | XCTAssertEqual($0[safe: 1]?.color, .green) 57 | XCTAssertEqual($0[safe: 2]?.color, .blue) 58 | #endif 59 | } 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/DatePickerTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class DatePickerTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformDatePicker = UIDatePicker 10 | #elseif canImport(AppKit) 11 | typealias PlatformDatePicker = NSDatePicker 12 | #endif 13 | 14 | func testDatePicker() { 15 | let date0 = Date(timeIntervalSince1970: 0) 16 | let date1 = Date(timeIntervalSince1970: 5) 17 | let date2 = Date(timeIntervalSince1970: 10) 18 | 19 | XCTAssertViewIntrospection(of: PlatformDatePicker.self) { spies in 20 | let spy0 = spies[0] 21 | let spy1 = spies[1] 22 | let spy2 = spies[2] 23 | 24 | VStack { 25 | DatePicker("", selection: .constant(date0)) 26 | #if os(iOS) || os(visionOS) 27 | .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 28 | #elseif os(macOS) 29 | .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 30 | #endif 31 | .cornerRadius(8) 32 | 33 | DatePicker("", selection: .constant(date1)) 34 | #if os(iOS) || os(visionOS) 35 | .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 36 | #elseif os(macOS) 37 | .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 38 | #endif 39 | .cornerRadius(8) 40 | 41 | DatePicker("", selection: .constant(date2)) 42 | #if os(iOS) || os(visionOS) 43 | .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 44 | #elseif os(macOS) 45 | .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 46 | #endif 47 | } 48 | } extraAssertions: { 49 | #if canImport(UIKit) 50 | XCTAssertEqual($0[safe: 0]?.date, date0) 51 | XCTAssertEqual($0[safe: 1]?.date, date1) 52 | XCTAssertEqual($0[safe: 2]?.date, date2) 53 | #elseif canImport(AppKit) 54 | XCTAssertEqual($0[safe: 0]?.dateValue, date0) 55 | XCTAssertEqual($0[safe: 1]?.dateValue, date1) 56 | XCTAssertEqual($0[safe: 2]?.dateValue, date2) 57 | #endif 58 | } 59 | } 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/DatePickerWithCompactFieldStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, macOS 10.15.4, *) 7 | @MainActor 8 | final class DatePickerWithCompactStyleTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformDatePickerWithCompactStyle = UIDatePicker 11 | #elseif canImport(AppKit) 12 | typealias PlatformDatePickerWithCompactStyle = NSDatePicker 13 | #endif 14 | 15 | func testDatePickerWithCompactStyle() throws { 16 | guard #available(iOS 14, macOS 10.15.4, *) else { 17 | throw XCTSkip() 18 | } 19 | 20 | let date0 = Date(timeIntervalSince1970: 0) 21 | let date1 = Date(timeIntervalSince1970: 5) 22 | let date2 = Date(timeIntervalSince1970: 10) 23 | 24 | XCTAssertViewIntrospection(of: PlatformDatePickerWithCompactStyle.self) { spies in 25 | let spy0 = spies[0] 26 | let spy1 = spies[1] 27 | let spy2 = spies[2] 28 | 29 | VStack { 30 | DatePicker("", selection: .constant(date0)) 31 | .datePickerStyle(.compact) 32 | #if os(iOS) || os(visionOS) 33 | .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 34 | #elseif os(macOS) 35 | .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14), customize: spy0) 36 | #endif 37 | .cornerRadius(8) 38 | 39 | DatePicker("", selection: .constant(date1)) 40 | .datePickerStyle(.compact) 41 | #if os(iOS) || os(visionOS) 42 | .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 43 | #elseif os(macOS) 44 | .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14), customize: spy1) 45 | #endif 46 | .cornerRadius(8) 47 | 48 | DatePicker("", selection: .constant(date2)) 49 | .datePickerStyle(.compact) 50 | #if os(iOS) || os(visionOS) 51 | .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 52 | #elseif os(macOS) 53 | .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14), customize: spy2) 54 | #endif 55 | } 56 | } extraAssertions: { 57 | #if canImport(UIKit) 58 | XCTAssertEqual($0[safe: 0]?.date, date0) 59 | XCTAssertEqual($0[safe: 1]?.date, date1) 60 | XCTAssertEqual($0[safe: 2]?.date, date2) 61 | #elseif canImport(AppKit) 62 | XCTAssertEqual($0[safe: 0]?.dateValue, date0) 63 | XCTAssertEqual($0[safe: 1]?.dateValue, date1) 64 | XCTAssertEqual($0[safe: 2]?.dateValue, date2) 65 | #endif 66 | } 67 | } 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/DatePickerWithFieldStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class DatePickerWithFieldStyleTests: XCTestCase { 8 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 9 | typealias PlatformDatePickerWithFieldStyle = NSDatePicker 10 | #endif 11 | 12 | func testDatePickerWithFieldStyle() { 13 | let date0 = Date(timeIntervalSince1970: 0) 14 | let date1 = Date(timeIntervalSince1970: 5) 15 | let date2 = Date(timeIntervalSince1970: 10) 16 | 17 | XCTAssertViewIntrospection(of: PlatformDatePickerWithFieldStyle.self) { spies in 18 | let spy0 = spies[0] 19 | let spy1 = spies[1] 20 | let spy2 = spies[2] 21 | 22 | VStack { 23 | DatePicker("", selection: .constant(date0)) 24 | .datePickerStyle(.field) 25 | #if os(macOS) 26 | .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 27 | #endif 28 | .cornerRadius(8) 29 | 30 | DatePicker("", selection: .constant(date1)) 31 | .datePickerStyle(.field) 32 | #if os(macOS) 33 | .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 34 | #endif 35 | .cornerRadius(8) 36 | 37 | DatePicker("", selection: .constant(date2)) 38 | .datePickerStyle(.field) 39 | #if os(macOS) 40 | .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 41 | #endif 42 | } 43 | } extraAssertions: { 44 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 45 | XCTAssertEqual($0[safe: 0]?.dateValue, date0) 46 | XCTAssertEqual($0[safe: 1]?.dateValue, date1) 47 | XCTAssertEqual($0[safe: 2]?.dateValue, date2) 48 | #endif 49 | } 50 | } 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/DatePickerWithGraphicalStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, *) 7 | @MainActor 8 | final class DatePickerWithGraphicalStyleTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformDatePickerWithGraphicalStyle = UIDatePicker 11 | #elseif canImport(AppKit) 12 | typealias PlatformDatePickerWithGraphicalStyle = NSDatePicker 13 | #endif 14 | 15 | func testDatePickerWithGraphicalStyle() throws { 16 | guard #available(iOS 14, *) else { 17 | throw XCTSkip() 18 | } 19 | 20 | let date0 = Date(timeIntervalSince1970: 0) 21 | let date1 = Date(timeIntervalSince1970: 3600 * 24 * 1) 22 | let date2 = Date(timeIntervalSince1970: 3600 * 24 * 2) 23 | 24 | XCTAssertViewIntrospection(of: PlatformDatePickerWithGraphicalStyle.self) { spies in 25 | let spy0 = spies[0] 26 | let spy1 = spies[1] 27 | let spy2 = spies[2] 28 | 29 | VStack { 30 | DatePicker("", selection: .constant(date0)) 31 | .datePickerStyle(.graphical) 32 | #if os(iOS) || os(visionOS) 33 | .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 34 | #elseif os(macOS) 35 | .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 36 | #endif 37 | .cornerRadius(8) 38 | 39 | DatePicker("", selection: .constant(date1)) 40 | .datePickerStyle(.graphical) 41 | #if os(iOS) || os(visionOS) 42 | .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 43 | #elseif os(macOS) 44 | .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 45 | #endif 46 | .cornerRadius(8) 47 | 48 | DatePicker("", selection: .constant(date2)) 49 | .datePickerStyle(.graphical) 50 | #if os(iOS) || os(visionOS) 51 | .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 52 | #elseif os(macOS) 53 | .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 54 | #endif 55 | } 56 | } extraAssertions: { 57 | #if canImport(UIKit) 58 | XCTAssertEqual($0[safe: 0]?.date, date0) 59 | XCTAssertEqual($0[safe: 1]?.date, date1) 60 | XCTAssertEqual($0[safe: 2]?.date, date2) 61 | #elseif canImport(AppKit) 62 | XCTAssertEqual($0[safe: 0]?.dateValue, date0) 63 | XCTAssertEqual($0[safe: 1]?.dateValue, date1) 64 | XCTAssertEqual($0[safe: 2]?.dateValue, date2) 65 | #endif 66 | } 67 | } 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/DatePickerWithStepperFieldStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class DatePickerWithStepperFieldStyleTests: XCTestCase { 8 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 9 | typealias PlatformDatePickerWithStepperFieldStyle = NSDatePicker 10 | #endif 11 | 12 | func testDatePickerWithStepperFieldStyle() { 13 | let date0 = Date(timeIntervalSince1970: 0) 14 | let date1 = Date(timeIntervalSince1970: 5) 15 | let date2 = Date(timeIntervalSince1970: 10) 16 | 17 | XCTAssertViewIntrospection(of: PlatformDatePickerWithStepperFieldStyle.self) { spies in 18 | let spy0 = spies[0] 19 | let spy1 = spies[1] 20 | let spy2 = spies[2] 21 | 22 | VStack { 23 | DatePicker("", selection: .constant(date0)) 24 | .datePickerStyle(.stepperField) 25 | #if os(macOS) 26 | .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 27 | #endif 28 | .cornerRadius(8) 29 | 30 | DatePicker("", selection: .constant(date1)) 31 | .datePickerStyle(.stepperField) 32 | #if os(macOS) 33 | .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 34 | #endif 35 | .cornerRadius(8) 36 | 37 | DatePicker("", selection: .constant(date2)) 38 | .datePickerStyle(.stepperField) 39 | #if os(macOS) 40 | .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 41 | #endif 42 | } 43 | } extraAssertions: { 44 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 45 | XCTAssertEqual($0[safe: 0]?.dateValue, date0) 46 | XCTAssertEqual($0[safe: 1]?.dateValue, date1) 47 | XCTAssertEqual($0[safe: 2]?.dateValue, date2) 48 | #endif 49 | } 50 | } 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/DatePickerWithWheelStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class DatePickerWithWheelStyleTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformDatePickerWithWheelStyle = UIDatePicker 10 | #endif 11 | 12 | func testDatePickerWithWheelStyle() { 13 | let date0 = Date(timeIntervalSince1970: 0) 14 | let date1 = Date(timeIntervalSince1970: 5) 15 | let date2 = Date(timeIntervalSince1970: 10) 16 | 17 | XCTAssertViewIntrospection(of: PlatformDatePickerWithWheelStyle.self) { spies in 18 | let spy0 = spies[0] 19 | let spy1 = spies[1] 20 | let spy2 = spies[2] 21 | 22 | VStack { 23 | DatePicker("", selection: .constant(date0)) 24 | .datePickerStyle(.wheel) 25 | #if os(iOS) || os(visionOS) 26 | .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 27 | #endif 28 | .cornerRadius(8) 29 | 30 | DatePicker("", selection: .constant(date1)) 31 | .datePickerStyle(.wheel) 32 | #if os(iOS) || os(visionOS) 33 | .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 34 | #endif 35 | .cornerRadius(8) 36 | 37 | DatePicker("", selection: .constant(date2)) 38 | .datePickerStyle(.wheel) 39 | #if os(iOS) || os(visionOS) 40 | .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 41 | #endif 42 | } 43 | } extraAssertions: { 44 | #if canImport(UIKit) 45 | XCTAssertEqual($0[safe: 0]?.date, date0) 46 | XCTAssertEqual($0[safe: 1]?.date, date1) 47 | XCTAssertEqual($0[safe: 2]?.date, date2) 48 | #endif 49 | } 50 | } 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/FormTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class FormTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformForm = UIScrollView // covers both UITableView and UICollectionView 10 | #elseif canImport(AppKit) 11 | typealias PlatformForm = NSScrollView 12 | #endif 13 | 14 | func testForm() throws { 15 | XCTAssertViewIntrospection(of: PlatformForm.self) { spies in 16 | let spy0 = spies[0] 17 | let spy1 = spies[1] 18 | 19 | HStack { 20 | Form { 21 | Text("Item 1") 22 | } 23 | #if os(iOS) || os(tvOS) || os(visionOS) 24 | .introspect(.form, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { spy0($0) } 25 | .introspect(.form, on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy0($0) } 26 | #endif 27 | 28 | Form { 29 | Text("Item 1") 30 | #if os(iOS) || os(tvOS) || os(visionOS) 31 | .introspect(.form, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), scope: .ancestor) { spy1($0) } 32 | .introspect(.form, on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor) { spy1($0) } 33 | #endif 34 | } 35 | } 36 | } extraAssertions: { 37 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 38 | } 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/FormWithGroupedStyleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @available(iOS 16, tvOS 16, macOS 13, *) 6 | @MainActor 7 | final class FormWithGroupedStyleTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformFormWithGroupedStyle = UIScrollView // covers both UITableView and UICollectionView 10 | #elseif canImport(AppKit) 11 | typealias PlatformFormWithGroupedStyle = NSScrollView 12 | #endif 13 | 14 | func testFormWithGroupedStyle() throws { 15 | guard #available(iOS 16, tvOS 16, macOS 13, *) else { 16 | throw XCTSkip() 17 | } 18 | 19 | XCTAssertViewIntrospection(of: PlatformFormWithGroupedStyle.self) { spies in 20 | let spy0 = spies[0] 21 | let spy1 = spies[1] 22 | 23 | HStack { 24 | Form { 25 | Text("Item 1") 26 | } 27 | .formStyle(.grouped) 28 | #if os(iOS) || os(tvOS) || os(visionOS) 29 | .introspect(.form(style: .grouped), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy0($0) } 30 | .introspect(.form(style: .grouped), on: .tvOS(.v16, .v17, .v18)) { spy0($0) } 31 | #elseif os(macOS) 32 | .introspect(.form(style: .grouped), on: .macOS(.v13, .v14)) { spy0($0) } 33 | #endif 34 | 35 | Form { 36 | Text("Item 1") 37 | #if os(iOS) || os(tvOS) || os(visionOS) 38 | .introspect(.form(style: .grouped), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor) { spy1($0) } 39 | .introspect(.form(style: .grouped), on: .tvOS(.v16, .v17, .v18), scope: .ancestor) { spy1($0) } 40 | #elseif os(macOS) 41 | .introspect(.form(style: .grouped), on: .macOS(.v13, .v14), scope: .ancestor) { spy1($0) } 42 | #endif 43 | } 44 | .formStyle(.grouped) 45 | } 46 | } extraAssertions: { 47 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/FullScreenCoverTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) && !targetEnvironment(macCatalyst) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, tvOS 14, *) 7 | @MainActor 8 | final class FullScreenCoverTests: XCTestCase { 9 | func testPresentationAsFullScreenCover() throws { 10 | guard #available(iOS 14, tvOS 14, *) else { 11 | throw XCTSkip() 12 | } 13 | 14 | XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in 15 | let spy0 = spies[0] 16 | 17 | Text("Root") 18 | .fullScreenCover(isPresented: .constant(true)) { 19 | Text("Content") 20 | #if os(iOS) || os(tvOS) || os(visionOS) 21 | .introspect( 22 | .fullScreenCover, 23 | on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), 24 | customize: spy0 25 | ) 26 | #endif 27 | } 28 | } 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ListCellTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class ListCellTests: XCTestCase { 7 | #if canImport(UIKit) 8 | typealias PlatformListCell = UIView // covers both UITableViewCell and UICollectionViewCell 9 | #elseif canImport(AppKit) 10 | typealias PlatformListCell = NSTableCellView 11 | #endif 12 | 13 | func testListCell() { 14 | XCTAssertViewIntrospection(of: PlatformListCell.self) { spies in 15 | let spy = spies[0] 16 | 17 | List { 18 | Text("Item 1") 19 | #if os(iOS) || os(tvOS) || os(visionOS) 20 | .introspect(.listCell, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { spy($0) } 21 | .introspect(.listCell, on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy($0) } 22 | #elseif os(macOS) 23 | .introspect(.listCell, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy($0) } 24 | #endif 25 | } 26 | } 27 | } 28 | 29 | func testMaskedListCell() { 30 | XCTAssertViewIntrospection(of: PlatformListCell.self) { spies in 31 | let spy = spies[0] 32 | 33 | List { 34 | Text("Item 1") 35 | #if os(iOS) || os(tvOS) || os(visionOS) 36 | .introspect(.listCell, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { spy($0) } 37 | .introspect(.listCell, on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy($0) } 38 | #elseif os(macOS) 39 | .introspect(.listCell, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy($0) } 40 | #endif 41 | .clipped() 42 | .clipShape(RoundedRectangle(cornerRadius: 20.0)) 43 | .cornerRadius(2.0) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ListWithBorderedStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(macOS 12, *) 7 | @MainActor 8 | final class ListWithBorderedStyleTests: XCTestCase { 9 | #if canImport(AppKit) 10 | typealias PlatformListWithBorderedStyle = NSTableView 11 | #endif 12 | 13 | func testListWithBorderedStyle() throws { 14 | guard #available(macOS 12, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformListWithBorderedStyle.self) { spies in 19 | let spy0 = spies[0] 20 | let spy1 = spies[1] 21 | 22 | HStack { 23 | List { 24 | Text("Item 1") 25 | } 26 | .listStyle(.bordered) 27 | #if os(macOS) 28 | .introspect(.list(style: .bordered), on: .macOS(.v12, .v13, .v14)) { spy0($0) } 29 | #endif 30 | 31 | List { 32 | Text("Item 1") 33 | #if os(macOS) 34 | .introspect(.list(style: .bordered), on: .macOS(.v12, .v13, .v14), scope: .ancestor) { spy1($0) } 35 | #endif 36 | } 37 | .listStyle(.bordered) 38 | } 39 | } extraAssertions: { 40 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 41 | } 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ListWithGroupedStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class ListWithGroupedStyleTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformListWithGroupedStyle = UIScrollView // covers both UITableView and UICollectionView 10 | #endif 11 | 12 | func testListWithGroupedStyle() { 13 | XCTAssertViewIntrospection(of: PlatformListWithGroupedStyle.self) { spies in 14 | let spy0 = spies[0] 15 | let spy1 = spies[1] 16 | 17 | HStack { 18 | List { 19 | Text("Item 1") 20 | } 21 | .listStyle(.grouped) 22 | #if os(iOS) || os(tvOS) || os(visionOS) 23 | .introspect(.list(style: .grouped), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { spy0($0) } 24 | .introspect(.list(style: .grouped), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy0($0) } 25 | #endif 26 | 27 | List { 28 | Text("Item 1") 29 | #if os(iOS) || os(tvOS) || os(visionOS) 30 | .introspect(.list(style: .grouped), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), scope: .ancestor) { spy1($0) } 31 | .introspect(.list(style: .grouped), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor) { spy1($0) } 32 | #endif 33 | } 34 | .listStyle(.grouped) 35 | } 36 | } extraAssertions: { 37 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 38 | } 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ListWithInsetGroupedStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, *) 7 | @MainActor 8 | final class ListWithInsetGroupedStyleTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformListWithInsetGroupedStyle = UIScrollView // covers both UITableView and UICollectionView 11 | #endif 12 | 13 | func testListWithInsetGroupedStyle() throws { 14 | guard #available(iOS 14, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformListWithInsetGroupedStyle.self) { spies in 19 | let spy0 = spies[0] 20 | let spy1 = spies[1] 21 | 22 | HStack { 23 | List { 24 | Text("Item 1") 25 | } 26 | .listStyle(.insetGrouped) 27 | #if os(iOS) || os(visionOS) 28 | .introspect(.list(style: .insetGrouped), on: .iOS(.v14, .v15)) { spy0($0) } 29 | .introspect(.list(style: .insetGrouped), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy0($0) } 30 | #endif 31 | 32 | List { 33 | Text("Item 1") 34 | #if os(iOS) || os(visionOS) 35 | .introspect(.list(style: .insetGrouped), on: .iOS(.v14, .v15), scope: .ancestor) { spy1($0) } 36 | .introspect(.list(style: .insetGrouped), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor) { spy1($0) } 37 | #endif 38 | } 39 | .listStyle(.insetGrouped) 40 | } 41 | } extraAssertions: { 42 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 43 | } 44 | } 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ListWithInsetStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, macOS 11, *) 7 | @MainActor 8 | final class ListWithInsetStyleTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformListWithInsetStyle = UIScrollView // covers both UITableView and UICollectionView 11 | #elseif canImport(AppKit) 12 | typealias PlatformListWithInsetStyle = NSTableView 13 | #endif 14 | 15 | func testListWithInsetStyle() throws { 16 | guard #available(iOS 14, macOS 11, *) else { 17 | throw XCTSkip() 18 | } 19 | 20 | XCTAssertViewIntrospection(of: PlatformListWithInsetStyle.self) { spies in 21 | let spy0 = spies[0] 22 | let spy1 = spies[1] 23 | 24 | HStack { 25 | List { 26 | Text("Item 1") 27 | } 28 | .listStyle(.inset) 29 | #if os(iOS) || os(visionOS) 30 | .introspect(.list(style: .inset), on: .iOS(.v14, .v15)) { spy0($0) } 31 | .introspect(.list(style: .inset), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy0($0) } 32 | #elseif os(macOS) 33 | .introspect(.list(style: .inset), on: .macOS(.v11, .v12, .v13, .v14)) { spy0($0) } 34 | #endif 35 | 36 | List { 37 | Text("Item 1") 38 | #if os(iOS) || os(visionOS) 39 | .introspect(.list(style: .inset), on: .iOS(.v14, .v15), scope: .ancestor) { spy1($0) } 40 | .introspect(.list(style: .inset), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor) { spy1($0) } 41 | #elseif os(macOS) 42 | .introspect(.list(style: .inset), on: .macOS(.v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } 43 | #endif 44 | } 45 | .listStyle(.inset) 46 | } 47 | } extraAssertions: { 48 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 49 | } 50 | } 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ListWithPlainStyleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class ListWithPlainStyleTests: XCTestCase { 7 | #if canImport(UIKit) 8 | typealias PlatformListWithPlainStyle = UIScrollView // covers both UITableView and UICollectionView 9 | #elseif canImport(AppKit) 10 | typealias PlatformListWithPlainStyle = NSTableView 11 | #endif 12 | 13 | func testListWithPlainStyle() { 14 | XCTAssertViewIntrospection(of: PlatformListWithPlainStyle.self) { spies in 15 | let spy0 = spies[0] 16 | let spy1 = spies[1] 17 | 18 | HStack { 19 | List { 20 | Text("Item 1") 21 | } 22 | .listStyle(.plain) 23 | #if os(iOS) || os(tvOS) || os(visionOS) 24 | .introspect(.list(style: .plain), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18)) { spy0($0) } 25 | .introspect(.list(style: .plain), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy0($0) } 26 | #elseif os(macOS) 27 | .introspect(.list(style: .plain), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy0($0) } 28 | #endif 29 | 30 | List { 31 | Text("Item 1") 32 | #if os(iOS) || os(tvOS) || os(visionOS) 33 | .introspect(.list(style: .plain), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), scope: .ancestor) { spy1($0) } 34 | .introspect(.list(style: .plain), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor) { spy1($0) } 35 | #elseif os(macOS) 36 | .introspect(.list(style: .plain), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } 37 | #endif 38 | } 39 | .listStyle(.plain) 40 | } 41 | } extraAssertions: { 42 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ListWithSidebarStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, macOS 10.15, *) 7 | @MainActor 8 | final class ListWithSidebarStyleTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformListWithSidebarStyle = UIScrollView // covers both UITableView and UICollectionView 11 | #elseif canImport(AppKit) 12 | typealias PlatformListWithSidebarStyle = NSTableView 13 | #endif 14 | 15 | func testListWithSidebarStyle() throws { 16 | guard #available(iOS 14, macOS 10.15, *) else { 17 | throw XCTSkip() 18 | } 19 | 20 | XCTAssertViewIntrospection(of: PlatformListWithSidebarStyle.self) { spies in 21 | let spy0 = spies[0] 22 | let spy1 = spies[1] 23 | 24 | HStack { 25 | List { 26 | Text("Item 1") 27 | } 28 | .listStyle(.sidebar) 29 | #if os(iOS) || os(visionOS) 30 | .introspect(.list(style: .sidebar), on: .iOS(.v14, .v15)) { spy0($0) } 31 | .introspect(.list(style: .sidebar), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2)) { spy0($0) } 32 | #elseif os(macOS) 33 | .introspect(.list(style: .sidebar), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy0($0) } 34 | #endif 35 | 36 | List { 37 | Text("Item 1") 38 | #if os(iOS) || os(visionOS) 39 | .introspect(.list(style: .sidebar), on: .iOS(.v14, .v15), scope: .ancestor) { spy1($0) } 40 | .introspect(.list(style: .sidebar), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor) { spy1($0) } 41 | #elseif os(macOS) 42 | .introspect(.list(style: .sidebar), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } 43 | #endif 44 | } 45 | .listStyle(.sidebar) 46 | } 47 | } extraAssertions: { 48 | XCTAssert($0[safe: 0] !== $0[safe: 1]) 49 | } 50 | } 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/MapTests.swift: -------------------------------------------------------------------------------- 1 | #if canImport(MapKit) 2 | import MapKit 3 | import SwiftUI 4 | import SwiftUIIntrospect 5 | import XCTest 6 | 7 | @available(iOS 14, tvOS 14, macOS 11, *) 8 | @MainActor 9 | final class MapTests: XCTestCase { 10 | typealias PlatformMap = MKMapView 11 | 12 | func testMap() throws { 13 | guard #available(iOS 14, tvOS 14, macOS 11, *) else { 14 | throw XCTSkip() 15 | } 16 | 17 | XCTAssertViewIntrospection(of: PlatformMap.self) { spies in 18 | let spy0 = spies[0] 19 | let spy1 = spies[1] 20 | let spy2 = spies[2] 21 | 22 | let region = Binding.constant(MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))) 23 | 24 | VStack { 25 | Map(coordinateRegion: region) 26 | .introspect( 27 | .map, 28 | on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1, .v2), 29 | customize: spy0 30 | ) 31 | 32 | Map(coordinateRegion: region) 33 | .introspect( 34 | .map, 35 | on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1, .v2), 36 | customize: spy1 37 | ) 38 | 39 | Map(coordinateRegion: region) 40 | .introspect( 41 | .map, 42 | on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1, .v2), 43 | customize: spy2 44 | ) 45 | } 46 | } extraAssertions: { 47 | XCTAssertNotIdentical($0[safe: 0], $0[safe: 1]) 48 | XCTAssertNotIdentical($0[safe: 0], $0[safe: 2]) 49 | XCTAssertNotIdentical($0[safe: 1], $0[safe: 2]) 50 | } 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/NavigationSplitViewTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @available(iOS 16, tvOS 16, macOS 13, *) 6 | @MainActor 7 | final class NavigationSplitViewTests: XCTestCase { 8 | #if canImport(UIKit) && (os(iOS) || os(visionOS)) 9 | typealias PlatformNavigationSplitView = UISplitViewController 10 | #elseif canImport(UIKit) && os(tvOS) 11 | typealias PlatformNavigationSplitView = UINavigationController 12 | #elseif canImport(AppKit) 13 | typealias PlatformNavigationSplitView = NSSplitView 14 | #endif 15 | 16 | func testNavigationSplitView() throws { 17 | guard #available(iOS 16, tvOS 16, macOS 13, *) else { 18 | throw XCTSkip() 19 | } 20 | guard #unavailable(tvOS 18) else { 21 | throw XCTSkip() 22 | } 23 | 24 | XCTAssertViewIntrospection(of: PlatformNavigationSplitView.self) { spies in 25 | let spy = spies[0] 26 | 27 | NavigationSplitView { 28 | ZStack { 29 | Color.red 30 | Text("Root") 31 | } 32 | } detail: { 33 | ZStack { 34 | Color.blue 35 | Text("Detail") 36 | } 37 | } 38 | #if os(iOS) || os(visionOS) 39 | .introspect(.navigationSplitView, on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy) 40 | #elseif os(tvOS) 41 | .introspect(.navigationSplitView, on: .tvOS(.v16, .v17), customize: spy) 42 | #elseif os(macOS) 43 | .introspect(.navigationSplitView, on: .macOS(.v13, .v14), customize: spy) 44 | #endif 45 | } 46 | } 47 | 48 | func testNavigationSplitViewAsAncestor() throws { 49 | guard #available(iOS 16, tvOS 16, macOS 13, *) else { 50 | throw XCTSkip() 51 | } 52 | guard #unavailable(tvOS 18) else { 53 | throw XCTSkip() 54 | } 55 | 56 | XCTAssertViewIntrospection(of: PlatformNavigationSplitView.self) { spies in 57 | let spy = spies[0] 58 | 59 | // NB: columnVisibility is explicitly set here for ancestor introspection to work, because initially on iPad the sidebar is hidden, so the introspection modifier isn't triggered until the user makes the sidebar appear. This is why ancestor introspection is discouraged for most situations and it's opt-in. 60 | NavigationSplitView(columnVisibility: .constant(.all)) { 61 | ZStack { 62 | Color.red 63 | Text("Sidebar") 64 | #if os(iOS) || os(visionOS) 65 | .introspect(.navigationSplitView, on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor, customize: spy) 66 | #elseif os(tvOS) 67 | .introspect(.navigationSplitView, on: .tvOS(.v16, .v17), scope: .ancestor, customize: spy) 68 | #elseif os(macOS) 69 | .introspect(.navigationSplitView, on: .macOS(.v13, .v14), scope: .ancestor, customize: spy) 70 | #endif 71 | } 72 | } detail: { 73 | ZStack { 74 | Color.blue 75 | Text("Detail") 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/NavigationStackTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 16, tvOS 16, *) 7 | @MainActor 8 | final class NavigationStackTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformNavigationStack = UINavigationController 11 | #endif 12 | 13 | func testNavigationStack() throws { 14 | guard #available(iOS 16, tvOS 16, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformNavigationStack.self) { spies in 19 | let spy = spies[0] 20 | 21 | NavigationStack { 22 | ZStack { 23 | Color.red 24 | Text("Something") 25 | } 26 | } 27 | #if os(iOS) || os(tvOS) || os(visionOS) 28 | .introspect(.navigationStack, on: .iOS(.v16, .v17, .v18), .tvOS(.v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy) 29 | #endif 30 | } 31 | } 32 | 33 | func testNavigationStackAsAncestor() throws { 34 | guard #available(iOS 16, tvOS 16, *) else { 35 | throw XCTSkip() 36 | } 37 | 38 | XCTAssertViewIntrospection(of: PlatformNavigationStack.self) { spies in 39 | let spy = spies[0] 40 | 41 | NavigationStack { 42 | ZStack { 43 | Color.red 44 | Text("Something") 45 | #if os(iOS) || os(tvOS) || os(visionOS) 46 | .introspect(.navigationStack, on: .iOS(.v16, .v17, .v18), .tvOS(.v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor, customize: spy) 47 | #endif 48 | } 49 | } 50 | } 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/NavigationViewWithColumnsStyleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class NavigationViewWithColumnsStyleTests: XCTestCase { 7 | #if canImport(UIKit) && (os(iOS) || os(visionOS)) 8 | typealias PlatformNavigationViewWithColumnsStyle = UISplitViewController 9 | #elseif canImport(UIKit) && os(tvOS) 10 | typealias PlatformNavigationViewWithColumnsStyle = UINavigationController 11 | #elseif canImport(AppKit) 12 | typealias PlatformNavigationViewWithColumnsStyle = NSSplitView 13 | #endif 14 | 15 | func testNavigationViewWithColumnsStyle() { 16 | XCTAssertViewIntrospection(of: PlatformNavigationViewWithColumnsStyle.self) { spies in 17 | let spy = spies[0] 18 | 19 | NavigationView { 20 | ZStack { 21 | Color.red 22 | Text("Something") 23 | } 24 | } 25 | .navigationViewStyle(DoubleColumnNavigationViewStyle()) 26 | #if os(iOS) || os(visionOS) 27 | .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy) 28 | #elseif os(tvOS) 29 | .introspect(.navigationView(style: .columns), on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy) 30 | #elseif os(macOS) 31 | .introspect(.navigationView(style: .columns), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy) 32 | #endif 33 | } 34 | } 35 | 36 | func testNavigationViewWithColumnsStyleAsAncestor() { 37 | XCTAssertViewIntrospection(of: PlatformNavigationViewWithColumnsStyle.self) { spies in 38 | let spy = spies[0] 39 | 40 | NavigationView { 41 | ZStack { 42 | Color.red 43 | Text("Something") 44 | #if os(iOS) || os(visionOS) 45 | .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor, customize: spy) 46 | #elseif os(tvOS) 47 | .introspect(.navigationView(style: .columns), on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), scope: .ancestor, customize: spy) 48 | #elseif os(macOS) 49 | .introspect(.navigationView(style: .columns), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor, customize: spy) 50 | #endif 51 | } 52 | } 53 | .navigationViewStyle(DoubleColumnNavigationViewStyle()) 54 | #if os(iOS) 55 | // NB: this is necessary for ancestor introspection to work, because initially on iPad the "Customized" text isn't shown as it's hidden in the sidebar. This is why ancestor introspection is discouraged for most situations and it's opt-in. 56 | .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { 57 | $0.preferredDisplayMode = .oneOverSecondary 58 | } 59 | #endif 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/NavigationViewWithStackStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class NavigationViewWithStackStyleTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformNavigationViewWithStackStyle = UINavigationController 10 | #endif 11 | 12 | func testNavigationViewWithStackStyle() { 13 | XCTAssertViewIntrospection(of: PlatformNavigationViewWithStackStyle.self) { spies in 14 | let spy = spies[0] 15 | 16 | NavigationView { 17 | ZStack { 18 | Color.red 19 | Text("Something") 20 | } 21 | } 22 | .navigationViewStyle(.stack) 23 | #if os(iOS) || os(tvOS) || os(visionOS) 24 | .introspect(.navigationView(style: .stack), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy) 25 | #endif 26 | } 27 | } 28 | 29 | func testNavigationViewWithStackStyleAsAncestor() { 30 | XCTAssertViewIntrospection(of: PlatformNavigationViewWithStackStyle.self) { spies in 31 | let spy = spies[0] 32 | 33 | NavigationView { 34 | ZStack { 35 | Color.red 36 | Text("Something") 37 | #if os(iOS) || os(tvOS) || os(visionOS) 38 | .introspect(.navigationView(style: .stack), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor, customize: spy) 39 | #endif 40 | } 41 | } 42 | .navigationViewStyle(.stack) 43 | } 44 | } 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/PageControlTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, tvOS 14, *) 7 | @MainActor 8 | final class PageControlTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformPageControl = UIPageControl 11 | #endif 12 | 13 | func testPageControl() throws { 14 | guard #available(iOS 14, tvOS 14, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformPageControl.self) { spies in 19 | let spy = spies[0] 20 | 21 | TabView { 22 | Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) 23 | Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) 24 | } 25 | .tabViewStyle(.page(indexDisplayMode: .always)) 26 | #if os(iOS) || os(tvOS) || os(visionOS) 27 | .introspect(.pageControl, on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy) 28 | #endif 29 | } 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/PickerWithMenuStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class PickerWithMenuStyleTests: XCTestCase { 8 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 9 | typealias PlatformPickerWithMenuStyle = NSPopUpButton 10 | #endif 11 | 12 | func testPickerWithMenuStyle() { 13 | XCTAssertViewIntrospection(of: PlatformPickerWithMenuStyle.self) { spies in 14 | let spy0 = spies[0] 15 | let spy1 = spies[1] 16 | let spy2 = spies[2] 17 | 18 | VStack { 19 | Picker("Pick", selection: .constant("1")) { 20 | Text("1").tag("1") 21 | } 22 | .pickerStyle(.menu) 23 | #if os(macOS) 24 | .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) 25 | #endif 26 | .cornerRadius(8) 27 | 28 | Picker("Pick", selection: .constant("1")) { 29 | Text("1").tag("1") 30 | Text("2").tag("2") 31 | } 32 | .pickerStyle(.menu) 33 | #if os(macOS) 34 | .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) 35 | #endif 36 | .cornerRadius(8) 37 | 38 | Picker("Pick", selection: .constant("1")) { 39 | Text("1").tag("1") 40 | Text("2").tag("2") 41 | Text("3").tag("3") 42 | } 43 | .pickerStyle(.menu) 44 | #if os(macOS) 45 | .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) 46 | #endif 47 | } 48 | } extraAssertions: { 49 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 50 | XCTAssertEqual($0[safe: 0]?.numberOfItems, 1) 51 | XCTAssertEqual($0[safe: 1]?.numberOfItems, 2) 52 | XCTAssertEqual($0[safe: 2]?.numberOfItems, 3) 53 | #endif 54 | } 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/PickerWithSegmentedStyleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class PickerWithSegmentedStyleTests: XCTestCase { 7 | #if canImport(UIKit) 8 | typealias PlatformPickerWithSegmentedStyle = UISegmentedControl 9 | #elseif canImport(AppKit) 10 | typealias PlatformPickerWithSegmentedStyle = NSSegmentedControl 11 | #endif 12 | 13 | func testPickerWithSegmentedStyle() { 14 | XCTAssertViewIntrospection(of: PlatformPickerWithSegmentedStyle.self) { spies in 15 | let spy0 = spies[0] 16 | let spy1 = spies[1] 17 | let spy2 = spies[2] 18 | 19 | VStack { 20 | Picker("Pick", selection: .constant("1")) { 21 | Text("1").tag("1") 22 | } 23 | .pickerStyle(.segmented) 24 | #if os(iOS) || os(tvOS) || os(visionOS) 25 | .introspect(.picker(style: .segmented), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 26 | #elseif os(macOS) 27 | .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 28 | #endif 29 | .cornerRadius(8) 30 | 31 | Picker("Pick", selection: .constant("1")) { 32 | Text("1").tag("1") 33 | Text("2").tag("2") 34 | } 35 | .pickerStyle(.segmented) 36 | #if os(iOS) || os(tvOS) || os(visionOS) 37 | .introspect(.picker(style: .segmented), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 38 | #elseif os(macOS) 39 | .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 40 | #endif 41 | .cornerRadius(8) 42 | 43 | Picker("Pick", selection: .constant("1")) { 44 | Text("1").tag("1") 45 | Text("2").tag("2") 46 | Text("3").tag("3") 47 | } 48 | .pickerStyle(.segmented) 49 | #if os(iOS) || os(tvOS) || os(visionOS) 50 | .introspect(.picker(style: .segmented), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 51 | #elseif os(macOS) 52 | .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 53 | #endif 54 | } 55 | } extraAssertions: { 56 | #if canImport(UIKit) 57 | XCTAssertEqual($0[safe: 0]?.numberOfSegments, 1) 58 | XCTAssertEqual($0[safe: 1]?.numberOfSegments, 2) 59 | XCTAssertEqual($0[safe: 2]?.numberOfSegments, 3) 60 | #elseif canImport(AppKit) 61 | XCTAssertEqual($0[safe: 0]?.segmentCount, 1) 62 | XCTAssertEqual($0[safe: 1]?.segmentCount, 2) 63 | XCTAssertEqual($0[safe: 2]?.segmentCount, 3) 64 | #endif 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/PickerWithWheelStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class PickerWithWheelStyleTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformPickerWithWheelStyle = UIPickerView 10 | #endif 11 | 12 | func testPickerWithWheelStyle() { 13 | XCTAssertViewIntrospection(of: PlatformPickerWithWheelStyle.self) { spies in 14 | let spy0 = spies[0] 15 | let spy1 = spies[1] 16 | let spy2 = spies[2] 17 | 18 | VStack { 19 | Picker("Pick", selection: .constant("1")) { 20 | Text("1").tag("1") 21 | } 22 | .pickerStyle(.wheel) 23 | #if os(iOS) || os(visionOS) 24 | .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 25 | #endif 26 | .cornerRadius(8) 27 | 28 | Picker("Pick", selection: .constant("1")) { 29 | Text("1").tag("1") 30 | Text("2").tag("2") 31 | } 32 | .pickerStyle(.wheel) 33 | #if os(iOS) || os(visionOS) 34 | .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 35 | #endif 36 | .cornerRadius(8) 37 | 38 | Picker("Pick", selection: .constant("1")) { 39 | Text("1").tag("1") 40 | Text("2").tag("2") 41 | Text("3").tag("3") 42 | } 43 | .pickerStyle(.wheel) 44 | #if os(iOS) || os(visionOS) 45 | .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 46 | #endif 47 | } 48 | } extraAssertions: { 49 | #if canImport(UIKit) 50 | XCTAssertEqual($0[safe: 0]?.numberOfRows(inComponent: 0), 1) 51 | XCTAssertEqual($0[safe: 1]?.numberOfRows(inComponent: 0), 2) 52 | XCTAssertEqual($0[safe: 2]?.numberOfRows(inComponent: 0), 3) 53 | #endif 54 | } 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/PopoverTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(macOS) && !targetEnvironment(macCatalyst) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class PopoverTests: XCTestCase { 8 | func testPopover() throws { 9 | XCTAssertViewIntrospection(of: UIPopoverPresentationController.self) { spies in 10 | let spy0 = spies[0] 11 | 12 | Text("Root") 13 | .popover(isPresented: .constant(true)) { 14 | Text("Popover") 15 | .introspect( 16 | .popover, 17 | on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), 18 | customize: spy0 19 | ) 20 | } 21 | } 22 | } 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ProgressViewWithCircularStyleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class ProgressViewWithCircularStyleTests: XCTestCase { 7 | #if canImport(UIKit) 8 | typealias PlatformProgressViewWithCircularStyle = UIActivityIndicatorView 9 | #elseif canImport(AppKit) 10 | typealias PlatformProgressViewWithCircularStyle = NSProgressIndicator 11 | #endif 12 | 13 | func testProgressViewWithCircularStyle() throws { 14 | guard #available(iOS 14, tvOS 14, macOS 11, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformProgressViewWithCircularStyle.self) { spies in 19 | let spy0 = spies[0] 20 | let spy1 = spies[1] 21 | let spy2 = spies[2] 22 | 23 | VStack { 24 | ProgressView(value: 0.25) 25 | .progressViewStyle(.circular) 26 | #if os(iOS) || os(tvOS) || os(visionOS) 27 | .introspect(.progressView(style: .circular), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 28 | #elseif os(macOS) 29 | .introspect(.progressView(style: .circular), on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) 30 | #endif 31 | 32 | ProgressView(value: 0.5) 33 | .progressViewStyle(.circular) 34 | #if os(iOS) || os(tvOS) || os(visionOS) 35 | .introspect(.progressView(style: .circular), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 36 | #elseif os(macOS) 37 | .introspect(.progressView(style: .circular), on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) 38 | #endif 39 | 40 | ProgressView(value: 0.75) 41 | .progressViewStyle(.circular) 42 | #if os(iOS) || os(tvOS) || os(visionOS) 43 | .introspect(.progressView(style: .circular), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 44 | #elseif os(macOS) 45 | .introspect(.progressView(style: .circular), on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) 46 | #endif 47 | } 48 | } extraAssertions: { 49 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 50 | XCTAssertEqual($0[safe: 0]?.doubleValue, 0.25) 51 | XCTAssertEqual($0[safe: 1]?.doubleValue, 0.5) 52 | XCTAssertEqual($0[safe: 2]?.doubleValue, 0.75) 53 | #endif 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ProgressViewWithLinearStyleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class ProgressViewWithLinearStyleTests: XCTestCase { 7 | #if canImport(UIKit) 8 | typealias PlatformProgressViewWithLinearStyle = UIProgressView 9 | #elseif canImport(AppKit) 10 | typealias PlatformProgressViewWithLinearStyle = NSProgressIndicator 11 | #endif 12 | 13 | func testProgressViewWithLinearStyle() throws { 14 | guard #available(iOS 14, tvOS 14, macOS 11, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformProgressViewWithLinearStyle.self) { spies in 19 | let spy0 = spies[0] 20 | let spy1 = spies[1] 21 | let spy2 = spies[2] 22 | 23 | VStack { 24 | ProgressView(value: 0.25) 25 | .progressViewStyle(.linear) 26 | #if os(iOS) || os(tvOS) || os(visionOS) 27 | .introspect(.progressView(style: .linear), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 28 | #elseif os(macOS) 29 | .introspect(.progressView(style: .linear), on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) 30 | #endif 31 | 32 | ProgressView(value: 0.5) 33 | .progressViewStyle(.linear) 34 | #if os(iOS) || os(tvOS) || os(visionOS) 35 | .introspect(.progressView(style: .linear), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 36 | #elseif os(macOS) 37 | .introspect(.progressView(style: .linear), on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) 38 | #endif 39 | 40 | ProgressView(value: 0.75) 41 | .progressViewStyle(.linear) 42 | #if os(iOS) || os(tvOS) || os(visionOS) 43 | .introspect(.progressView(style: .linear), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 44 | #elseif os(macOS) 45 | .introspect(.progressView(style: .linear), on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) 46 | #endif 47 | } 48 | } extraAssertions: { 49 | #if canImport(UIKit) 50 | XCTAssertEqual($0[safe: 0]?.progress, 0.25) 51 | XCTAssertEqual($0[safe: 1]?.progress, 0.5) 52 | XCTAssertEqual($0[safe: 2]?.progress, 0.75) 53 | #elseif canImport(AppKit) && !targetEnvironment(macCatalyst) 54 | XCTAssertEqual($0[safe: 0]?.doubleValue, 0.25) 55 | XCTAssertEqual($0[safe: 1]?.doubleValue, 0.5) 56 | XCTAssertEqual($0[safe: 2]?.doubleValue, 0.75) 57 | #endif 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/SheetTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) && !targetEnvironment(macCatalyst) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class SheetTests: XCTestCase { 8 | #if os(iOS) 9 | func testSheet() throws { 10 | XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in 11 | let spy0 = spies[0] 12 | 13 | Text("Root") 14 | .sheet(isPresented: .constant(true)) { 15 | Text("Sheet") 16 | .introspect( 17 | .sheet, 18 | on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), 19 | customize: spy0 20 | ) 21 | } 22 | } 23 | } 24 | 25 | func testSheetAsSheetPresentationController() throws { 26 | guard #available(iOS 15, tvOS 15, *) else { 27 | throw XCTSkip() 28 | } 29 | 30 | XCTAssertViewIntrospection(of: UISheetPresentationController.self) { spies in 31 | let spy0 = spies[0] 32 | 33 | Text("Root") 34 | .sheet(isPresented: .constant(true)) { 35 | Text("Sheet") 36 | .introspect( 37 | .sheet, 38 | on: .iOS(.v15, .v16, .v17, .v18), 39 | customize: spy0 40 | ) 41 | } 42 | } 43 | } 44 | #elseif os(tvOS) 45 | func testSheet() throws { 46 | XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in 47 | let spy0 = spies[0] 48 | 49 | Text("Root") 50 | .sheet(isPresented: .constant(true)) { 51 | Text("Content") 52 | .introspect( 53 | .sheet, 54 | on: .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), 55 | customize: spy0 56 | ) 57 | } 58 | } 59 | } 60 | #elseif os(visionOS) 61 | func testSheet() throws { 62 | XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in 63 | let spy0 = spies[0] 64 | 65 | Text("Root") 66 | .sheet(isPresented: .constant(true)) { 67 | Text("Sheet") 68 | .introspect( 69 | .sheet, 70 | on: .visionOS(.v1, .v2), 71 | customize: spy0 72 | ) 73 | } 74 | } 75 | } 76 | #endif 77 | } 78 | #endif 79 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/SliderTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class SliderTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformSlider = UISlider 10 | #elseif canImport(AppKit) 11 | typealias PlatformSlider = NSSlider 12 | #endif 13 | 14 | func testSlider() { 15 | XCTAssertViewIntrospection(of: PlatformSlider.self) { spies in 16 | let spy0 = spies[0] 17 | let spy1 = spies[1] 18 | let spy2 = spies[2] 19 | 20 | VStack { 21 | Slider(value: .constant(0.2), in: 0...1) 22 | #if os(iOS) 23 | .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy0) 24 | #elseif os(macOS) 25 | .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 26 | #endif 27 | .cornerRadius(8) 28 | 29 | Slider(value: .constant(0.5), in: 0...1) 30 | #if os(iOS) 31 | .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy1) 32 | #elseif os(macOS) 33 | .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 34 | #endif 35 | .cornerRadius(8) 36 | 37 | Slider(value: .constant(0.8), in: 0...1) 38 | #if os(iOS) 39 | .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy2) 40 | #elseif os(macOS) 41 | .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 42 | #endif 43 | } 44 | } extraAssertions: { 45 | #if canImport(UIKit) 46 | XCTAssertEqual($0[safe: 0]?.value, 0.2) 47 | XCTAssertEqual($0[safe: 1]?.value, 0.5) 48 | XCTAssertEqual($0[safe: 2]?.value, 0.8) 49 | #elseif canImport(AppKit) 50 | XCTAssertEqual($0[safe: 0]?.floatValue, 0.2) 51 | XCTAssertEqual($0[safe: 1]?.floatValue, 0.5) 52 | XCTAssertEqual($0[safe: 2]?.floatValue, 0.8) 53 | #endif 54 | } 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/StepperTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class StepperTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformStepper = UIStepper 10 | #elseif canImport(AppKit) 11 | typealias PlatformStepper = NSStepper 12 | #endif 13 | 14 | func testStepper() { 15 | XCTAssertViewIntrospection(of: PlatformStepper.self) { spies in 16 | let spy0 = spies[0] 17 | let spy1 = spies[1] 18 | let spy2 = spies[2] 19 | 20 | VStack { 21 | Stepper("", value: .constant(0), in: 0...10) 22 | #if os(iOS) 23 | .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy0) 24 | #elseif os(macOS) 25 | .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 26 | #endif 27 | .cornerRadius(8) 28 | 29 | Stepper("", value: .constant(0), in: 0...10) 30 | #if os(iOS) 31 | .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy1) 32 | #elseif os(macOS) 33 | .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 34 | #endif 35 | .cornerRadius(8) 36 | 37 | Stepper("", value: .constant(0), in: 0...10) 38 | #if os(iOS) 39 | .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy2) 40 | #elseif os(macOS) 41 | .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 42 | #endif 43 | } 44 | } extraAssertions: { 45 | XCTAssert(Set($0.map(ObjectIdentifier.init)).count == 3) 46 | } 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/TabViewTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class TabViewTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformTabView = UITabBarController 10 | #elseif canImport(AppKit) 11 | typealias PlatformTabView = NSTabView 12 | #endif 13 | 14 | func testTabView() { 15 | XCTAssertViewIntrospection(of: PlatformTabView.self) { spies in 16 | let spy = spies[0] 17 | 18 | TabView { 19 | ZStack { 20 | Color.red 21 | Text("Something") 22 | } 23 | } 24 | #if os(iOS) || os(tvOS) 25 | .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy) 26 | #elseif os(macOS) 27 | .introspect(.tabView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy) 28 | #endif 29 | } 30 | } 31 | 32 | func testTabViewAsAncestor() { 33 | XCTAssertViewIntrospection(of: PlatformTabView.self) { spies in 34 | let spy = spies[0] 35 | 36 | TabView { 37 | ZStack { 38 | Color.red 39 | Text("Something") 40 | #if os(iOS) || os(tvOS) 41 | .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), scope: .ancestor, customize: spy) 42 | #elseif os(macOS) 43 | .introspect(.tabView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor, customize: spy) 44 | #endif 45 | } 46 | } 47 | } 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/TabViewWithPageStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, tvOS 14, *) 7 | @MainActor 8 | final class TabViewWithPageStyleTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformTabViewWithPageStyle = UICollectionView 11 | #endif 12 | 13 | func testTabViewWithPageStyle() throws { 14 | guard #available(iOS 14, tvOS 14, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformTabViewWithPageStyle.self) { spies in 19 | let spy = spies[0] 20 | 21 | TabView { 22 | Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) 23 | Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) 24 | } 25 | .tabViewStyle(.page) 26 | #if os(iOS) || os(tvOS) || os(visionOS) 27 | .introspect(.tabView(style: .page), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy) 28 | #endif 29 | } 30 | } 31 | 32 | func testTabViewWithPageStyleAsAncestor() throws { 33 | guard #available(iOS 14, tvOS 14, *) else { 34 | throw XCTSkip() 35 | } 36 | 37 | XCTAssertViewIntrospection(of: PlatformTabViewWithPageStyle.self) { spies in 38 | let spy = spies[0] 39 | 40 | TabView { 41 | Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) 42 | #if os(iOS) || os(tvOS) || os(visionOS) 43 | .introspect(.tabView(style: .page), on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), scope: .ancestor, customize: spy) 44 | #endif 45 | Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) 46 | } 47 | .tabViewStyle(.page) 48 | } 49 | } 50 | } 51 | #endif 52 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/TextEditorTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(iOS 14, macOS 11, *) 7 | @MainActor 8 | final class TextEditorTests: XCTestCase { 9 | #if canImport(UIKit) 10 | typealias PlatformTextEditor = UITextView 11 | #elseif canImport(AppKit) 12 | typealias PlatformTextEditor = NSTextView 13 | #endif 14 | 15 | func testTextEditor() throws { 16 | guard #available(iOS 14, macOS 11, *) else { 17 | throw XCTSkip() 18 | } 19 | 20 | XCTAssertViewIntrospection(of: PlatformTextEditor.self) { spies in 21 | let spy0 = spies[0] 22 | let spy1 = spies[1] 23 | let spy2 = spies[2] 24 | 25 | VStack { 26 | TextEditor(text: .constant("Text Field 0")) 27 | #if os(iOS) || os(visionOS) 28 | .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 29 | #elseif os(macOS) 30 | .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) 31 | #endif 32 | .cornerRadius(8) 33 | 34 | TextEditor(text: .constant("Text Field 1")) 35 | #if os(iOS) || os(visionOS) 36 | .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 37 | #elseif os(macOS) 38 | .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) 39 | #endif 40 | .cornerRadius(8) 41 | 42 | TextEditor(text: .constant("Text Field 2")) 43 | #if os(iOS) || os(visionOS) 44 | .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 45 | #elseif os(macOS) 46 | .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) 47 | #endif 48 | } 49 | } extraAssertions: { 50 | #if canImport(UIKit) 51 | XCTAssertEqual($0[safe: 0]?.text, "Text Field 0") 52 | XCTAssertEqual($0[safe: 1]?.text, "Text Field 1") 53 | XCTAssertEqual($0[safe: 2]?.text, "Text Field 2") 54 | #elseif canImport(AppKit) 55 | XCTAssertEqual($0[safe: 0]?.string, "Text Field 0") 56 | XCTAssertEqual($0[safe: 1]?.string, "Text Field 1") 57 | XCTAssertEqual($0[safe: 2]?.string, "Text Field 2") 58 | #endif 59 | } 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/TextFieldWithVerticalAxisTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @available(iOS 16, tvOS 16, macOS 13, *) 6 | @MainActor 7 | final class TextFieldWithVerticalAxisTests: XCTestCase { 8 | #if canImport(UIKit) && (os(iOS) || os(visionOS)) 9 | typealias PlatformTextField = UITextView 10 | #elseif canImport(UIKit) && os(tvOS) 11 | typealias PlatformTextField = UITextField 12 | #elseif canImport(AppKit) 13 | typealias PlatformTextField = NSTextField 14 | #endif 15 | 16 | func testTextFieldWithVerticalAxis() throws { 17 | guard #available(iOS 16, tvOS 16, macOS 13, *) else { 18 | throw XCTSkip() 19 | } 20 | 21 | XCTAssertViewIntrospection(of: PlatformTextField.self) { spies in 22 | let spy0 = spies[0] 23 | let spy1 = spies[1] 24 | let spy2 = spies[2] 25 | 26 | VStack { 27 | TextField("", text: .constant("Text Field 1"), axis: .vertical) 28 | #if os(iOS) || os(visionOS) 29 | .introspect(.textField(axis: .vertical), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 30 | #elseif os(tvOS) 31 | .introspect(.textField(axis: .vertical), on: .tvOS(.v16, .v17, .v18), customize: spy0) 32 | #elseif os(macOS) 33 | .introspect(.textField(axis: .vertical), on: .macOS(.v13, .v14), customize: spy0) 34 | #endif 35 | .cornerRadius(8) 36 | 37 | TextField("", text: .constant("Text Field 2"), axis: .vertical) 38 | #if os(iOS) || os(visionOS) 39 | .introspect(.textField(axis: .vertical), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 40 | #elseif os(tvOS) 41 | .introspect(.textField(axis: .vertical), on: .tvOS(.v16, .v17, .v18), customize: spy1) 42 | #elseif os(macOS) 43 | .introspect(.textField(axis: .vertical), on: .macOS(.v13, .v14), customize: spy1) 44 | #endif 45 | .cornerRadius(8) 46 | 47 | TextField("", text: .constant("Text Field 3"), axis: .vertical) 48 | #if os(iOS) || os(visionOS) 49 | .introspect(.textField(axis: .vertical), on: .iOS(.v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 50 | #elseif os(tvOS) 51 | .introspect(.textField(axis: .vertical), on: .tvOS(.v16, .v17, .v18), customize: spy2) 52 | #elseif os(macOS) 53 | .introspect(.textField(axis: .vertical), on: .macOS(.v13, .v14), customize: spy2) 54 | #endif 55 | } 56 | } extraAssertions: { 57 | #if canImport(UIKit) 58 | XCTAssertEqual($0[safe: 0]?.text, "Text Field 1") 59 | XCTAssertEqual($0[safe: 1]?.text, "Text Field 2") 60 | XCTAssertEqual($0[safe: 2]?.text, "Text Field 3") 61 | #elseif canImport(AppKit) 62 | XCTAssertEqual($0[safe: 0]?.stringValue, "Text Field 1") 63 | XCTAssertEqual($0[safe: 1]?.stringValue, "Text Field 2") 64 | XCTAssertEqual($0[safe: 2]?.stringValue, "Text Field 3") 65 | #endif 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ToggleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class ToggleTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformToggle = UISwitch 10 | #elseif canImport(AppKit) 11 | typealias PlatformToggle = NSButton 12 | #endif 13 | 14 | func testToggle() { 15 | XCTAssertViewIntrospection(of: PlatformToggle.self) { spies in 16 | let spy0 = spies[0] 17 | let spy1 = spies[1] 18 | let spy2 = spies[2] 19 | 20 | VStack { 21 | Toggle("", isOn: .constant(true)) 22 | #if os(iOS) 23 | .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy0) 24 | #elseif os(macOS) 25 | .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 26 | #endif 27 | 28 | Toggle("", isOn: .constant(false)) 29 | #if os(iOS) 30 | .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy1) 31 | #elseif os(macOS) 32 | .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 33 | #endif 34 | 35 | Toggle("", isOn: .constant(true)) 36 | #if os(iOS) 37 | .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy2) 38 | #elseif os(macOS) 39 | .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 40 | #endif 41 | } 42 | } extraAssertions: { 43 | #if canImport(UIKit) 44 | XCTAssertEqual($0[safe: 0]?.isOn, true) 45 | XCTAssertEqual($0[safe: 1]?.isOn, false) 46 | XCTAssertEqual($0[safe: 2]?.isOn, true) 47 | #elseif canImport(AppKit) 48 | XCTAssertEqual($0[safe: 0]?.state, .on) 49 | XCTAssertEqual($0[safe: 1]?.state, .off) 50 | XCTAssertEqual($0[safe: 2]?.state, .on) 51 | #endif 52 | } 53 | } 54 | } 55 | #endif 56 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ToggleWithButtonStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @available(macOS 12, *) 7 | @MainActor 8 | final class ToggleWithButtonStyleTests: XCTestCase { 9 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 10 | typealias PlatformToggleWithButtonStyle = NSButton 11 | #endif 12 | 13 | func testToggleWithButtonStyle() throws { 14 | guard #available(macOS 12, *) else { 15 | throw XCTSkip() 16 | } 17 | 18 | XCTAssertViewIntrospection(of: PlatformToggleWithButtonStyle.self) { spies in 19 | let spy0 = spies[0] 20 | let spy1 = spies[1] 21 | let spy2 = spies[2] 22 | 23 | VStack { 24 | Toggle("", isOn: .constant(true)) 25 | .toggleStyle(.button) 26 | #if os(macOS) 27 | .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14), customize: spy0) 28 | #endif 29 | 30 | Toggle("", isOn: .constant(false)) 31 | .toggleStyle(.button) 32 | #if os(macOS) 33 | .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14), customize: spy1) 34 | #endif 35 | 36 | Toggle("", isOn: .constant(true)) 37 | .toggleStyle(.button) 38 | #if os(macOS) 39 | .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14), customize: spy2) 40 | #endif 41 | } 42 | } extraAssertions: { 43 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 44 | XCTAssertEqual($0[safe: 0]?.state, .on) 45 | XCTAssertEqual($0[safe: 1]?.state, .off) 46 | XCTAssertEqual($0[safe: 2]?.state, .on) 47 | #endif 48 | } 49 | } 50 | } 51 | #endif 52 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ToggleWithCheckboxStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) && !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class ToggleWithCheckboxStyleTests: XCTestCase { 8 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 9 | typealias PlatformToggleWithCheckboxStyle = NSButton 10 | #endif 11 | 12 | func testToggleWithCheckboxStyle() throws { 13 | XCTAssertViewIntrospection(of: PlatformToggleWithCheckboxStyle.self) { spies in 14 | let spy0 = spies[0] 15 | let spy1 = spies[1] 16 | let spy2 = spies[2] 17 | 18 | VStack { 19 | Toggle("", isOn: .constant(true)) 20 | .toggleStyle(.checkbox) 21 | #if os(macOS) 22 | .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 23 | #endif 24 | 25 | Toggle("", isOn: .constant(false)) 26 | .toggleStyle(.checkbox) 27 | #if os(macOS) 28 | .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 29 | #endif 30 | 31 | Toggle("", isOn: .constant(true)) 32 | .toggleStyle(.checkbox) 33 | #if os(macOS) 34 | .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 35 | #endif 36 | } 37 | } extraAssertions: { 38 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 39 | XCTAssertEqual($0[safe: 0]?.state, .on) 40 | XCTAssertEqual($0[safe: 1]?.state, .off) 41 | XCTAssertEqual($0[safe: 2]?.state, .on) 42 | #endif 43 | } 44 | } 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ToggleWithSwitchStyleTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(tvOS) && !os(visionOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class ToggleWithSwitchStyleTests: XCTestCase { 8 | #if canImport(UIKit) 9 | typealias PlatformToggleWithSwitchStyle = UISwitch 10 | #elseif canImport(AppKit) 11 | typealias PlatformToggleWithSwitchStyle = NSSwitch 12 | #endif 13 | 14 | func testToggleWithSwitchStyle() { 15 | XCTAssertViewIntrospection(of: PlatformToggleWithSwitchStyle.self) { spies in 16 | let spy0 = spies[0] 17 | let spy1 = spies[1] 18 | let spy2 = spies[2] 19 | 20 | VStack { 21 | Toggle("", isOn: .constant(true)) 22 | .toggleStyle(.switch) 23 | #if os(iOS) 24 | .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy0) 25 | #elseif os(macOS) 26 | .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 27 | #endif 28 | 29 | Toggle("", isOn: .constant(false)) 30 | .toggleStyle(.switch) 31 | #if os(iOS) 32 | .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy1) 33 | #elseif os(macOS) 34 | .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 35 | #endif 36 | 37 | Toggle("", isOn: .constant(true)) 38 | .toggleStyle(.switch) 39 | #if os(iOS) 40 | .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), customize: spy2) 41 | #elseif os(macOS) 42 | .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 43 | #endif 44 | } 45 | } extraAssertions: { 46 | #if canImport(UIKit) 47 | XCTAssertEqual($0[safe: 0]?.isOn, true) 48 | XCTAssertEqual($0[safe: 1]?.isOn, false) 49 | XCTAssertEqual($0[safe: 2]?.isOn, true) 50 | #elseif canImport(AppKit) 51 | XCTAssertEqual($0[safe: 0]?.state, .on) 52 | XCTAssertEqual($0[safe: 1]?.state, .off) 53 | XCTAssertEqual($0[safe: 2]?.state, .on) 54 | #endif 55 | } 56 | } 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/VideoPlayerTests.swift: -------------------------------------------------------------------------------- 1 | #if canImport(AVKit) 2 | import AVKit 3 | import SwiftUI 4 | import SwiftUIIntrospect 5 | import XCTest 6 | 7 | @available(iOS 14, tvOS 14, macOS 11, *) 8 | @MainActor 9 | final class VideoPlayerTests: XCTestCase { 10 | #if canImport(UIKit) 11 | typealias PlatformVideoPlayer = AVPlayerViewController 12 | #elseif canImport(AppKit) 13 | typealias PlatformVideoPlayer = AVPlayerView 14 | #endif 15 | 16 | func testVideoPlayer() throws { 17 | guard #available(iOS 14, tvOS 14, macOS 11, *) else { 18 | throw XCTSkip() 19 | } 20 | 21 | let videoURL0 = URL(string: "https://bit.ly/swswift#1")! 22 | let videoURL1 = URL(string: "https://bit.ly/swswift#2")! 23 | let videoURL2 = URL(string: "https://bit.ly/swswift#3")! 24 | 25 | XCTAssertViewIntrospection(of: PlatformVideoPlayer.self) { spies in 26 | let spy0 = spies[0] 27 | let spy1 = spies[1] 28 | let spy2 = spies[2] 29 | 30 | VStack { 31 | VideoPlayer(player: AVPlayer(url: videoURL0)) 32 | #if os(iOS) || os(tvOS) || os(visionOS) 33 | .introspect(.videoPlayer, on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 34 | #elseif os(macOS) 35 | .introspect(.videoPlayer, on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) 36 | #endif 37 | 38 | VideoPlayer(player: AVPlayer(url: videoURL1)) 39 | #if os(iOS) || os(tvOS) || os(visionOS) 40 | .introspect(.videoPlayer, on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 41 | #elseif os(macOS) 42 | .introspect(.videoPlayer, on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) 43 | #endif 44 | 45 | VideoPlayer(player: AVPlayer(url: videoURL2)) 46 | #if os(iOS) || os(tvOS) || os(visionOS) 47 | .introspect(.videoPlayer, on: .iOS(.v14, .v15, .v16, .v17, .v18), .tvOS(.v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 48 | #elseif os(macOS) 49 | .introspect(.videoPlayer, on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) 50 | #endif 51 | } 52 | } extraAssertions: { 53 | XCTAssertEqual(($0[safe: 0]?.player?.currentItem?.asset as? AVURLAsset)?.url, videoURL0) 54 | XCTAssertEqual(($0[safe: 1]?.player?.currentItem?.asset as? AVURLAsset)?.url, videoURL1) 55 | XCTAssertEqual(($0[safe: 2]?.player?.currentItem?.asset as? AVURLAsset)?.url, videoURL2) 56 | } 57 | } 58 | } 59 | #endif 60 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import SwiftUI 3 | import SwiftUIIntrospect 4 | import XCTest 5 | 6 | @MainActor 7 | final class ViewControllerTests: XCTestCase { 8 | func testViewController() { 9 | XCTAssertViewIntrospection(of: PlatformViewController.self) { spies in 10 | let spy0 = spies[0] 11 | let spy1 = spies[1] 12 | let spy2 = spies[2] 13 | 14 | TabView { 15 | NavigationView { 16 | Text("Root").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) 17 | .introspect( 18 | .viewController, 19 | on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), 20 | customize: spy2 21 | ) 22 | } 23 | .navigationViewStyle(.stack) 24 | .tabItem { 25 | Image(systemName: "1.circle") 26 | Text("Tab 1") 27 | } 28 | .introspect( 29 | .viewController, 30 | on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), 31 | customize: spy1 32 | ) 33 | } 34 | .introspect( 35 | .viewController, 36 | on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), 37 | customize: spy0 38 | ) 39 | } extraAssertions: { 40 | #if !os(visionOS) 41 | XCTAssert($0[safe: 0] is UITabBarController) 42 | #endif 43 | XCTAssert($0[safe: 1] is UINavigationController) 44 | XCTAssert(String(describing: $0[safe: 2]).contains("UIHostingController")) 45 | XCTAssert($0[safe: 1] === $0[safe: 2]?.parent) 46 | } 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/ViewTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class ViewTests: XCTestCase { 7 | func testView() { 8 | XCTAssertViewIntrospection(of: PlatformView.self) { spies in 9 | let spy0 = spies[0] 10 | let spy1 = spies[1] 11 | let spy2 = spies[2] 12 | 13 | VStack(spacing: 10) { 14 | Image(systemName: "scribble").resizable().frame(height: 30) 15 | #if os(iOS) || os(tvOS) || os(visionOS) 16 | .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 17 | #elseif os(macOS) 18 | .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 19 | #endif 20 | 21 | Text("Text").frame(height: 40) 22 | #if os(iOS) || os(tvOS) || os(visionOS) 23 | .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 24 | #elseif os(macOS) 25 | .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 26 | #endif 27 | } 28 | .padding(10) 29 | #if os(iOS) || os(tvOS) || os(visionOS) 30 | .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 31 | #elseif os(macOS) 32 | .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 33 | #endif 34 | } extraAssertions: { 35 | XCTAssertEqual($0[safe: 0]?.frame.height, 30) 36 | XCTAssertEqual($0[safe: 1]?.frame.height, 40) 37 | XCTAssertEqual($0[safe: 2]?.frame.height, 100) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/Tests/ViewTypes/WindowTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIIntrospect 3 | import XCTest 4 | 5 | @MainActor 6 | final class WindowTests: XCTestCase { 7 | #if canImport(UIKit) 8 | typealias PlatformWindow = UIWindow 9 | #elseif canImport(AppKit) 10 | typealias PlatformWindow = NSWindow 11 | #endif 12 | 13 | func testWindow() { 14 | XCTAssertViewIntrospection(of: PlatformWindow.self) { spies in 15 | let spy0 = spies[0] 16 | let spy1 = spies[1] 17 | let spy2 = spies[2] 18 | 19 | VStack { 20 | Image(systemName: "scribble") 21 | #if os(iOS) || os(tvOS) || os(visionOS) 22 | .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy0) 23 | #elseif os(macOS) 24 | .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) 25 | #endif 26 | 27 | Text("Text") 28 | #if os(iOS) || os(tvOS) || os(visionOS) 29 | .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy1) 30 | #elseif os(macOS) 31 | .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) 32 | #endif 33 | } 34 | #if os(iOS) || os(tvOS) || os(visionOS) 35 | .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), .tvOS(.v13, .v14, .v15, .v16, .v17, .v18), .visionOS(.v1, .v2), customize: spy2) 36 | #elseif os(macOS) 37 | .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) 38 | #endif 39 | } extraAssertions: { 40 | XCTAssertIdentical($0[safe: 0], $0[safe: 1]) 41 | XCTAssertIdentical($0[safe: 0], $0[safe: 2]) 42 | XCTAssertIdentical($0[safe: 1], $0[safe: 2]) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Tests/WeakTests.swift: -------------------------------------------------------------------------------- 1 | @_spi(Advanced) import SwiftUIIntrospect 2 | import XCTest 3 | 4 | final class WeakTests: XCTestCase { 5 | final class Foo {} 6 | 7 | var strongFoo: Foo? = Foo() 8 | 9 | func testInit_nil() { 10 | @Weak var weakFoo: Foo? 11 | XCTAssertNil(weakFoo) 12 | } 13 | 14 | func testInit_nonNil() { 15 | @Weak var weakFoo: Foo? = strongFoo 16 | XCTAssertIdentical(weakFoo, strongFoo) 17 | } 18 | 19 | func testAssignment_nilToNil() { 20 | @Weak var weakFoo: Foo? 21 | weakFoo = nil 22 | XCTAssertNil(weakFoo) 23 | } 24 | 25 | func testAssignment_nilToNonNil() { 26 | @Weak var weakFoo: Foo? 27 | let otherFoo = Foo() 28 | weakFoo = otherFoo 29 | XCTAssertIdentical(weakFoo, otherFoo) 30 | } 31 | 32 | func testAssignment_nonNilToNil() { 33 | @Weak var weakFoo: Foo? = strongFoo 34 | weakFoo = nil 35 | XCTAssertNil(weakFoo) 36 | } 37 | 38 | func testAssignment_nonNilToNonNil() { 39 | @Weak var weakFoo: Foo? = strongFoo 40 | let otherFoo = Foo() 41 | weakFoo = otherFoo 42 | XCTAssertIdentical(weakFoo, otherFoo) 43 | } 44 | 45 | func testIndirectAssignment_nonNilToNil() { 46 | @Weak var weakFoo: Foo? = strongFoo 47 | strongFoo = nil 48 | XCTAssertNil(weakFoo) 49 | } 50 | 51 | func testIndirectAssignment_nonNilToNonNil() { 52 | @Weak var weakFoo: Foo? = strongFoo 53 | strongFoo = Foo() 54 | XCTAssertNil(weakFoo) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/TestsHostApp/TestsHostApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct App: SwiftUI.App { 5 | var body: some Scene { 6 | WindowGroup { 7 | EmptyView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Tests/UITests/StatusBarStyleUITests.swift: -------------------------------------------------------------------------------- 1 | import SnapshotTesting 2 | import XCTest 3 | 4 | final class StatusBarStyleUITests: UITestCase { 5 | override var testCase: TestCase { 6 | .statusBarStyle 7 | } 8 | 9 | func test() throws { 10 | guard #unavailable(iOS 17) else { 11 | throw XCTSkip("SimulatorStatusMagic stopped working in iOS 17, so we can no longer consistently compare status bar screenshots") 12 | } 13 | 14 | app.buttons["Navigate To Detail"].tap() 15 | app.buttons["Navigate To Detail"].tap() 16 | app.buttons["Navigate Back"].tap() 17 | 18 | let iOSDevice = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone" 19 | let iOSVersion = ProcessInfo().operatingSystemVersion 20 | func screenshotName(_ number: Int) -> String { 21 | "\(iOSDevice)-ios-\(iOSVersion.majorVersion)-screenshot-\(number)" 22 | } 23 | 24 | assertSnapshot( 25 | matching: app.windows.firstMatch.screenshot().image, 26 | as: .image(perceptualPrecision: 0.95), 27 | named: screenshotName(1) 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/UITests/UITestCase.swift: -------------------------------------------------------------------------------- 1 | import SimulatorStatusMagic 2 | import XCTest 3 | 4 | class UITestCase: XCTestCase { 5 | var testCase: TestCase { 6 | preconditionFailure("Please override this property") 7 | } 8 | 9 | let app = XCUIApplication() 10 | 11 | override func invokeTest() { 12 | SDStatusBarManager.sharedInstance().enableOverrides() 13 | 14 | continueAfterFailure = false 15 | 16 | app.launchEnvironment["testCase"] = testCase.rawValue 17 | app.launch() 18 | 19 | super.invokeTest() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/UITests/UITests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "DD0EEECD-4762-4A68-91A8-F7B5A2209B45", 5 | "name" : "Test Scheme Action", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "target" : { 17 | "containerPath" : "container:Tests.xcodeproj", 18 | "identifier" : "D58D832A2A66BDD500A203BE", 19 | "name" : "UITests" 20 | } 21 | } 22 | ], 23 | "version" : 1 24 | } 25 | -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-13-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-13-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-14-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-14-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-15-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-15-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-16-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-16-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-13-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-13-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-14-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-14-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-15-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-15-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-16-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siteline/swiftui-introspect/807f73ce09a9b9723f12385e592b4e0aaebd3336/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-16-screenshot-1.png -------------------------------------------------------------------------------- /Tests/UITestsHostApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIViewControllerBasedStatusBarAppearance 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/StatusBarStyle/HostingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostingController.swift 3 | // Blago 4 | // 5 | // Created by Dmytro Chumakov on 04.05.2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | final class HostingController: UIHostingController where ContentView: View { 11 | 12 | var statusBarStyle: UIStatusBarStyle = .darkContent 13 | var isInteractivePopGestureEnabled = true 14 | 15 | override var preferredStatusBarStyle: UIStatusBarStyle { 16 | statusBarStyle 17 | } 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | navigationController?.interactivePopGestureRecognizer?.isEnabled = isInteractivePopGestureEnabled 22 | } 23 | 24 | override func viewWillLayoutSubviews() { 25 | super.viewWillLayoutSubviews() 26 | 27 | guard #available(iOS 16, *) else { 28 | navigationController?.setNavigationBarHidden(true, animated: false) 29 | return 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/StatusBarStyle/NavigationView.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // NavigationController.swift 4 | // Blago 5 | // 6 | // Created by Dmytro Chumakov on 11.05.2023. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - NavigationController 12 | 13 | final class NavigationController: UINavigationController { 14 | 15 | static var shared: UINavigationController? 16 | 17 | // MARK: Lifecycle 18 | 19 | override init(rootViewController: UIViewController) { 20 | super.init(rootViewController: rootViewController) 21 | setNavigationBarHidden(true, animated: false) 22 | } 23 | 24 | required init?(coder _: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | override var preferredStatusBarStyle: UIStatusBarStyle { 29 | topViewController?.preferredStatusBarStyle ?? .default 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | interactivePopGestureRecognizer?.delegate = self 35 | } 36 | } 37 | 38 | // MARK: UIGestureRecognizerDelegate 39 | 40 | extension NavigationController: UIGestureRecognizerDelegate { 41 | func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool { 42 | viewControllers.count > 1 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/StatusBarStyle/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootView.swift 3 | // Showcase 4 | // 5 | // Created by Orackle on 14.07.2023. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftUIIntrospect 10 | 11 | struct RootView: View { 12 | 13 | var body: some View { 14 | VStack { 15 | Button("Navigate To Detail", action: navigateToDetail) 16 | } 17 | } 18 | 19 | @MainActor // for below Swift 6.0 20 | private func navigateToDetail() { 21 | let controller = HostingController(rootView: DetailView()) 22 | controller.statusBarStyle = .lightContent 23 | NavigationController.shared?.pushViewController(controller, animated: true) 24 | } 25 | } 26 | 27 | struct DetailView: View { 28 | @Environment(\.presentationMode) var dismiss 29 | 30 | var body: some View { 31 | ZStack { 32 | Color.red.edgesIgnoringSafeArea(.all) 33 | 34 | VStack { 35 | Button("Navigate To Detail", action: navigateToDetail) 36 | Button("Navigate Back", action: goBack) 37 | } 38 | } 39 | .introspect(.viewController, on: .iOS(.v13, .v14, .v15, .v16)) { viewController in 40 | /// some customizations there 41 | } 42 | } 43 | 44 | private func goBack() { 45 | dismiss.wrappedValue.dismiss() 46 | } 47 | 48 | @MainActor // for below Swift 6.0 49 | private func navigateToDetail() { 50 | let controller = HostingController(rootView: DetailView()) 51 | controller.statusBarStyle = .lightContent 52 | NavigationController.shared?.pushViewController(controller, animated: true) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/TestCases.swift: -------------------------------------------------------------------------------- 1 | public enum TestCase: String { 2 | case statusBarStyle = "Status Bar Style" 3 | } 4 | -------------------------------------------------------------------------------- /Tests/UITestsHostApp/UITestsHostApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | final class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | window = UIWindow(frame: UIScreen.main.bounds) 10 | guard 11 | let testCaseRawValue = ProcessInfo.processInfo.environment["testCase"], 12 | let testCase = TestCase(rawValue: testCaseRawValue) 13 | else { 14 | preconditionFailure("entryViewController not set") 15 | } 16 | 17 | window?.rootViewController = { 18 | switch testCase { 19 | case .statusBarStyle: 20 | let navController = NavigationController(rootViewController: HostingController(rootView: RootView())) 21 | NavigationController.shared = navController 22 | return navController 23 | } 24 | }() 25 | window?.makeKeyAndVisible() 26 | return true 27 | } 28 | } 29 | --------------------------------------------------------------------------------