diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c09dd18a6..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,62 +0,0 @@ - -# Shared Xcode version between jobs -xcode-version: &xcode-version "12.4.0" - -# This is the key that we use to store and restore our dependencies cache. -# It needs to be different anytime we add Pods, Gems, or npm packages. -# If something went wrong during a cache build, you can increase the cache version -# like this: 1-dependencies-... => 2-dependencies-... -cache-key: &cache-key 4-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Podfile.lock" }} - -# Environment that is shared between jobs -shared-env: &shared-env - BUNDLE_PATH: vendor/bundle - FL_OUTPUT_DIR: output - HOMEBREW_NO_AUTO_UPDATE: 1 - -version: 2 -jobs: - build: - macos: - xcode: *xcode-version - environment: - <<: *shared-env - steps: - - checkout - - restore_cache: - key: *cache-key - - run: - name: Install bundler - command: gem install bundler:1.17.3 - - run: - name: Install Ruby bundle dependencies - command: bundle check || bundle install - - run: - name: Install Cocoapods dependencies - command: diff ./Podfile.lock ./Pods/Manifest.lock > /dev/null || bundle exec pod install - - save_cache: - key: *cache-key - paths: - - vendor/bundle/ - - Pods/ - - run: - name: Create reports directory - command: mkdir output - - run: - name: Test iOS/tvOS framework - command: bundle exec fastlane ios test - - run: - name: Test macOS framework - command: bundle exec fastlane mac test - - run: - name: Lint podspec - command: bundle exec pod lib lint - - run: - name: Build swift package - command: swift build - - run: - name: Test swift package - command: swift test - - store_test_results: - path: output - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..827f8c14a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,71 @@ +name: Bug Report +description: Something isn't working as expected +labels: [bug] +body: +- type: markdown + attributes: + value: | + Thank you for contributing to SwiftUI Introspect! + + 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. +- type: textarea + attributes: + label: Description + description: | + A short description of the incorrect behavior. + + 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. + validations: + required: true +- type: checkboxes + attributes: + label: Checklist + options: + - label: I have read the [README](https://github.com/siteline/swiftui-introspect#swiftui-introspect) before submitting this report. + required: true + - 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). + required: true +- type: textarea + attributes: + label: Expected behavior + description: Describe what you expected to happen. + validations: + required: false +- type: textarea + attributes: + label: Actual behavior + description: Describe or copy/paste the behavior you observe. + validations: + required: false +- type: textarea + attributes: + label: Steps to reproduce + description: | + Explanation of how to reproduce the incorrect behavior. + + This could include an attached project or link to code that is exhibiting the issue, and/or a screen recording. + placeholder: | + 1. ... + validations: + required: false +- type: input + attributes: + label: Version information + description: The version of SwiftUIIntrospect used to reproduce this issue. + placeholder: "'0.11.0' for example, or a commit hash" +- type: input + attributes: + label: Destination operating system + description: The OS running the SwiftUIIntrospect module. + placeholder: "'iOS 17' for example" +- type: input + attributes: + label: Xcode version information + description: The version of Xcode used to reproduce this issue. + placeholder: "The version displayed from 'Xcode 〉About Xcode'" +- type: textarea + attributes: + label: Swift Compiler version information + description: The version of Swift used to reproduce this issue. + placeholder: Output from 'xcrun swiftc --version' + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..ed0d557fc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +blank_issues_enabled: false + +contact_links: + - name: Project Discussion + url: https://github.com/siteline/swiftui-introspect/discussions + about: Q&A, ideas, and more + - name: Documentation + url: https://github.com/siteline/swiftui-introspect#swiftui-introspect + about: Read SwiftUI Introspect's documentation diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml deleted file mode 100644 index 836fb0b6e..000000000 --- a/.github/workflows/build-and-test.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Build and Test Package -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - runs-on: macos-10.15 - steps: - - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Setup Xcode 12 - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '12.4' - - - name: Export macOS SDK - run: echo SDKROOT=$(xcrun --sdk macosx --show-sdk-path) >> $GITHUB_ENV - - - name: Install gem dependencies - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Cache cocoapods dependencies - uses: actions/cache@v2 - id: cache-pods - with: - path: Pods - key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} - restore-keys: ${{ runner.os }}-pods- - - - name: Install cocoapods dependencies - if: steps.cache-pods.outputs.cache-hit != 'true' - run: bundle exec pod install - - - name: Build Swift Package - run: swift build -j 2 --disable-index-store -v - - - name: Test Framework on iOS and tvOS - run: bundle exec fastlane ios test ci:github - - - name: Test Framework on macOS - run: bundle exec fastlane mac test ci:github - - - name: Lint podspec - run: bundle exec pod lib lint diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 000000000..9bd0fef3f --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,30 @@ +name: cd + +on: + push: + tags: + - '*' + +jobs: + deploy: + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + podspec: + - Introspect.podspec + - SwiftUIIntrospect.podspec + steps: + - name: Git Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 # required to be able to find Git tags + + - name: Deploy to CocoaPods Trunk (${{ matrix.podspec }}) + run: | + set -eo pipefail + export LIB_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) + pod lib lint ${{ matrix.podspec }} --allow-warnings + pod trunk push ${{ matrix.podspec }} --allow-warnings + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..e2f5c8c1f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,154 @@ +name: ci + +on: + push: + branches: + - master + pull_request: + branches: + - "**" + schedule: + - cron: '3 3 * * 2' # 3:03 AM, every Tuesday + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-podspecs: + name: lint podspecs + runs-on: macos-latest + steps: + - name: Git Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 # required to be able to find Git tags + + - name: Lint Introspect.podspec + run: | + set -eo pipefail + export LIB_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) + pod lib lint Introspect.podspec --allow-warnings + + - name: Lint SwiftUIIntrospect.podspec + run: | + set -eo pipefail + export LIB_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) + pod lib lint SwiftUIIntrospect.podspec --allow-warnings + + ci: + name: ${{ matrix.platform[0] }} ${{ matrix.platform[1] }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - platform: [ios, 13] + runtime: iOS 13.7 + os: macos-12 + xcode: 14.2 + install: true + - platform: [ios, 14] + runtime: iOS 14.5 + os: macos-13 + xcode: 14.3.1 + install: true + - platform: [ios, 15] + runtime: iOS 15.5 + os: macos-13 + xcode: 15.0 + install: true + - platform: [ios, 16] + runtime: iOS 16.4 + os: macos-13 + xcode: 14.3.1 + - platform: [ios, 17] + runtime: iOS 17.0 + os: macos-13 + xcode: 15.0 + + - platform: [tvos, 13] + runtime: tvOS 13.4 + os: macos-12 + xcode: 14.2 + install: true + - platform: [tvos, 14] + runtime: tvOS 14.5 + os: macos-13 + xcode: 14.3.1 + install: true + - platform: [tvos, 15] + runtime: tvOS 15.4 + os: macos-13 + xcode: 15.0 + install: true + - platform: [tvos, 16] + runtime: tvOS 16.4 + os: macos-13 + xcode: 15.0 + - platform: [tvos, 17] + runtime: tvOS 17.0 + os: macos-13 + xcode: 15.0 + + - platform: [macos, 11] + runtime: macOS 11 + os: macos-11 + xcode: 13.2.1 + - platform: [macos, 12] + runtime: macOS 12 + os: macos-12 + xcode: 14.2 + - platform: [macos, 13] + runtime: macOS 13 + os: macos-13 + xcode: 15.0 + + # FIXME: this currently hangs on CI + # - platform: [visionos, 1] + # runtime: visionOS 1.0-beta2 + # os: macos-13 + # xcode: 15.0 + # install: true + steps: + - name: Git Checkout + uses: actions/checkout@v3 + + - name: Setup tea environment + uses: teaxyz/setup@v0 + with: + +: | + github.com/XcodesOrg/xcodes + + - name: Select Xcode version + run: sudo xcodes select ${{ matrix.xcode }} + + - if: ${{ matrix.install }} + name: Install Required Runtime (${{ matrix.runtime }}) + uses: nick-fields/retry@v2 + with: + timeout_minutes: 15 + max_attempts: 3 + command: sudo xcodes runtimes install '${{ matrix.runtime }}' + + - if: false + name: '[Debug] List Available Runtimes, Simulators, and Destinations' + run: | + xcrun simctl list + xcodebuild -scheme "Showcase" -showdestinations + + - if: ${{ join(matrix.platform, ' ') != 'macos 11' }} + name: Build Showcase + run: fastlane build platform:${{ matrix.platform[0] }} version:${{ matrix.platform[1] }} scheme:Showcase + + - if: ${{ join(matrix.platform, ' ') != 'ios 13' && join(matrix.platform, ' ') != 'tvos 13' && join(matrix.platform, ' ') != 'ios 17' && join(matrix.platform, ' ') != 'tvos 17' && matrix.platform[0] != 'visionos' }} + name: Run Tests (Introspect) + run: fastlane test platform:${{ matrix.platform[0] }} version:${{ matrix.platform[1] }} scheme:Introspect + + - if: ${{ join(matrix.platform, ' ') != 'macos 11' }} + name: Run Tests (SwiftUIIntrospect) + run: fastlane test platform:${{ matrix.platform[0] }} version:${{ matrix.platform[1] }} scheme:SwiftUIIntrospectTests configuration:Debug + + - if: ${{ matrix.platform[0] == 'ios' && matrix.platform[1] <= '16' }} + name: Run UI Tests (SwiftUIIntrospect) + run: fastlane test platform:${{ matrix.platform[0] }} version:${{ matrix.platform[1] }} scheme:SwiftUIIntrospectUITests configuration:Debug diff --git a/.gitignore b/.gitignore index d43323d26..4f0a90591 100644 --- a/.gitignore +++ b/.gitignore @@ -1,87 +1,13 @@ .DS_Store - -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## User settings +/.build +/.swiftpm +/Packages +/*.xcodeproj xcuserdata/ - -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) -*.xcscmblueprint -*.xccheckout - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) -build/ DerivedData/ -*.moved-aside -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 - -## Obj-C/Swift specific -*.hmap - -## App packaging -*.ipa -*.dSYM.zip -*.dSYM - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - -# Swift Package Manager -# -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -# Package.pins -# Package.resolved -# *.xcodeproj -# -# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata -# hence it is not needed unless you have added a package configuration file to your project -# .swiftpm - -.build/ - -# CocoaPods -Pods/ -# -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build/ - -# Accio dependency management -Dependencies/ -.accio/ - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control +.netrc fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output - -# Code Injection -# -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 37c2961c2..000000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.7.2 diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 000000000..c139fb9cd --- /dev/null +++ b/.spi.yml @@ -0,0 +1,5 @@ +version: 1 +builder: + configs: + - documentation_targets: [SwiftUIIntrospect] + custom_documentation_parameters: [--include-extended-types] diff --git a/CHANGELOG.md b/CHANGELOG.md index 41efcd5d4..9d00ca4c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,166 @@ Changelog ## master +- Infrastructure: removed min iOS version constraint for UI Tests (#343) + +## [0.12.0] + +- Added: `@Weak` property wrapper (#341) +- Documentation: added advanced usage section to README (#341) +- Documentation: added community projects section to README (#342) + +## [0.11.1] + +- Fixed: `@_spi` errors (#339) + +## [0.11.0] + +- Added: visionOS support (#327) +- Infrastructure: run CI tests on iOS & tvOS 17 (#323) + +## [0.10.0] + +- Added: `SecureField` introspection (#317) + +## [0.9.2] + +- Fixed: occasionally wrong status bar style (#313) +- Infrastructure: added UI Test suite (#314) +- Infrastructure: disabled "Autocreate schemes" (#308) + +## [0.9.1] + +- Fixed: only box up content for `.view` introspection (#305) + +## [0.9.0] + +- Added: view controller introspection (#298) +- Added: page control introspection (#297) + +## [0.8.0] + +- Added: `Map` introspection (#288) +- Added: advanced range-based platform version predicates (#285) +- Changed: renamed `@_spi(Internals)` to `@_spi(Advanced)` (#290) +- Documentation: generate docs for extensions (#282) +- Infrastructure: set up `tea` for CI and local environments (#276) + +## [0.7.0] + +### SwiftUIIntrospect + +- Added: window introspection (#269) +- Added: `.sheet` introspection (#268) +- Added: `.fullScreenCover` introspection (#268) +- Added: `.popover` introspection (#268) +- Added: `VideoPlayer` introspection (#264) +- Added: `SignInWithAppleButton` introspection (#265) +- Added: `View` introspection on macOS (#266) +- Improved: `View` introspection accuracy (#266) +- Documentation: added some more docs for public symbols (#273) + +### Introspect + +This module is now deprecated (#272) and will be removed later this year (whenever iOS/tvOS 17 come out). + +## [0.6.3] + +### SwiftUIIntrospect + +- Changed: disabled accessibility for introspection views (#261) +- Documentation: code samples are now split by OS (#262) +- Infrastructure: use [`xcodes`](https://github.com/XcodesOrg/xcodes) via [`tea`](https://github.com/teaxyz/cli) on CI (#261) + +## [0.6.2] + +### SwiftUIIntrospect + +- Documentation: added docs for all view types (#258) +- Infrastructure: fixed iOS/tvOS 13 checks on CI (#257) + +## [0.6.1] + +### SwiftUIIntrospect + +- Improved: optimized receiver lookup algorithm (#246) +- Infrastructure: refactored `.introspect` to use `ViewModifier` (#253) +- Infrastructure: retry runtime download on timeout or error on CI (#247) + +## [0.6.0] + +### SwiftUIIntrospect + +- Added: iOS 17 / tvOS 17 / macOS 14 compatibility (#243) + +### Introspect + +- Fixed: `UIColorWell` build error on tvOS 13 (#217) + +## [0.5.2] + +### SwiftUIIntrospect + +- Added: selector overrides (#239) +- Changed: optimized ancestor controller selectors (#240) + +## [0.5.1] + +### SwiftUIIntrospect + +- Fixed: SwiftUIIntrospect.podspec (#237) + +## [0.5.0] + +### SwiftUIIntrospect + +- Added: support for custom selectors (#233) +- Changed: unified introspect modifiers into one (#232) +- Fixed: `searchField` introspection (#234) +- Documentation: added explicit SPI import (#229) + +## [0.4.0] + +- Added: all-new implementation, API, and module (SwiftUIIntrospect) (#207) + +## [0.3.1] + +- Fixed: wrong Swift version in podspec (#220) + +## [0.3.0] + +- Changed: minimum language version required is now Swift 5.5 (#209) +- Fixed: finding UIScrollViews that are clipped(), masked or combined with clipShape() or cornerRadius() (#213) +- Documentation: UICollectionView introspection support in README (#218) +- Infrastructure: symlink older SDKs to use in newer Xcode versions on CI (#208) + +## [0.2.3] + +- Fixed: `introspectPagedTabView` on iOS 16 (#200) +- Infrastructure: auto-deploy to CocoaPods (#201) + +## [0.2.2] + +- Hotfix: #192 (#196) + +## [0.2.1] + +- Fixed: memory leak in #161 and regression in #194 (#192) + +## [0.2.0] + +- Added: `introspectCollectionView/introspectCollectionViewCell` (#169) +- Added: `introspectSearchController` (#129) +- Added: `introspectPagedTabView` (#117) +- Added: `introspectMapView` (#125) +- Added: `introspectSplitView` on macOS (#100) +- Added: explicitly static/dynamic SPM library products (#168) +- Fixed: view controller introspection (#165) +- Fixed: issue where introspecting within a LazyVStack would silently fail #153 +- Infrastructure: test coverage now spans iOS/tvOS 14/15/16 and macOS 11/12 (#185) +- Infrastructure: removed CircleCI in favor of GitHub Actions (#182, #183) + +## [0.1.4] + - Added `.introspectSplitViewController()` on iOS - Fixed iPad tests - Added iPad to CI @@ -59,11 +219,12 @@ Changelog ## [0.0.2] - Added documentation for all methods. - + ## [0.0.1] - First release. +[0.2.0]: https://github.com/timbersoftware/SwiftUI-Introspect/releases/tag/0.2.0 [0.1.3]: https://github.com/timbersoftware/SwiftUI-Introspect/releases/tag/0.1.3 [0.1.2]: https://github.com/timbersoftware/SwiftUI-Introspect/releases/tag/0.1.2 [0.1.1]: https://github.com/timbersoftware/SwiftUI-Introspect/releases/tag/0.1.1 diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/Examples/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata similarity index 100% rename from .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata rename to Examples/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/Examples/Package.swift b/Examples/Package.swift new file mode 100644 index 000000000..dbea1ee81 --- /dev/null +++ b/Examples/Package.swift @@ -0,0 +1,9 @@ +// swift-tools-version:5.4 + +import PackageDescription + +let package = Package( + name: "Examples", + products: [], + targets: [] +) diff --git a/Examples/Showcase/Showcase.xcodeproj/project.pbxproj b/Examples/Showcase/Showcase.xcodeproj/project.pbxproj new file mode 100644 index 000000000..2b7edd2c5 --- /dev/null +++ b/Examples/Showcase/Showcase.xcodeproj/project.pbxproj @@ -0,0 +1,372 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + D53071F729983CEF00F1936C /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53071F629983CEF00F1936C /* App.swift */; }; + D53071F929983CEF00F1936C /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53071F829983CEF00F1936C /* AppView.swift */; }; + D5B829752999738200920EBD /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B829742999738200920EBD /* Helpers.swift */; }; + D5E3180329C132B6005847DC /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D5E3180229C132B6005847DC /* SwiftUIIntrospect */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + D53071F329983CEF00F1936C /* Showcase.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Showcase.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D53071F629983CEF00F1936C /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; + D53071F829983CEF00F1936C /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; + D530720429983D9300F1936C /* Showcase.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Showcase.entitlements; sourceTree = ""; }; + D5B829742999738200920EBD /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D53071F029983CEF00F1936C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D5E3180329C132B6005847DC /* SwiftUIIntrospect in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D53071EA29983CEF00F1936C = { + isa = PBXGroup; + children = ( + D53071F529983CEF00F1936C /* Showcase */, + D53071F429983CEF00F1936C /* Products */, + D530720529983DCA00F1936C /* Frameworks */, + ); + sourceTree = ""; + }; + D53071F429983CEF00F1936C /* Products */ = { + isa = PBXGroup; + children = ( + D53071F329983CEF00F1936C /* Showcase.app */, + ); + name = Products; + sourceTree = ""; + }; + D53071F529983CEF00F1936C /* Showcase */ = { + isa = PBXGroup; + children = ( + D530720429983D9300F1936C /* Showcase.entitlements */, + D53071F629983CEF00F1936C /* App.swift */, + D53071F829983CEF00F1936C /* AppView.swift */, + D5B829742999738200920EBD /* Helpers.swift */, + ); + path = Showcase; + sourceTree = ""; + }; + D530720529983DCA00F1936C /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D53071F229983CEF00F1936C /* Showcase */ = { + isa = PBXNativeTarget; + buildConfigurationList = D530720129983CF000F1936C /* Build configuration list for PBXNativeTarget "Showcase" */; + buildPhases = ( + D53071EF29983CEF00F1936C /* Sources */, + D53071F029983CEF00F1936C /* Frameworks */, + D53071F129983CEF00F1936C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Showcase; + packageProductDependencies = ( + D5E3180229C132B6005847DC /* SwiftUIIntrospect */, + ); + productName = Showcase; + productReference = D53071F329983CEF00F1936C /* Showcase.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D53071EB29983CEF00F1936C /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1420; + LastUpgradeCheck = 1420; + TargetAttributes = { + D53071F229983CEF00F1936C = { + CreatedOnToolsVersion = 14.2; + }; + }; + }; + buildConfigurationList = D53071EE29983CEF00F1936C /* Build configuration list for PBXProject "Showcase" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D53071EA29983CEF00F1936C; + productRefGroup = D53071F429983CEF00F1936C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D53071F229983CEF00F1936C /* Showcase */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D53071F129983CEF00F1936C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D53071EF29983CEF00F1936C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D53071F929983CEF00F1936C /* AppView.swift in Sources */, + D5B829752999738200920EBD /* Helpers.swift in Sources */, + D53071F729983CEF00F1936C /* App.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + D53071FF29983CF000F1936C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos"; + SUPPORTS_MACCATALYST = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TVOS_DEPLOYMENT_TARGET = 13.0; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Debug; + }; + D530720029983CF000F1936C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos"; + SUPPORTS_MACCATALYST = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TVOS_DEPLOYMENT_TARGET = 13.0; + VALIDATE_PRODUCT = YES; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Release; + }; + D530720229983CF000F1936C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Showcase/Showcase.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UILaunchStoryboardName = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Showcase; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; + }; + name = Debug; + }; + D530720329983CF000F1936C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Showcase/Showcase.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UILaunchStoryboardName = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Showcase; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D53071EE29983CEF00F1936C /* Build configuration list for PBXProject "Showcase" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D53071FF29983CF000F1936C /* Debug */, + D530720029983CF000F1936C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D530720129983CF000F1936C /* Build configuration list for PBXNativeTarget "Showcase" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D530720229983CF000F1936C /* Debug */, + D530720329983CF000F1936C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + D5E3180229C132B6005847DC /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + productName = SwiftUIIntrospect; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = D53071EB29983CEF00F1936C /* Project object */; +} diff --git a/Introspect.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/Showcase/Showcase.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 70% rename from Introspect.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Examples/Showcase/Showcase.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 3bd6eed33..919434a62 100644 --- a/Introspect.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/Examples/Showcase/Showcase.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Introspect.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/Showcase/Showcase.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Introspect.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Examples/Showcase/Showcase.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect macOS.xcscheme b/Examples/Showcase/Showcase.xcodeproj/xcshareddata/xcschemes/Showcase.xcscheme similarity index 65% rename from Introspect.xcodeproj/xcshareddata/xcschemes/Introspect macOS.xcscheme rename to Examples/Showcase/Showcase.xcodeproj/xcshareddata/xcschemes/Showcase.xcscheme index 8a729eff0..1533efdd2 100644 --- a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect macOS.xcscheme +++ b/Examples/Showcase/Showcase.xcodeproj/xcshareddata/xcschemes/Showcase.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "D53071F229983CEF00F1936C" + BuildableName = "Showcase.app" + BlueprintName = "Showcase" + ReferencedContainer = "container:Showcase.xcodeproj"> @@ -28,16 +28,6 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + - + + BlueprintIdentifier = "D53071F229983CEF00F1936C" + BuildableName = "Showcase.app" + BlueprintName = "Showcase" + ReferencedContainer = "container:Showcase.xcodeproj"> - + diff --git a/Examples/Showcase/Showcase/App.swift b/Examples/Showcase/Showcase/App.swift new file mode 100644 index 000000000..1a772f53b --- /dev/null +++ b/Examples/Showcase/Showcase/App.swift @@ -0,0 +1,31 @@ +import SwiftUI + +#if os(iOS) || os(tvOS) +@UIApplicationMain +final class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = UIHostingController(rootView: AppView()) + window?.makeKeyAndVisible() + return true + } +} +#elseif os(macOS) || os(visionOS) +@main +struct App: SwiftUI.App { + var body: some Scene { + WindowGroup { + AppView() + } + } +} +#endif + +#if swift(>=5.9) +#Preview { + AppView() +} +#endif diff --git a/Examples/Showcase/Showcase/AppView.swift b/Examples/Showcase/Showcase/AppView.swift new file mode 100644 index 000000000..f00f5a709 --- /dev/null +++ b/Examples/Showcase/Showcase/AppView.swift @@ -0,0 +1,534 @@ +import SwiftUI +import SwiftUIIntrospect + +struct AppView: View { + var body: some View { + ContentView() + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .window, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { window in + window.backgroundColor = .brown + } + #elseif os(macOS) + .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { window in + window.backgroundColor = .lightGray + } + #endif + } +} + +struct ContentView: View { + @State var selection = 0 + + var body: some View { + TabView(selection: $selection) { + ListShowcase() + .tabItem { Text("List") } + .tag(0) + ScrollViewShowcase() + .tabItem { Text("ScrollView") } + .tag(1) + #if !os(macOS) + NavigationShowcase() + .tabItem { Text("Navigation") } + .tag(2) + PresentationShowcase() + .tabItem { Text("Presentation") } + .tag(3) + #endif + GenericViewShowcase() + .tabItem { Text("Generic View") } + .tag(4) + SimpleElementsShowcase() + .tabItem { Text("Simple elements") } + .tag(5) + } + #if os(iOS) || os(tvOS) + .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17)) { tabBarController in + tabBarController.tabBar.layer.backgroundColor = UIColor.green.cgColor + } + #elseif os(macOS) + .introspect(.tabView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { splitView in + splitView.subviews.first?.layer?.backgroundColor = NSColor.green.cgColor + } + #endif + .preferredColorScheme(.light) + } +} + +struct ListShowcase: View { + var body: some View { + VStack(spacing: 40) { + VStack { + Text("Default") + .lineLimit(1) + .minimumScaleFactor(0.5) + .padding(.horizontal, 12) + List { + Text("Item 1") + Text("Item 2") + } + } + + VStack { + Text(".introspect(.list, ...)") + .lineLimit(1) + .minimumScaleFactor(0.5) + .padding(.horizontal, 12) + .font(.system(.subheadline, design: .monospaced)) + List { + Text("Item 1") + Text("Item 2") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { tableView in + tableView.backgroundView = UIView() + tableView.backgroundColor = .cyan + } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1)) { collectionView in + collectionView.backgroundView = UIView() + collectionView.subviews.dropFirst(1).first?.backgroundColor = .cyan + } + #elseif os(macOS) + .introspect(.list, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { tableView in + tableView.backgroundColor = .cyan + } + #endif + } + + VStack { + Text(".introspect(.list, ..., scope: .ancestor)") + .lineLimit(1) + .minimumScaleFactor(0.5) + .padding(.horizontal, 12) + .font(.system(.subheadline, design: .monospaced)) + List { + Text("Item 1") + Text("Item 2") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor) { tableView in + tableView.backgroundView = UIView() + tableView.backgroundColor = .cyan + } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { collectionView in + collectionView.backgroundView = UIView() + collectionView.subviews.dropFirst(1).first?.backgroundColor = .cyan + } + #elseif os(macOS) + .introspect(.list, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { tableView in + tableView.backgroundColor = .cyan + } + #endif + } + } + } + + } +} + +struct ScrollViewShowcase: View { + var body: some View { + VStack(spacing: 40) { + ScrollView { + Text("Default") + .frame(maxWidth: .infinity) + .lineLimit(1) + .minimumScaleFactor(0.5) + .padding(.horizontal, 12) + } + + ScrollView { + Text(".introspect(.scrollView, ...)") + .frame(maxWidth: .infinity) + .lineLimit(1) + .minimumScaleFactor(0.5) + .padding(.horizontal, 12) + .font(.system(.subheadline, design: .monospaced)) + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .scrollView, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { scrollView in + scrollView.layer.backgroundColor = UIColor.cyan.cgColor + } + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { scrollView in + scrollView.drawsBackground = true + scrollView.backgroundColor = .cyan + } + #endif + + ScrollView { + Text(".introspect(.scrollView, ..., scope: .ancestor)") + .frame(maxWidth: .infinity) + .lineLimit(1) + .minimumScaleFactor(0.5) + .padding(.horizontal, 12) + .font(.system(.subheadline, design: .monospaced)) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .scrollView, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), + scope: .ancestor + ) { scrollView in + scrollView.layer.backgroundColor = UIColor.cyan.cgColor + } + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { scrollView in + scrollView.drawsBackground = true + scrollView.backgroundColor = .cyan + } + #endif + } + } + } +} + +struct NavigationShowcase: View { + var body: some View { + NavigationView { + Text("Content") + .modifier { + if #available(iOS 15, tvOS 15, macOS 12, *) { + $0.searchable(text: .constant("")) + } else { + $0 + } + } + #if os(iOS) || os(visionOS) + .navigationBarTitle(Text("Customized"), displayMode: .inline) + #elseif os(macOS) + .navigationTitle(Text("Navigation")) + #endif + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .navigationView(style: .stack), + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { navigationController in + navigationController.navigationBar.backgroundColor = .cyan + } + .introspect( + .navigationView(style: .columns), + on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { splitViewController in + #if os(visionOS) + splitViewController.preferredDisplayMode = .oneBesideSecondary + #else + splitViewController.preferredDisplayMode = .oneOverSecondary + #endif + } + .introspect(.navigationView(style: .columns), on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { navigationController in + navigationController.navigationBar.backgroundColor = .cyan + } + .introspect( + .searchField, + on: .iOS(.v15, .v16, .v17), .tvOS(.v15, .v16, .v17), .visionOS(.v1) + ) { searchBar in + searchBar.backgroundColor = .red + #if os(iOS) + searchBar.searchTextField.backgroundColor = .purple + #endif + } + #endif + } +} + +#if !os(macOS) +struct PresentationShowcase: View { + @State var isSheetPresented = false + @State var isFullScreenPresented = false + @State var isPopoverPresented = false + + var body: some View { + VStack(spacing: 20) { + Button("Sheet", action: { isSheetPresented = true }) + .sheet(isPresented: $isSheetPresented) { + Button("Dismiss", action: { isSheetPresented = false }) + #if os(iOS) || os(tvOS) + .introspect( + .sheet, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17) + ) { presentationController in + presentationController.containerView?.backgroundColor = .red.withAlphaComponent(0.75) + } + #elseif os(visionOS) + .introspect(.sheet, on: .visionOS(.v1)) { sheetPresentationController in + sheetPresentationController.containerView?.backgroundColor = .red.withAlphaComponent(0.75) + } + #endif + } + + if #available(iOS 14, tvOS 14, *) { + Button("Full Screen Cover", action: { isFullScreenPresented = true }) + .fullScreenCover(isPresented: $isFullScreenPresented) { + Button("Dismiss", action: { isFullScreenPresented = false }) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .fullScreenCover, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1) + ) { presentationController in + presentationController.containerView?.backgroundColor = .red.withAlphaComponent(0.75) + } + #endif + } + } + + #if os(iOS) || os(visionOS) + Button("Popover", action: { isPopoverPresented = true }) + .popover(isPresented: $isPopoverPresented) { + Button("Dismiss", action: { isPopoverPresented = false }) + .padding() + .introspect( + .popover, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { presentationController in + presentationController.containerView?.backgroundColor = .red.withAlphaComponent(0.75) + } + } + #endif + } + } +} +#endif + +struct GenericViewShowcase: View { + var body: some View { + VStack(spacing: 10) { + Text(".introspect(.view, ...)") + .lineLimit(1) + .minimumScaleFactor(0.5) + .font(.system(.subheadline, design: .monospaced)) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .view, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { view in + view.backgroundColor = .cyan + } + #elseif os(macOS) + .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { view in + view.layer?.backgroundColor = NSColor.cyan.cgColor + } + #endif + + Button("A button", action: {}) + .padding(5) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .view, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { view in + view.backgroundColor = .lightGray + } + #elseif os(macOS) + .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { view in + view.layer?.backgroundColor = NSColor.lightGray.cgColor + } + #endif + + Image(systemName: "scribble") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .view, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { view in + view.backgroundColor = .blue + } + #elseif os(macOS) + .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { view in + view.layer?.backgroundColor = NSColor.blue.cgColor + } + #endif + } + .padding() + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .view, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { view in + view.backgroundColor = .red + } + #elseif os(macOS) + .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { view in + view.layer?.backgroundColor = NSColor.red.cgColor + } + #endif + } +} + +struct SimpleElementsShowcase: View { + + @State private var textFieldValue = "" + @State private var toggleValue = false + @State private var sliderValue = 0.0 + @State private var datePickerValue = Date() + @State private var segmentedControlValue = 0 + + var body: some View { + VStack { + HStack { + TextField("Text Field Red", text: $textFieldValue) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .textField, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { textField in + textField.backgroundColor = .red + } + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { textField in + textField.backgroundColor = .red + } + #endif + + TextField("Text Field Green", text: $textFieldValue) + .cornerRadius(8) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .textField, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { textField in + textField.backgroundColor = .green + } + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { textField in + textField.backgroundColor = .green + } + #endif + } + + HStack { + Toggle("Toggle Red", isOn: $toggleValue) + #if os(iOS) || os(visionOS) + .introspect( + .toggle, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { toggle in + toggle.backgroundColor = .red + } + #elseif os(macOS) + .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { toggle in + toggle.layer?.backgroundColor = NSColor.red.cgColor + } + #endif + + Toggle("Toggle Green", isOn: $toggleValue) + #if os(iOS) || os(visionOS) + .introspect( + .toggle, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { toggle in + toggle.backgroundColor = .green + } + #elseif os(macOS) + .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { toggle in + toggle.layer?.backgroundColor = NSColor.green.cgColor + } + #endif + } + + #if !os(tvOS) + #if !os(visionOS) + HStack { + Slider(value: $sliderValue, in: 0...100) + #if os(iOS) + .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { slider in + slider.backgroundColor = .red + } + #elseif os(macOS) + .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { slider in + slider.layer?.backgroundColor = NSColor.red.cgColor + } + #endif + + Slider(value: $sliderValue, in: 0...100) + #if os(iOS) + .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { slider in + slider.backgroundColor = .green + } + #elseif os(macOS) + .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { slider in + slider.layer?.backgroundColor = NSColor.green.cgColor + } + #endif + } + + HStack { + Stepper(onIncrement: {}, onDecrement: {}) { + Text("Stepper Red") + } + #if os(iOS) + .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { stepper in + stepper.backgroundColor = .red + } + #elseif os(macOS) + .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { stepper in + stepper.layer?.backgroundColor = NSColor.red.cgColor + } + #endif + + Stepper(onIncrement: {}, onDecrement: {}) { + Text("Stepper Green") + } + #if os(iOS) + .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { stepper in + stepper.backgroundColor = .green + } + #elseif os(macOS) + .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { stepper in + stepper.layer?.backgroundColor = NSColor.green.cgColor + } + #endif + } + #endif + + HStack { + DatePicker(selection: $datePickerValue) { + Text("DatePicker Red") + } + #if os(iOS) || os(visionOS) + .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1)) { datePicker in + datePicker.backgroundColor = .red + } + #elseif os(macOS) + .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { datePicker in + datePicker.layer?.backgroundColor = NSColor.red.cgColor + } + #endif + } + #endif + + HStack { + Picker(selection: $segmentedControlValue, label: Text("Segmented control")) { + Text("Option 1").tag(0) + Text("Option 2").tag(1) + Text("Option 3").tag(2) + } + .pickerStyle(SegmentedPickerStyle()) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .picker(style: .segmented), + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1) + ) { datePicker in + datePicker.backgroundColor = .red + } + #elseif os(macOS) + .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { datePicker in + datePicker.layer?.backgroundColor = NSColor.red.cgColor + } + #endif + } + } + + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Examples/Showcase/Showcase/Helpers.swift b/Examples/Showcase/Showcase/Helpers.swift new file mode 100644 index 000000000..ae85f5c15 --- /dev/null +++ b/Examples/Showcase/Showcase/Helpers.swift @@ -0,0 +1,17 @@ +import SwiftUI + +extension View { + /// Modify a view with a `ViewBuilder` closure. + /// + /// This represents a streamlining of the + /// [`modifier`](https://developer.apple.com/documentation/swiftui/view/modifier(_:)) + + /// [`ViewModifier`](https://developer.apple.com/documentation/swiftui/viewmodifier) pattern. + /// + /// - Note: Useful only when you don't need to reuse the closure. + /// If you do, turn the closure into a proper modifier. + public func modifier( + @ViewBuilder _ modifier: (Self) -> ModifiedContent + ) -> ModifiedContent { + modifier(self) + } +} diff --git a/Examples/Showcase/Showcase/Showcase.entitlements b/Examples/Showcase/Showcase/Showcase.entitlements new file mode 100644 index 000000000..ee95ab7e5 --- /dev/null +++ b/Examples/Showcase/Showcase/Showcase.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/Gemfile b/Gemfile deleted file mode 100644 index a0469b6fd..000000000 --- a/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -source "https://rubygems.org" - -gem "cocoapods", "1.10.1" -gem "fastlane", "~> 2.137" - -plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 1ee04c956..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,258 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.3) - activesupport (5.2.4.5) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - aws-eventstream (1.1.0) - aws-partitions (1.384.0) - aws-sdk-core (3.109.1) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.39.0) - aws-sdk-core (~> 3, >= 3.109.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.83.1) - aws-sdk-core (~> 3, >= 3.109.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.2) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - claide (1.0.3) - cocoapods (1.10.1) - addressable (~> 2.6) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.10.1) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.6.6) - nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.19.0, < 2.0) - cocoapods-core (1.10.1) - activesupport (> 5.0, < 6) - addressable (~> 2.6) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.4.0) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.0) - cocoapods-trunk (1.5.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored (1.2) - colored2 (3.1.2) - colorize (0.8.1) - commander-fastlane (4.4.6) - highline (~> 1.7.2) - concurrent-ruby (1.1.8) - declarative (0.0.20) - declarative-option (0.1.0) - digest-crc (0.6.1) - rake (~> 13.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) - emoji_regex (3.0.0) - escape (0.0.4) - ethon (0.12.0) - ffi (>= 1.3.0) - excon (0.78.0) - faraday (1.1.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday_middleware (1.0.0) - faraday (~> 1.0) - fastimage (2.2.0) - fastlane (2.164.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander-fastlane (>= 4.4.6, < 5.0.0) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.37.0, < 0.39.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-test_center (3.15.3) - colorize - json - plist - trainer - xcodeproj - xctest_list (>= 1.2.1) - ffi (1.15.0) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - google-api-client (0.38.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.12) - google-cloud-core (1.5.0) - google-cloud-env (~> 1.0) - google-cloud-errors (~> 1.0) - google-cloud-env (1.4.0) - faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.0.1) - google-cloud-storage (1.29.1) - addressable (~> 2.5) - digest-crc (~> 0.4) - google-api-client (~> 0.33) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) - mini_mime (~> 1.0) - googleauth (0.14.0) - faraday (>= 0.17.3, < 2.0) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (~> 0.14) - highline (1.7.10) - http-cookie (1.0.3) - domain_name (~> 0.5) - httpclient (2.8.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - jmespath (1.4.0) - json (2.5.1) - jwt (2.2.2) - memoist (0.16.2) - mini_magick (4.10.1) - mini_mime (1.0.2) - minitest (5.14.4) - molinillo (0.6.6) - multi_json (1.15.0) - multipart-post (2.0.0) - nanaimo (0.3.0) - nap (1.1.0) - naturally (2.2.0) - netrc (0.11.0) - os (1.1.1) - plist (3.5.0) - public_suffix (4.0.6) - rake (13.0.1) - representable (3.0.4) - declarative (< 0.1.0) - declarative-option (< 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rouge (2.0.7) - ruby-macho (1.4.0) - ruby2_keywords (0.0.2) - rubyzip (2.3.0) - security (0.1.3) - signet (0.14.0) - addressable (~> 2.3) - faraday (>= 0.17.3, < 2.0) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.8) - CFPropertyList - naturally - slack-notifier (2.3.2) - terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - trainer (0.9.1) - fastlane (>= 2.25.0) - plist (>= 3.1.0, < 4.0.0) - tty-cursor (0.7.1) - tty-screen (0.8.1) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (1.2.9) - thread_safe (~> 0.1) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) - word_wrap (1.0.0) - xcodeproj (1.19.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) - xcpretty (~> 0.2, >= 0.0.7) - xctest_list (1.2.1) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods (= 1.10.1) - fastlane (~> 2.137) - fastlane-plugin-test_center - -BUNDLED WITH - 1.17.3 diff --git a/Introspect.podspec b/Introspect.podspec index 5182b2c87..ba45cbe28 100644 --- a/Introspect.podspec +++ b/Introspect.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Introspect' - spec.version = '0.1.3' + spec.version = ENV['LIB_VERSION'] spec.license = { type: 'MIT' } spec.homepage = 'https://github.com/siteline/SwiftUI-Introspect.git' spec.authors = { 'Lois Di Qual' => 'lois@siteline.com' } @@ -12,8 +12,8 @@ Pod::Spec.new do |spec| spec.source_files = 'Introspect/*.swift' - spec.swift_version = '5.1' - spec.ios.deployment_target = '11.0' - spec.tvos.deployment_target = '11.0' - spec.osx.deployment_target = '10.13' + spec.swift_version = '5.5' + spec.ios.deployment_target = '13.0' + spec.tvos.deployment_target = '13.0' + spec.osx.deployment_target = '10.15' end diff --git a/Introspect.xcodeproj/project.pbxproj b/Introspect.xcodeproj/project.pbxproj deleted file mode 100644 index 35a6f308a..000000000 --- a/Introspect.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1172 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - C068701C238DE85D00DAFD3D /* Introspect.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0687012238DE85D00DAFD3D /* Introspect.framework */; }; - C0687023238DE85D00DAFD3D /* Introspect.h in Headers */ = {isa = PBXBuildFile; fileRef = C0687015238DE85D00DAFD3D /* Introspect.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C068702D238DE8FD00DAFD3D /* Introspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = C068702C238DE8FD00DAFD3D /* Introspect.swift */; }; - C0796E2723F3CD18002BF033 /* Introspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = C068702C238DE8FD00DAFD3D /* Introspect.swift */; }; - C0796E2823F3CD1D002BF033 /* UIKitIntrospectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341423F3A13C005FA859 /* UIKitIntrospectionView.swift */; }; - C0796E2923F3CD1D002BF033 /* UIKitIntrospectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341623F3A149005FA859 /* UIKitIntrospectionViewController.swift */; }; - C0796E3423F3CDA4002BF033 /* Introspect.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0796E1F23F3CCD2002BF033 /* Introspect.framework */; }; - C0C6D68E238E006B00DA6285 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6D68D238E006B00DA6285 /* AppDelegate.swift */; }; - C0C6D690238E006B00DA6285 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6D68F238E006B00DA6285 /* SceneDelegate.swift */; }; - C0C6D692238E006B00DA6285 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6D691238E006B00DA6285 /* ContentView.swift */; }; - C0C6D6A0238E00D300DA6285 /* Introspect.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0687012238DE85D00DAFD3D /* Introspect.framework */; }; - C0ED341123F39EA2005FA859 /* Introspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = C068702C238DE8FD00DAFD3D /* Introspect.swift */; }; - C0ED341523F3A13C005FA859 /* UIKitIntrospectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341423F3A13C005FA859 /* UIKitIntrospectionView.swift */; }; - C0ED341723F3A149005FA859 /* UIKitIntrospectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341623F3A149005FA859 /* UIKitIntrospectionViewController.swift */; }; - C0ED341B23F3A258005FA859 /* AppKitIntrospectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341A23F3A258005FA859 /* AppKitIntrospectionView.swift */; }; - C0ED341D23F3A58B005FA859 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341C23F3A58B005FA859 /* ViewExtensions.swift */; }; - C0ED343A23F3AC43005FA859 /* Introspect.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0ED340923F39E93005FA859 /* Introspect.framework */; }; - C0ED344023F3AC7F005FA859 /* AppKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED342223F3AC14005FA859 /* AppKitTests.swift */; }; - C0ED345D23F48534005FA859 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341C23F3A58B005FA859 /* ViewExtensions.swift */; }; - C0ED345E23F48535005FA859 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341C23F3A58B005FA859 /* ViewExtensions.swift */; }; - C0ED345F23F48671005FA859 /* AppKitIntrospectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341A23F3A258005FA859 /* AppKitIntrospectionView.swift */; }; - C0ED346023F48672005FA859 /* AppKitIntrospectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341A23F3A258005FA859 /* AppKitIntrospectionView.swift */; }; - C0ED346123F486D0005FA859 /* UIKitIntrospectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341423F3A13C005FA859 /* UIKitIntrospectionView.swift */; }; - C0ED346223F48770005FA859 /* UIKitIntrospectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED341623F3A149005FA859 /* UIKitIntrospectionViewController.swift */; }; - C0ED346323F48776005FA859 /* Introspect.h in Headers */ = {isa = PBXBuildFile; fileRef = C0687015238DE85D00DAFD3D /* Introspect.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C0ED346423F48776005FA859 /* Introspect.h in Headers */ = {isa = PBXBuildFile; fileRef = C0687015238DE85D00DAFD3D /* Introspect.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C0ED346623F48992005FA859 /* UIKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0687030238DF3C900DAFD3D /* UIKitTests.swift */; }; - C0ED346723F489D5005FA859 /* AppKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED342223F3AC14005FA859 /* AppKitTests.swift */; }; - C0ED346823F489D5005FA859 /* UIKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0687030238DF3C900DAFD3D /* UIKitTests.swift */; }; - C0ED346923F489D6005FA859 /* AppKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED342223F3AC14005FA859 /* AppKitTests.swift */; }; - C0ED346A23F489D6005FA859 /* UIKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0687030238DF3C900DAFD3D /* UIKitTests.swift */; }; - E0B11E0609FFA04A7B6A1418 /* Pods_IntrospectExamples.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5785749C354BCF848BC4EAD9 /* Pods_IntrospectExamples.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - C068701D238DE85D00DAFD3D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C0687009238DE85D00DAFD3D /* Project object */; - proxyType = 1; - remoteGlobalIDString = C0687011238DE85D00DAFD3D; - remoteInfo = Introspect; - }; - C0796E3523F3CDA4002BF033 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C0687009238DE85D00DAFD3D /* Project object */; - proxyType = 1; - remoteGlobalIDString = C0796E1E23F3CCD2002BF033; - remoteInfo = "Introspect tvOS"; - }; - C0ED343B23F3AC43005FA859 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C0687009238DE85D00DAFD3D /* Project object */; - proxyType = 1; - remoteGlobalIDString = C0ED340823F39E93005FA859; - remoteInfo = "Introspect macOS"; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 5785749C354BCF848BC4EAD9 /* Pods_IntrospectExamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IntrospectExamples.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9BBE78DB32CCDC560004DB54 /* Pods-IntrospectExamples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IntrospectExamples.debug.xcconfig"; path = "Target Support Files/Pods-IntrospectExamples/Pods-IntrospectExamples.debug.xcconfig"; sourceTree = ""; }; - B826284199E111BBEA21E76B /* Pods-IntrospectExamples.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IntrospectExamples.release.xcconfig"; path = "Target Support Files/Pods-IntrospectExamples/Pods-IntrospectExamples.release.xcconfig"; sourceTree = ""; }; - C0687012238DE85D00DAFD3D /* Introspect.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Introspect.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C0687015238DE85D00DAFD3D /* Introspect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Introspect.h; sourceTree = ""; }; - C0687016238DE85D00DAFD3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C068701B238DE85D00DAFD3D /* Introspect iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Introspect iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - C0687022238DE85D00DAFD3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C068702C238DE8FD00DAFD3D /* Introspect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Introspect.swift; sourceTree = ""; }; - C0687030238DF3C900DAFD3D /* UIKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitTests.swift; sourceTree = ""; }; - C0796E1F23F3CCD2002BF033 /* Introspect.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Introspect.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C0796E2F23F3CDA4002BF033 /* Introspect tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Introspect tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - C0C6D68B238E006B00DA6285 /* IntrospectExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntrospectExamples.app; sourceTree = BUILT_PRODUCTS_DIR; }; - C0C6D68D238E006B00DA6285 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - C0C6D68F238E006B00DA6285 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - C0C6D691238E006B00DA6285 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - C0C6D69B238E007100DA6285 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C0ED340923F39E93005FA859 /* Introspect.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Introspect.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C0ED341423F3A13C005FA859 /* UIKitIntrospectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitIntrospectionView.swift; sourceTree = ""; }; - C0ED341623F3A149005FA859 /* UIKitIntrospectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitIntrospectionViewController.swift; sourceTree = ""; }; - C0ED341A23F3A258005FA859 /* AppKitIntrospectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppKitIntrospectionView.swift; sourceTree = ""; }; - C0ED341C23F3A58B005FA859 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = ""; }; - C0ED342223F3AC14005FA859 /* AppKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppKitTests.swift; sourceTree = ""; }; - C0ED343523F3AC43005FA859 /* Introspect macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Introspect macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - C068700F238DE85D00DAFD3D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0687018238DE85D00DAFD3D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C068701C238DE85D00DAFD3D /* Introspect.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0796E1C23F3CCD2002BF033 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0796E2C23F3CDA4002BF033 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C0796E3423F3CDA4002BF033 /* Introspect.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0C6D688238E006B00DA6285 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C0C6D6A0238E00D300DA6285 /* Introspect.framework in Frameworks */, - E0B11E0609FFA04A7B6A1418 /* Pods_IntrospectExamples.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0ED340623F39E93005FA859 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0ED343223F3AC43005FA859 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED343A23F3AC43005FA859 /* Introspect.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 0534532308DF3E515053BF9F /* Pods */ = { - isa = PBXGroup; - children = ( - 9BBE78DB32CCDC560004DB54 /* Pods-IntrospectExamples.debug.xcconfig */, - B826284199E111BBEA21E76B /* Pods-IntrospectExamples.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - C0687008238DE85D00DAFD3D = { - isa = PBXGroup; - children = ( - C0687014238DE85D00DAFD3D /* Introspect */, - C068701F238DE85D00DAFD3D /* IntrospectTests */, - C0C6D68C238E006B00DA6285 /* IntrospectExamples */, - C0687013238DE85D00DAFD3D /* Products */, - C0C6D69F238E00D300DA6285 /* Frameworks */, - 0534532308DF3E515053BF9F /* Pods */, - ); - sourceTree = ""; - }; - C0687013238DE85D00DAFD3D /* Products */ = { - isa = PBXGroup; - children = ( - C0687012238DE85D00DAFD3D /* Introspect.framework */, - C068701B238DE85D00DAFD3D /* Introspect iOS Tests.xctest */, - C0C6D68B238E006B00DA6285 /* IntrospectExamples.app */, - C0ED340923F39E93005FA859 /* Introspect.framework */, - C0ED343523F3AC43005FA859 /* Introspect macOS Tests.xctest */, - C0796E1F23F3CCD2002BF033 /* Introspect.framework */, - C0796E2F23F3CDA4002BF033 /* Introspect tvOS Tests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - C0687014238DE85D00DAFD3D /* Introspect */ = { - isa = PBXGroup; - children = ( - C0ED341A23F3A258005FA859 /* AppKitIntrospectionView.swift */, - C068702C238DE8FD00DAFD3D /* Introspect.swift */, - C0ED341623F3A149005FA859 /* UIKitIntrospectionViewController.swift */, - C0ED341423F3A13C005FA859 /* UIKitIntrospectionView.swift */, - C0ED341C23F3A58B005FA859 /* ViewExtensions.swift */, - C0ED346523F4880B005FA859 /* Supporting Files */, - ); - path = Introspect; - sourceTree = ""; - }; - C068701F238DE85D00DAFD3D /* IntrospectTests */ = { - isa = PBXGroup; - children = ( - C0ED342223F3AC14005FA859 /* AppKitTests.swift */, - C0687030238DF3C900DAFD3D /* UIKitTests.swift */, - C0687022238DE85D00DAFD3D /* Info.plist */, - ); - path = IntrospectTests; - sourceTree = ""; - }; - C0C6D68C238E006B00DA6285 /* IntrospectExamples */ = { - isa = PBXGroup; - children = ( - C0C6D68D238E006B00DA6285 /* AppDelegate.swift */, - C0C6D68F238E006B00DA6285 /* SceneDelegate.swift */, - C0C6D691238E006B00DA6285 /* ContentView.swift */, - C0C6D69B238E007100DA6285 /* Info.plist */, - ); - path = IntrospectExamples; - sourceTree = ""; - }; - C0C6D69F238E00D300DA6285 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 5785749C354BCF848BC4EAD9 /* Pods_IntrospectExamples.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - C0ED346523F4880B005FA859 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - C0687016238DE85D00DAFD3D /* Info.plist */, - C0687015238DE85D00DAFD3D /* Introspect.h */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - C068700D238DE85D00DAFD3D /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - C0687023238DE85D00DAFD3D /* Introspect.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0796E1A23F3CCD2002BF033 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED346423F48776005FA859 /* Introspect.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0ED340423F39E93005FA859 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED346323F48776005FA859 /* Introspect.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - C0687011238DE85D00DAFD3D /* Introspect iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0687026238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS" */; - buildPhases = ( - C068700D238DE85D00DAFD3D /* Headers */, - C068700E238DE85D00DAFD3D /* Sources */, - C068700F238DE85D00DAFD3D /* Frameworks */, - C0687010238DE85D00DAFD3D /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "Introspect iOS"; - productName = Introspect; - productReference = C0687012238DE85D00DAFD3D /* Introspect.framework */; - productType = "com.apple.product-type.framework"; - }; - C068701A238DE85D00DAFD3D /* Introspect iOS Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0687029238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS Tests" */; - buildPhases = ( - C0687017238DE85D00DAFD3D /* Sources */, - C0687018238DE85D00DAFD3D /* Frameworks */, - C0687019238DE85D00DAFD3D /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - C068701E238DE85D00DAFD3D /* PBXTargetDependency */, - ); - name = "Introspect iOS Tests"; - productName = IntrospectTests; - productReference = C068701B238DE85D00DAFD3D /* Introspect iOS Tests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - C0796E1E23F3CCD2002BF033 /* Introspect tvOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0796E2423F3CCD2002BF033 /* Build configuration list for PBXNativeTarget "Introspect tvOS" */; - buildPhases = ( - C0796E1A23F3CCD2002BF033 /* Headers */, - C0796E1B23F3CCD2002BF033 /* Sources */, - C0796E1C23F3CCD2002BF033 /* Frameworks */, - C0796E1D23F3CCD2002BF033 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "Introspect tvOS"; - productName = "Introspect tvOS"; - productReference = C0796E1F23F3CCD2002BF033 /* Introspect.framework */; - productType = "com.apple.product-type.framework"; - }; - C0796E2E23F3CDA4002BF033 /* Introspect tvOS Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0796E3723F3CDA4002BF033 /* Build configuration list for PBXNativeTarget "Introspect tvOS Tests" */; - buildPhases = ( - C0796E2B23F3CDA4002BF033 /* Sources */, - C0796E2C23F3CDA4002BF033 /* Frameworks */, - C0796E2D23F3CDA4002BF033 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - C0796E3623F3CDA4002BF033 /* PBXTargetDependency */, - ); - name = "Introspect tvOS Tests"; - productName = "Introspect tvOS Tests"; - productReference = C0796E2F23F3CDA4002BF033 /* Introspect tvOS Tests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - C0C6D68A238E006B00DA6285 /* IntrospectExamples */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0C6D69E238E007100DA6285 /* Build configuration list for PBXNativeTarget "IntrospectExamples" */; - buildPhases = ( - 9C6427CDD5EF4F1EAC75C7F9 /* [CP] Check Pods Manifest.lock */, - C0C6D687238E006B00DA6285 /* Sources */, - C0C6D688238E006B00DA6285 /* Frameworks */, - C0C6D689238E006B00DA6285 /* Resources */, - EC4E9FC6E0E62CFD8A53623A /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = IntrospectExamples; - productName = IntrospectExamples; - productReference = C0C6D68B238E006B00DA6285 /* IntrospectExamples.app */; - productType = "com.apple.product-type.application"; - }; - C0ED340823F39E93005FA859 /* Introspect macOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0ED340E23F39E93005FA859 /* Build configuration list for PBXNativeTarget "Introspect macOS" */; - buildPhases = ( - C0ED340423F39E93005FA859 /* Headers */, - C0ED340523F39E93005FA859 /* Sources */, - C0ED340623F39E93005FA859 /* Frameworks */, - C0ED340723F39E93005FA859 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "Introspect macOS"; - productName = "Introspect macOS"; - productReference = C0ED340923F39E93005FA859 /* Introspect.framework */; - productType = "com.apple.product-type.framework"; - }; - C0ED343423F3AC43005FA859 /* Introspect macOS Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0ED343D23F3AC43005FA859 /* Build configuration list for PBXNativeTarget "Introspect macOS Tests" */; - buildPhases = ( - C0ED343123F3AC43005FA859 /* Sources */, - C0ED343223F3AC43005FA859 /* Frameworks */, - C0ED343323F3AC43005FA859 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - C0ED343C23F3AC43005FA859 /* PBXTargetDependency */, - ); - name = "Introspect macOS Tests"; - productName = "Introspect macOS Tests"; - productReference = C0ED343523F3AC43005FA859 /* Introspect macOS Tests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - C0687009238DE85D00DAFD3D /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1130; - LastUpgradeCheck = 1120; - ORGANIZATIONNAME = "Lois Di Qual"; - TargetAttributes = { - C0687011238DE85D00DAFD3D = { - CreatedOnToolsVersion = 11.2.1; - LastSwiftMigration = 1120; - }; - C068701A238DE85D00DAFD3D = { - CreatedOnToolsVersion = 11.2.1; - }; - C0796E1E23F3CCD2002BF033 = { - CreatedOnToolsVersion = 11.3.1; - }; - C0796E2E23F3CDA4002BF033 = { - CreatedOnToolsVersion = 11.3.1; - }; - C0C6D68A238E006B00DA6285 = { - CreatedOnToolsVersion = 11.2.1; - }; - C0ED340823F39E93005FA859 = { - CreatedOnToolsVersion = 11.3.1; - }; - C0ED343423F3AC43005FA859 = { - CreatedOnToolsVersion = 11.3.1; - }; - }; - }; - buildConfigurationList = C068700C238DE85D00DAFD3D /* Build configuration list for PBXProject "Introspect" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = C0687008238DE85D00DAFD3D; - productRefGroup = C0687013238DE85D00DAFD3D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - C0687011238DE85D00DAFD3D /* Introspect iOS */, - C068701A238DE85D00DAFD3D /* Introspect iOS Tests */, - C0ED340823F39E93005FA859 /* Introspect macOS */, - C0ED343423F3AC43005FA859 /* Introspect macOS Tests */, - C0796E1E23F3CCD2002BF033 /* Introspect tvOS */, - C0796E2E23F3CDA4002BF033 /* Introspect tvOS Tests */, - C0C6D68A238E006B00DA6285 /* IntrospectExamples */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - C0687010238DE85D00DAFD3D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0687019238DE85D00DAFD3D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0796E1D23F3CCD2002BF033 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0796E2D23F3CDA4002BF033 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0C6D689238E006B00DA6285 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0ED340723F39E93005FA859 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0ED343323F3AC43005FA859 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 9C6427CDD5EF4F1EAC75C7F9 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-IntrospectExamples-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - EC4E9FC6E0E62CFD8A53623A /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-IntrospectExamples/Pods-IntrospectExamples-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-IntrospectExamples/Pods-IntrospectExamples-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-IntrospectExamples/Pods-IntrospectExamples-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - C068700E238DE85D00DAFD3D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED341723F3A149005FA859 /* UIKitIntrospectionViewController.swift in Sources */, - C0ED345F23F48671005FA859 /* AppKitIntrospectionView.swift in Sources */, - C0ED341523F3A13C005FA859 /* UIKitIntrospectionView.swift in Sources */, - C0ED345E23F48535005FA859 /* ViewExtensions.swift in Sources */, - C068702D238DE8FD00DAFD3D /* Introspect.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0687017238DE85D00DAFD3D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED346823F489D5005FA859 /* UIKitTests.swift in Sources */, - C0ED346723F489D5005FA859 /* AppKitTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0796E1B23F3CCD2002BF033 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0796E2823F3CD1D002BF033 /* UIKitIntrospectionView.swift in Sources */, - C0ED346023F48672005FA859 /* AppKitIntrospectionView.swift in Sources */, - C0796E2923F3CD1D002BF033 /* UIKitIntrospectionViewController.swift in Sources */, - C0ED345D23F48534005FA859 /* ViewExtensions.swift in Sources */, - C0796E2723F3CD18002BF033 /* Introspect.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0796E2B23F3CDA4002BF033 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED346A23F489D6005FA859 /* UIKitTests.swift in Sources */, - C0ED346923F489D6005FA859 /* AppKitTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0C6D687238E006B00DA6285 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0C6D68E238E006B00DA6285 /* AppDelegate.swift in Sources */, - C0C6D690238E006B00DA6285 /* SceneDelegate.swift in Sources */, - C0C6D692238E006B00DA6285 /* ContentView.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0ED340523F39E93005FA859 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED341B23F3A258005FA859 /* AppKitIntrospectionView.swift in Sources */, - C0ED341D23F3A58B005FA859 /* ViewExtensions.swift in Sources */, - C0ED346123F486D0005FA859 /* UIKitIntrospectionView.swift in Sources */, - C0ED341123F39EA2005FA859 /* Introspect.swift in Sources */, - C0ED346223F48770005FA859 /* UIKitIntrospectionViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C0ED343123F3AC43005FA859 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0ED346623F48992005FA859 /* UIKitTests.swift in Sources */, - C0ED344023F3AC7F005FA859 /* AppKitTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - C068701E238DE85D00DAFD3D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C0687011238DE85D00DAFD3D /* Introspect iOS */; - targetProxy = C068701D238DE85D00DAFD3D /* PBXContainerItemProxy */; - }; - C0796E3623F3CDA4002BF033 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C0796E1E23F3CCD2002BF033 /* Introspect tvOS */; - targetProxy = C0796E3523F3CDA4002BF033 /* PBXContainerItemProxy */; - }; - C0ED343C23F3AC43005FA859 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C0ED340823F39E93005FA859 /* Introspect macOS */; - targetProxy = C0ED343B23F3AC43005FA859 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - C0687024238DE85D00DAFD3D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.2; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TVOS_DEPLOYMENT_TARGET = 13.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - C0687025238DE85D00DAFD3D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.2; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TVOS_DEPLOYMENT_TARGET = 13.0; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - C0687027238DE85D00DAFD3D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Manual; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Introspect/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 0.1.3; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; - PRODUCT_NAME = Introspect; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - C0687028238DE85D00DAFD3D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Manual; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Introspect/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 0.1.3; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; - PRODUCT_NAME = Introspect; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - C068702A238DE85D00DAFD3D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = IntrospectTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.loisdiqual.IntrospectTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - C068702B238DE85D00DAFD3D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = IntrospectTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.loisdiqual.IntrospectTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - C0796E2523F3CCD2002BF033 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Introspect/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 0.1.3; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; - PRODUCT_NAME = Introspect; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.2; - }; - name = Debug; - }; - C0796E2623F3CCD2002BF033 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Introspect/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 0.1.3; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; - PRODUCT_NAME = Introspect; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.2; - }; - name = Release; - }; - C0796E3823F3CDA4002BF033 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = IntrospectTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.siteline.Introspect-tvOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = appletvos; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.2; - }; - name = Debug; - }; - C0796E3923F3CDA4002BF033 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = IntrospectTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.siteline.Introspect-tvOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = appletvos; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.2; - }; - name = Release; - }; - C0C6D69C238E007100DA6285 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9BBE78DB32CCDC560004DB54 /* Pods-IntrospectExamples.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - ENABLE_PREVIEWS = YES; - INFOPLIST_FILE = IntrospectExamples/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 0.0.6; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.IntrospectExamples; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - C0C6D69D238E007100DA6285 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B826284199E111BBEA21E76B /* Pods-IntrospectExamples.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - ENABLE_PREVIEWS = YES; - INFOPLIST_FILE = IntrospectExamples/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 0.0.6; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.IntrospectExamples; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - C0ED340F23F39E93005FA859 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Introspect/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 0.1.3; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; - PRODUCT_NAME = Introspect; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - C0ED341023F39E93005FA859 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Introspect/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 0.1.3; - PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; - PRODUCT_NAME = Introspect; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - C0ED343E23F3AC43005FA859 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = IntrospectTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - PRODUCT_BUNDLE_IDENTIFIER = "com.siteline.Introspect-macOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - C0ED343F23F3AC43005FA859 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = IntrospectTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - PRODUCT_BUNDLE_IDENTIFIER = "com.siteline.Introspect-macOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - C068700C238DE85D00DAFD3D /* Build configuration list for PBXProject "Introspect" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0687024238DE85D00DAFD3D /* Debug */, - C0687025238DE85D00DAFD3D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C0687026238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0687027238DE85D00DAFD3D /* Debug */, - C0687028238DE85D00DAFD3D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C0687029238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C068702A238DE85D00DAFD3D /* Debug */, - C068702B238DE85D00DAFD3D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C0796E2423F3CCD2002BF033 /* Build configuration list for PBXNativeTarget "Introspect tvOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0796E2523F3CCD2002BF033 /* Debug */, - C0796E2623F3CCD2002BF033 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C0796E3723F3CDA4002BF033 /* Build configuration list for PBXNativeTarget "Introspect tvOS Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0796E3823F3CDA4002BF033 /* Debug */, - C0796E3923F3CDA4002BF033 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C0C6D69E238E007100DA6285 /* Build configuration list for PBXNativeTarget "IntrospectExamples" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0C6D69C238E007100DA6285 /* Debug */, - C0C6D69D238E007100DA6285 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C0ED340E23F39E93005FA859 /* Build configuration list for PBXNativeTarget "Introspect macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0ED340F23F39E93005FA859 /* Debug */, - C0ED341023F39E93005FA859 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C0ED343D23F3AC43005FA859 /* Build configuration list for PBXNativeTarget "Introspect macOS Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0ED343E23F3AC43005FA859 /* Debug */, - C0ED343F23F3AC43005FA859 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = C0687009238DE85D00DAFD3D /* Project object */; -} diff --git a/Introspect.xcodeproj/xcuserdata/ldiqual.xcuserdatad/xcschemes/xcschememanagement.plist b/Introspect.xcodeproj/xcuserdata/ldiqual.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 1651105eb..000000000 --- a/Introspect.xcodeproj/xcuserdata/ldiqual.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,62 +0,0 @@ - - - - - SchemeUserState - - Introspect iOS.xcscheme_^#shared#^_ - - orderHint - 0 - - Introspect macOS.xcscheme_^#shared#^_ - - orderHint - 1 - - Introspect tvOS.xcscheme_^#shared#^_ - - orderHint - 2 - - IntrospectExamples.xcscheme_^#shared#^_ - - orderHint - 3 - - - SuppressBuildableAutocreation - - C0687011238DE85D00DAFD3D - - primary - - - C068701A238DE85D00DAFD3D - - primary - - - C0796E1E23F3CCD2002BF033 - - primary - - - C0796E2E23F3CDA4002BF033 - - primary - - - C0ED340823F39E93005FA859 - - primary - - - C0ED343423F3AC43005FA859 - - primary - - - - - diff --git a/Introspect.xcworkspace/contents.xcworkspacedata b/Introspect.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 14ab32111..000000000 --- a/Introspect.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/Introspect/AppKitIntrospectionView.swift b/Introspect/AppKitIntrospectionView.swift index d2ace3d23..ae375ff07 100644 --- a/Introspect/AppKitIntrospectionView.swift +++ b/Introspect/AppKitIntrospectionView.swift @@ -3,7 +3,6 @@ import SwiftUI import AppKit /// Introspection NSView that is inserted alongside the target view. -@available(macOS 10.15.0, *) public class IntrospectionNSView: NSView { required init() { @@ -23,7 +22,6 @@ public class IntrospectionNSView: NSView { /// Introspection View that is injected into the UIKit hierarchy alongside the target view. /// After `updateNSView` is called, it calls `selector` to find the target view, then `customize` when the target view is found. -@available(macOS 10.15.0, *) public struct AppKitIntrospectionView: NSViewRepresentable { /// Method that introspects the view hierarchy to find the target view. diff --git a/Introspect/Info.plist b/Introspect/Info.plist deleted file mode 100644 index c0701c6d7..000000000 --- a/Introspect/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/Introspect/Introspect.h b/Introspect/Introspect.h deleted file mode 100644 index 7c52b636a..000000000 --- a/Introspect/Introspect.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -//! Project version number for Introspect. -FOUNDATION_EXPORT double IntrospectVersionNumber; - -//! Project version string for Introspect. -FOUNDATION_EXPORT const unsigned char IntrospectVersionString[]; diff --git a/Introspect/Introspect.swift b/Introspect/Introspect.swift index cbc756263..09e8b36ed 100644 --- a/Introspect/Introspect.swift +++ b/Introspect/Introspect.swift @@ -318,6 +318,16 @@ public enum TargetViewSelector { } return Introspect.findAncestor(ofType: TargetView.self, from: entry) } + + public static func siblingOrAncestorOrSiblingContainingOrAncestorChild(from entry: PlatformView) -> TargetView? { + if let sibling: TargetView = siblingOfType(from: entry) { + return sibling + } + if let ancestor: TargetView = Introspect.findAncestor(ofType: TargetView.self, from: entry) { + return ancestor + } + return siblingContainingOrAncestorOrAncestorChild(from: entry) + } public static func ancestorOrSiblingContaining(from entry: PlatformView) -> TargetView? { if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) { diff --git a/Introspect/UIKitIntrospectionView.swift b/Introspect/UIKitIntrospectionView.swift index 5307afed3..9d9765fe9 100644 --- a/Introspect/UIKitIntrospectionView.swift +++ b/Introspect/UIKitIntrospectionView.swift @@ -3,9 +3,10 @@ import UIKit import SwiftUI /// Introspection UIView that is inserted alongside the target view. -@available(iOS 13.0, *) public class IntrospectionUIView: UIView { + var moveToWindowHandler: (() -> Void)? + required init() { super.init(frame: .zero) isHidden = true @@ -16,11 +17,15 @@ public class IntrospectionUIView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override public func didMoveToWindow() { + super.didMoveToWindow() + moveToWindowHandler?() + } } /// Introspection View that is injected into the UIKit hierarchy alongside the target view. /// After `updateUIView` is called, it calls `selector` to find the target view, then `customize` when the target view is found. -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) public struct UIKitIntrospectionView: UIViewRepresentable { /// Method that introspects the view hierarchy to find the target view. @@ -38,27 +43,42 @@ public struct UIKitIntrospectionView: UIViewRepresentabl self.customize = customize } + /// When `makeUIView` and `updateUIView` are called, the Introspection view is not yet in the UIKit hierarchy. + /// At this point, `introspectionView.superview.superview` is nil and we can't access the target UIKit view. + /// To workaround this, we wait until the runloop is done inserting the introspection view in the hierarchy, then run the selector. + /// Finding the target view fails silently if the selector yields no result. This happens when the introspection view gets + /// removed from the hierarchy. public func makeUIView(context: UIViewRepresentableContext) -> IntrospectionUIView { let view = IntrospectionUIView() view.accessibilityLabel = "IntrospectionUIView<\(TargetViewType.self)>" + view.moveToWindowHandler = { [weak view] in + guard let view = view else { return } + DispatchQueue.main.async { + guard let targetView = self.selector(view) else { + return + } + self.customize(targetView) + } + } return view } - - /// When `updateUiView` is called after creating the Introspection view, it is not yet in the UIKit hierarchy. - /// At this point, `introspectionView.superview.superview` is nil and we can't access the target UIKit view. - /// To workaround this, we wait until the runloop is done inserting the introspection view in the hierarchy, then run the selector. - /// Finding the target view fails silently if the selector yield no result. This happens when `updateUIView` - /// gets called when the introspection view gets removed from the hierarchy. + + /// SwiftUI state changes after `makeUIView` will trigger this function, not + /// `makeUIView`, so we need to call the handler again to allow re-customization + /// based on the newest state. public func updateUIView( - _ uiView: IntrospectionUIView, + _ view: IntrospectionUIView, context: UIViewRepresentableContext ) { - DispatchQueue.main.async { - guard let targetView = self.selector(uiView) else { - return - } - self.customize(targetView) + guard let targetView = self.selector(view) else { + return } + self.customize(targetView) + } + + /// Avoid memory leaks. + public static func dismantleUIView(_ view: IntrospectionUIView, coordinator: ()) { + view.moveToWindowHandler = nil } } #endif diff --git a/Introspect/UIKitIntrospectionViewController.swift b/Introspect/UIKitIntrospectionViewController.swift index e7343a334..a0e20d7bc 100644 --- a/Introspect/UIKitIntrospectionViewController.swift +++ b/Introspect/UIKitIntrospectionViewController.swift @@ -3,7 +3,6 @@ import SwiftUI import UIKit /// Introspection UIViewController that is inserted alongside the target view controller. -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) public class IntrospectionUIViewController: UIViewController { required init() { super.init(nibName: nil, bundle: nil) @@ -17,7 +16,6 @@ public class IntrospectionUIViewController: UIViewController { } /// This is the same logic as IntrospectionView but for view controllers. Please see details above. -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) public struct UIKitIntrospectionViewController: UIViewControllerRepresentable { let selector: (IntrospectionUIViewController) -> TargetViewControllerType? @@ -31,25 +29,45 @@ public struct UIKitIntrospectionViewController ) -> IntrospectionUIViewController { let viewController = IntrospectionUIViewController() viewController.accessibilityLabel = "IntrospectionUIViewController<\(TargetViewControllerType.self)>" viewController.view.accessibilityLabel = "IntrospectionUIView<\(TargetViewControllerType.self)>" + (viewController.view as? IntrospectionUIView)?.moveToWindowHandler = { [weak viewController] in + guard let viewController = viewController else { return } + DispatchQueue.main.async { + guard let targetView = self.selector(viewController) else { + return + } + self.customize(targetView) + } + } return viewController } + /// SwiftUI state changes after `makeUIViewController` will trigger this function, not + /// `makeUIViewController`, so we need to call the handler again to allow re-customization + /// based on the newest state. public func updateUIViewController( - _ uiViewController: IntrospectionUIViewController, + _ viewController: IntrospectionUIViewController, context: UIViewControllerRepresentableContext ) { - DispatchQueue.main.async { - guard let targetView = self.selector(uiViewController) else { - return - } - self.customize(targetView) + guard let targetView = self.selector(viewController) else { + return } + self.customize(targetView) + } + + /// Avoid memory leaks. + public static func dismantleUIViewController(_ viewController: IntrospectionUIViewController, coordinator: ()) { + (viewController.view as? IntrospectionUIView)?.moveToWindowHandler = nil } } #endif diff --git a/Introspect/ViewExtensions.swift b/Introspect/ViewExtensions.swift index 9ae53d6c0..6ff3627a2 100644 --- a/Introspect/ViewExtensions.swift +++ b/Introspect/ViewExtensions.swift @@ -6,7 +6,6 @@ import AppKit import UIKit #endif -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) extension View { public func inject(_ view: SomeView) -> some View where SomeView: View { overlay(view.frame(width: 0, height: 0)) @@ -14,10 +13,10 @@ extension View { } #if canImport(UIKit) -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) extension View { - + /// Finds a `TargetView` from a `SwiftUI.View` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspect( selector: @escaping (IntrospectionUIView) -> TargetView?, customize: @escaping (TargetView) -> () @@ -27,8 +26,9 @@ extension View { customize: customize )) } - + /// Finds a `UINavigationController` from any view embedded in a `SwiftUI.NavigationView`. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectNavigationController(customize: @escaping (UINavigationController) -> ()) -> some View { inject(UIKitIntrospectionViewController( selector: { introspectionViewController in @@ -46,23 +46,25 @@ extension View { } /// Finds a `UISplitViewController` from a `SwiftUI.NavigationView` with style `DoubleColumnNavigationViewStyle`. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectSplitViewController(customize: @escaping (UISplitViewController) -> ()) -> some View { - inject(UIKitIntrospectionViewController( - selector: { introspectionViewController in - - // Search in ancestors - if let splitViewController = introspectionViewController.splitViewController { - return splitViewController - } - - // Search in siblings - return Introspect.previousSibling(containing: UISplitViewController.self, from: introspectionViewController) - }, - customize: customize - )) - } + inject(UIKitIntrospectionViewController( + selector: { introspectionViewController in + + // Search in ancestors + if let splitViewController = introspectionViewController.splitViewController { + return splitViewController + } + + // Search in siblings + return Introspect.previousSibling(containing: UISplitViewController.self, from: introspectionViewController) + }, + customize: customize + )) + } /// Finds the containing `UIViewController` of a SwiftUI view. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectViewController(customize: @escaping (UIViewController) -> ()) -> some View { inject(UIKitIntrospectionViewController( selector: { $0.parent }, @@ -71,6 +73,7 @@ extension View { } /// Finds a `UITabBarController` from any SwiftUI view embedded in a `SwiftUI.TabView` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTabBarController(customize: @escaping (UITabBarController) -> ()) -> some View { inject(UIKitIntrospectionViewController( selector: { introspectionViewController in @@ -86,79 +89,139 @@ extension View { customize: customize )) } + + /// Finds a `UISearchController` from a `SwiftUI.View` with a `.searchable` modifier + @available(iOS 15, *) + @available(tvOS, unavailable) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") + public func introspectSearchController(customize: @escaping (UISearchController) -> ()) -> some View { + introspectNavigationController { navigationController in + let navigationBar = navigationController.navigationBar + if let searchController = navigationBar.topItem?.searchController { + customize(searchController) + } + } + } /// Finds a `UITableView` from a `SwiftUI.List`, or `SwiftUI.List` child. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTableView(customize: @escaping (UITableView) -> ()) -> some View { introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) } /// Finds a `UITableViewCell` from a `SwiftUI.List`, or `SwiftUI.List` child. You can attach this directly to the element inside the list. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTableViewCell(customize: @escaping (UITableViewCell) -> ()) -> some View { introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) } + /// Finds a `UICollectionView` from a `SwiftUI.List`, or `SwiftUI.List` child. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") + public func introspectCollectionView(customize: @escaping (UICollectionView) -> ()) -> some View { + introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) + } + + /// Finds a `UICollectionView` from a `SwiftUI.List`, or `SwiftUI.List` child. You can attach this directly to the element inside the list. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") + public func introspectCollectionViewCell(customize: @escaping (UICollectionViewCell) -> ()) -> some View { + introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) + } + /// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View { - if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) { - return introspect(selector: TargetViewSelector.siblingOfTypeOrAncestor, customize: customize) + if #available(iOS 14, tvOS 14, *) { + return introspect(selector: TargetViewSelector.siblingOrAncestorOrSiblingContainingOrAncestorChild, customize: customize) } else { return introspect(selector: TargetViewSelector.siblingContainingOrAncestor, customize: customize) } } - + + /// Finds the horizontal `UIScrollView` from a `SwiftUI.TabBarView` with tab style `SwiftUI.PageTabViewStyle`. + /// + /// Customize is called with a `UICollectionView` wrapper, and the horizontal `UIScrollView`. + @available(iOS 14, tvOS 14, *) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") + public func introspectPagedTabView(customize: @escaping (UICollectionView, UIScrollView) -> ()) -> some View { + if #available(iOS 16, *) { + return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: { (collectionView: UICollectionView) in + customize(collectionView, collectionView) + }) + } else { + return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: { (collectionView: UICollectionView) in + for subview in collectionView.subviews { + if NSStringFromClass(type(of: subview)).contains("EmbeddedScrollView"), let scrollView = subview as? UIScrollView { + customize(collectionView, scrollView) + break + } + } + }) + } + } + /// Finds a `UITextField` from a `SwiftUI.TextField` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTextField(customize: @escaping (UITextField) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContainingOrAncestorOrAncestorChild, customize: customize) } /// Finds a `UITextView` from a `SwiftUI.TextEditor` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTextView(customize: @escaping (UITextView) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `UISwitch` from a `SwiftUI.Toggle` @available(tvOS, unavailable) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectSwitch(customize: @escaping (UISwitch) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `UISlider` from a `SwiftUI.Slider` @available(tvOS, unavailable) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectSlider(customize: @escaping (UISlider) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `UIStepper` from a `SwiftUI.Stepper` @available(tvOS, unavailable) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectStepper(customize: @escaping (UIStepper) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `UIDatePicker` from a `SwiftUI.DatePicker` @available(tvOS, unavailable) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectDatePicker(customize: @escaping (UIDatePicker) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `UISegmentedControl` from a `SwiftUI.Picker` with style `SegmentedPickerStyle` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectSegmentedControl(customize: @escaping (UISegmentedControl) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `UIColorWell` from a `SwiftUI.ColorPicker` - @available(iOS 14.0, *) + #if os(iOS) + @available(iOS 14, *) @available(tvOS, unavailable) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectColorWell(customize: @escaping (UIColorWell) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } + #endif } #endif #if canImport(AppKit) && !targetEnvironment(macCatalyst) -@available(macOS 10.15.0, *) extension View { /// Finds a `TargetView` from a `SwiftUI.View` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspect( selector: @escaping (IntrospectionNSView) -> TargetView?, customize: @escaping (TargetView) -> () @@ -168,70 +231,101 @@ extension View { customize: customize )) } + + /// Finds a `NSSplitViewController` from a `SwiftUI.NavigationView` with style `DoubleColumnNavigationViewStyle`. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") + public func introspectSplitView(customize: @escaping (NSSplitView) -> ()) -> some View { + return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) + } /// Finds a `NSTableView` from a `SwiftUI.List`, or `SwiftUI.List` child. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTableView(customize: @escaping (NSTableView) -> ()) -> some View { introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) } /// Finds a `NSTableCellView` from a `SwiftUI.List`, or `SwiftUI.List` child. You can attach this directly to the element inside the list. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTableViewCell(customize: @escaping (NSTableCellView) -> ()) -> some View { introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) } /// Finds a `NSScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child. + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectScrollView(customize: @escaping (NSScrollView) -> ()) -> some View { - if #available(macOS 11.0, *) { - return introspect(selector: TargetViewSelector.siblingOfTypeOrAncestor, customize: customize) + if #available(macOS 11, *) { + return introspect(selector: TargetViewSelector.siblingOrAncestorOrSiblingContainingOrAncestorChild, customize: customize) } else { return introspect(selector: TargetViewSelector.siblingContainingOrAncestor, customize: customize) } } /// Finds a `NSTextField` from a `SwiftUI.TextField` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTextField(customize: @escaping (NSTextField) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSTextView` from a `SwiftUI.TextView` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTextView(customize: @escaping (NSTextView) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSSlider` from a `SwiftUI.Slider` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectSlider(customize: @escaping (NSSlider) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSStepper` from a `SwiftUI.Stepper` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectStepper(customize: @escaping (NSStepper) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSDatePicker` from a `SwiftUI.DatePicker` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectDatePicker(customize: @escaping (NSDatePicker) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSSegmentedControl` from a `SwiftUI.Picker` with style `SegmentedPickerStyle` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectSegmentedControl(customize: @escaping (NSSegmentedControl) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSTabView` from a `SwiftUI.TabView` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectTabView(customize: @escaping (NSTabView) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSButton` from a `SwiftUI.Button` + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectButton(customize: @escaping (NSButton) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } /// Finds a `NSColorWell` from a `SwiftUI.ColorPicker` - @available(macOS 11.0, *) + @available(macOS 11, *) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") public func introspectColorWell(customize: @escaping (NSColorWell) -> ()) -> some View { introspect(selector: TargetViewSelector.siblingContaining, customize: customize) } } #endif + +#if canImport(MapKit) +import MapKit + +extension View { + /// Finds an `MKMapView` from a `SwiftUI.Map` + @available(iOS 14, tvOS 14, macOS 11, *) + @available(*, deprecated, message: "The Introspect module is deprecated and will be obsoleted later this year. Please switch over to the new and improved SwiftUIIntrospect module. More info: https://github.com/siteline/swiftui-introspect#readme") + public func introspectMapView(customize: @escaping (MKMapView) -> ()) -> some View { + introspect(selector: TargetViewSelector.siblingContaining, customize: customize) + } +} +#endif diff --git a/IntrospectExamples/AppDelegate.swift b/IntrospectExamples/AppDelegate.swift deleted file mode 100644 index 9bafec633..000000000 --- a/IntrospectExamples/AppDelegate.swift +++ /dev/null @@ -1,9 +0,0 @@ -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - return true - } -} - diff --git a/IntrospectExamples/ContentView.swift b/IntrospectExamples/ContentView.swift deleted file mode 100644 index bf5f7fba2..000000000 --- a/IntrospectExamples/ContentView.swift +++ /dev/null @@ -1,211 +0,0 @@ -import SwiftUI -import Introspect - -struct ContentView: View { - @State private var selection = 0 - var body: some View { - TabView(selection: $selection) { - ListExample() - .tabItem { Text("List") } - .tag(0) - .introspectTabBarController { tabBarController in - tabBarController.tabBar.layer.backgroundColor = UIColor.green.cgColor - } - ScrollViewExample() - .tabItem { Text("ScrollView") } - .tag(1) - NavigationExample() - .tabItem { Text("Navigation") } - .tag(2) - ViewControllerExample() - .tabItem { Text("ViewController") } - .tag(3) - SimpleElementsExample() - .tabItem { Text("Simple elements") } - .tag(4) - } - } -} - -struct ListExample: View { - var body: some View { - - HStack { - VStack { - Text("Default") - List { - Text("Item 1") - Text("Item 2") - } - } - - VStack { - Text("List.introspectTableView()") - List { - Text("Item 1") - Text("Item 2") - } - .introspectTableView { tableView in - tableView.separatorStyle = .none - } - } - - VStack { - Text("child.introspectTableView()") - List { - Text("Item 1") - Text("Item 2") - .introspectTableView { tableView in - tableView.separatorStyle = .none - } - } - } - } - - } -} - -struct NavigationExample: View { - var body: some View { - NavigationView { - VStack { - Text("Customized") - } - .navigationBarTitle(Text("Customized"), displayMode: .inline) - .introspectNavigationController { nvc in - nvc.navigationBar.backgroundColor = .red - } - } - } -} - -struct ViewControllerExample: View { - var body: some View { - NavigationView { - VStack { - Text("Customized") - } - .introspectViewController { viewController in - viewController.navigationItem.title = "Customized" - } - } - } -} - -struct ScrollViewExample: View { - var body: some View { - HStack { - ScrollView { - Text("Default") - } - ScrollView { - Text("ScrollView.introspectScrollView()") - } - .introspectScrollView { scrollView in - scrollView.layer.backgroundColor = UIColor.red.cgColor - } - ScrollView { - Text("child.introspectScrollView()") - .introspectScrollView { scrollView in - scrollView.layer.backgroundColor = UIColor.green.cgColor - } - } - } - } -} - -struct SimpleElementsExample: View { - - @State private var textFieldValue = "" - @State private var toggleValue = false - @State private var sliderValue = 0.0 - @State private var datePickerValue = Date() - @State private var segmentedControlValue = 0 - - var body: some View { - VStack { - HStack { - TextField("Text Field Red", text: $textFieldValue) - .introspectTextField { textField in - textField.layer.backgroundColor = UIColor.red.cgColor - } - - TextField("Text Field Green", text: $textFieldValue) - .introspectTextField { textField in - textField.layer.backgroundColor = UIColor.green.cgColor - } - } - - HStack { - Toggle("Toggle Red", isOn: $toggleValue) - .introspectSwitch { uiSwitch in - uiSwitch.layer.backgroundColor = UIColor.red.cgColor - } - - Toggle("Toggle Green", isOn: $toggleValue) - .introspectSwitch { uiSwitch in - uiSwitch.layer.backgroundColor = UIColor.green.cgColor - } - } - - HStack { - Slider(value: $sliderValue, in: 0...100) - .introspectSlider { slider in - slider.layer.backgroundColor = UIColor.red.cgColor - } - - Slider(value: $sliderValue, in: 0...100) - .introspectSlider { slider in - slider.layer.backgroundColor = UIColor.green.cgColor - } - } - - HStack { - Stepper(onIncrement: {}, onDecrement: {}) { - Text("Stepper Red") - } - .introspectStepper { stepper in - stepper.layer.backgroundColor = UIColor.red.cgColor - } - - Stepper(onIncrement: {}, onDecrement: {}) { - Text("Stepper Green") - } - .introspectStepper { stepper in - stepper.layer.backgroundColor = UIColor.green.cgColor - } - } - - HStack { - DatePicker(selection: $datePickerValue) { - Text("DatePicker Red") - } - .introspectDatePicker { datePicker in - datePicker.layer.backgroundColor = UIColor.red.cgColor - } - } - - HStack { - Picker(selection: $segmentedControlValue, label: Text("Segmented control")) { - Text("Option 1").tag(0) - Text("Option 2").tag(1) - Text("Option 3").tag(2) - } - .pickerStyle(SegmentedPickerStyle()) - .introspectSegmentedControl { segmentedControl in - segmentedControl.layer.backgroundColor = UIColor.red.cgColor - } - } - } - - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - Group { - ListExample() - NavigationExample() - } - } -} diff --git a/IntrospectExamples/Info.plist b/IntrospectExamples/Info.plist deleted file mode 100644 index 49e2bd08f..000000000 --- a/IntrospectExamples/Info.plist +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 0.1.3 - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - - - - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/IntrospectExamples/SceneDelegate.swift b/IntrospectExamples/SceneDelegate.swift deleted file mode 100644 index dad927ff9..000000000 --- a/IntrospectExamples/SceneDelegate.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// SceneDelegate.swift -// IntrospectExamples -// -// Created by Lois Di Qual on 11/26/19. -// Copyright © 2019 Lois Di Qual. All rights reserved. -// - -import UIKit -import SwiftUI - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - let contentView = ContentView() - if let windowScene = scene as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - window.rootViewController = UIHostingController(rootView: contentView) - self.window = window - window.makeKeyAndVisible() - } - } -} - diff --git a/IntrospectTests/AppKitTests.swift b/IntrospectTests/AppKitTests.swift index 687b697c7..5a14e78ce 100644 --- a/IntrospectTests/AppKitTests.swift +++ b/IntrospectTests/AppKitTests.swift @@ -4,7 +4,6 @@ import XCTest import SwiftUI @testable import Introspect -@available(macOS 10.15.0, *) enum TestUtils { enum Constants { static let timeout: TimeInterval = 5 @@ -24,7 +23,20 @@ enum TestUtils { } } -@available(macOS 10.15.0, *) +private struct SplitNavigationTestView: View { + let spy: () -> Void + var body: some View { + NavigationView { + VStack { + EmptyView() + } + } + .introspectSplitView { splitView in + self.spy() + } + } +} + private struct ListTestView: View { let spy1: () -> Void @@ -53,7 +65,6 @@ private struct ListTestView: View { } } -@available(macOS 10.15.0, *) private struct ScrollTestView: View { let spy1: (NSScrollView) -> Void @@ -78,7 +89,6 @@ private struct ScrollTestView: View { } } -@available(macOS 10.15.0, *) private struct NestedScrollTestView: View { let spy1: (NSScrollView) -> Void @@ -103,7 +113,32 @@ private struct NestedScrollTestView: View { } } -@available(macOS 10.15.0, *) +private struct MaskedScrollTestView: View { + + let spy1: (NSScrollView) -> Void + let spy2: (NSScrollView) -> Void + + var body: some View { + HStack { + ScrollView { + Text("Item 1") + } + .introspectScrollView { scrollView in + self.spy1(scrollView) + } + .clipped() + .clipShape(RoundedRectangle(cornerRadius: 20.0)) + .cornerRadius(2.0) + ScrollView { + Text("Item 1") + .introspectScrollView { scrollView in + self.spy2(scrollView) + } + } + } + } +} + private struct TextFieldTestView: View { let spy: () -> Void @State private var textFieldValue = "" @@ -115,7 +150,7 @@ private struct TextFieldTestView: View { } } -@available(macOS 11.0, *) +@available(macOS 11, *) private struct TextEditorTestView: View { let spy: () -> Void @State private var textEditorValue = "" @@ -127,7 +162,6 @@ private struct TextEditorTestView: View { } } -@available(macOS 10.15.0, *) private struct SliderTestView: View { let spy: () -> Void @State private var sliderValue = 0.0 @@ -139,7 +173,6 @@ private struct SliderTestView: View { } } -@available(macOS 10.15.0, *) private struct StepperTestView: View { let spy: () -> Void var body: some View { @@ -152,7 +185,6 @@ private struct StepperTestView: View { } } -@available(macOS 10.15.0, *) private struct DatePickerTestView: View { let spy: () -> Void @State private var datePickerValue = Date() @@ -166,7 +198,6 @@ private struct DatePickerTestView: View { } } -@available(macOS 10.15.0, *) private struct SegmentedControlTestView: View { @State private var pickerValue = 0 let spy: () -> Void @@ -183,7 +214,6 @@ private struct SegmentedControlTestView: View { } } -@available(macOS 10.15.0, *) private struct TabViewTestView: View { let spy: () -> Void var body: some View { @@ -203,7 +233,6 @@ private struct TabViewTestView: View { } } -@available(macOS 10.15.0, *) private struct ButtonTestView: View { let spy: () -> Void var body: some View { @@ -214,7 +243,6 @@ private struct ButtonTestView: View { } } -@available(macOS 10.15.0, *) private struct ToggleTestView: View { let spy: () -> Void @State private var toggleValue = false @@ -226,7 +254,7 @@ private struct ToggleTestView: View { } } -@available(macOS 11.0, *) +@available(macOS 11, *) private struct ColorWellTestView: View { @State private var color = Color.black let spy: () -> Void @@ -239,10 +267,37 @@ private struct ColorWellTestView: View { } } -@available(macOS 10.15.0, *) +import MapKit +@available(macOS 11, *) +private struct MapTestView: View { + @State private var coordinateRegion = MKCoordinateRegion(.world) + let spy: () -> Void + + var body: some View { + Map(coordinateRegion: $coordinateRegion) + .introspectMapView { mapView in + self.spy() + } + } +} + class AppKitTests: XCTestCase { - + + func testSplitNavigation() { + + let expectation = XCTestExpectation() + let view = SplitNavigationTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: TestUtils.Constants.timeout) + } + func testList() { + if #available(macOS 11, *) { + return // TODO: verify whether List still uses NSTableView under the hood in macOS >=11 + } + let expectation1 = XCTestExpectation() let expectation2 = XCTestExpectation() let cellExpectation1 = XCTestExpectation() @@ -285,7 +340,6 @@ class AppKitTests: XCTestCase { } func testNestedScrollView() throws { - let expectation1 = XCTestExpectation() let expectation2 = XCTestExpectation() @@ -310,6 +364,33 @@ class AppKitTests: XCTestCase { XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2) } + + func testMaskedScrollView() throws { + let expectation1 = XCTestExpectation() + let expectation2 = XCTestExpectation() + + var scrollView1: NSScrollView? + var scrollView2: NSScrollView? + + let view = MaskedScrollTestView( + spy1: { scrollView in + scrollView1 = scrollView + expectation1.fulfill() + }, + spy2: { scrollView in + scrollView2 = scrollView + expectation2.fulfill() + } + ) + + TestUtils.present(view: view) + wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout) + + let unwrappedScrollView1 = try XCTUnwrap(scrollView1) + let unwrappedScrollView2 = try XCTUnwrap(scrollView2) + + XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2) + } func testTextField() { @@ -320,17 +401,14 @@ class AppKitTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - - func testTextEditor() { - if #available(macOS 11.0, *) { - let expectation = XCTestExpectation() - let view = TextEditorTestView(spy: { - expectation.fulfill() - }) - TestUtils.present(view: view) - wait(for: [expectation], timeout: TestUtils.Constants.timeout) - } + func testTextEditor() { + let expectation = XCTestExpectation() + let view = TextEditorTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: TestUtils.Constants.timeout) } func testSlider() { @@ -382,9 +460,12 @@ class AppKitTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - + func testButton() { - + + if #available(macOS 12, *) { + return // TODO: verify whether Button still uses NSButton under the hood in macOS >=12 + } let expectation = XCTestExpectation() let view = ButtonTestView(spy: { expectation.fulfill() @@ -405,14 +486,21 @@ class AppKitTests: XCTestCase { func testColorPicker() { - if #available(macOS 11.0, *) { - let expectation = XCTestExpectation() - let view = ColorWellTestView(spy: { - expectation.fulfill() - }) - TestUtils.present(view: view) - wait(for: [expectation], timeout: TestUtils.Constants.timeout) - } + let expectation = XCTestExpectation() + let view = ColorWellTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: TestUtils.Constants.timeout) + } + + func testMapView() { + let expectation = XCTestExpectation() + let view = MapTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: TestUtils.Constants.timeout) } } #endif diff --git a/IntrospectTests/Info.plist b/IntrospectTests/Info.plist deleted file mode 100644 index 5e0761dbe..000000000 --- a/IntrospectTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 0.1.3 - CFBundleVersion - 1 - - diff --git a/IntrospectTests/UIKitTests.swift b/IntrospectTests/UIKitTests.swift index 8f22333ce..dcb0bfcbf 100644 --- a/IntrospectTests/UIKitTests.swift +++ b/IntrospectTests/UIKitTests.swift @@ -4,7 +4,6 @@ import SwiftUI @testable import Introspect -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) enum TestUtils { enum Constants { static let timeout: TimeInterval = 3 @@ -33,7 +32,6 @@ enum TestUtils { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct NavigationTestView: View { let spy: () -> Void var body: some View { @@ -49,7 +47,6 @@ private struct NavigationTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct SplitNavigationTestView: View { let spy: () -> Void var body: some View { @@ -65,7 +62,6 @@ private struct SplitNavigationTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct ViewControllerTestView: View { let spy: () -> Void var body: some View { @@ -80,7 +76,6 @@ private struct ViewControllerTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct NavigationRootTestView: View { let spy: () -> Void var body: some View { @@ -95,7 +90,6 @@ private struct NavigationRootTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct TabTestView: View { @State private var selection = 0 let spy: () -> Void @@ -110,7 +104,6 @@ private struct TabTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct TabRootTestView: View { @State private var selection = 0 let spy: () -> Void @@ -125,7 +118,23 @@ private struct TabRootTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) +@available(iOS 14, tvOS 14, *) +private struct PageTabViewStyleTestView: View { + + let spy: (UICollectionView, UIScrollView) -> Void + + var body: some View { + TabView { + Text("Item 1") + .tag(0) + } + .tabViewStyle(PageTabViewStyle()) + .introspectPagedTabView { collectionView, scrollView in + spy(collectionView, scrollView) + } + } +} + private struct ListTestView: View { let spy1: () -> Void @@ -134,27 +143,47 @@ private struct ListTestView: View { let spyCell2: () -> Void var body: some View { - List { - Text("Item 1") - Text("Item 2") - .introspectTableView { tableView in - self.spy2() - } - .introspectTableViewCell { cell in - self.spyCell2() - } - - } - .introspectTableView { tableView in - self.spy1() - } - .introspectTableViewCell { cell in - self.spyCell1() + if #available(iOS 16, tvOS 16, *) { + List { + Text("Item 1") + Text("Item 2") + .introspectCollectionView { tableView in + self.spy2() + } + .introspectCollectionViewCell { cell in + self.spyCell2() + } + + } + .introspectCollectionView { tableView in + self.spy1() + } + .introspectCollectionViewCell { cell in + self.spyCell1() + } + } else { + List { + Text("Item 1") + Text("Item 2") + .introspectTableView { tableView in + self.spy2() + } + .introspectTableViewCell { cell in + self.spyCell2() + } + + } + .introspectTableView { tableView in + self.spy1() + } + .introspectTableViewCell { cell in + self.spyCell1() + } } } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) + private struct ScrollTestView: View { let spy1: (UIScrollView) -> Void @@ -178,7 +207,6 @@ private struct ScrollTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct NestedScrollTestView: View { let spy1: (UIScrollView) -> Void @@ -203,7 +231,32 @@ private struct NestedScrollTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) +private struct MaskedScrollTestView: View { + + let spy1: (UIScrollView) -> Void + let spy2: (UIScrollView) -> Void + + var body: some View { + HStack { + ScrollView { + Text("Item 1") + } + .introspectScrollView { scrollView in + self.spy1(scrollView) + } + .clipped() + .clipShape(RoundedRectangle(cornerRadius: 20.0)) + .cornerRadius(2.0) + ScrollView { + Text("Item 1") + .introspectScrollView { scrollView in + self.spy2(scrollView) + } + } + } + } +} + private struct TextFieldTestView: View { let spy1: (UITextField) -> Void let spy2: (UITextField) -> Void @@ -234,7 +287,7 @@ private struct TextFieldTestView: View { } } -@available(iOS 14.0, macCatalyst 14.0, macOS 11.0, tvOS 13.0, *) +@available(iOS 14, *) @available(tvOS, unavailable, message: "TextEditor is not available in tvOS.") private struct TextEditorTestView: View { let spy: () -> Void @@ -247,7 +300,6 @@ private struct TextEditorTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) @available(tvOS, unavailable) private struct ToggleTestView: View { let spy: () -> Void @@ -260,7 +312,6 @@ private struct ToggleTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) @available(tvOS, unavailable) private struct SliderTestView: View { let spy: () -> Void @@ -273,7 +324,6 @@ private struct SliderTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) @available(tvOS, unavailable) private struct StepperTestView: View { let spy: () -> Void @@ -287,7 +337,6 @@ private struct StepperTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) @available(tvOS, unavailable) private struct DatePickerTestView: View { let spy: () -> Void @@ -302,7 +351,6 @@ private struct DatePickerTestView: View { } } -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) private struct SegmentedControlTestView: View { @State private var pickerValue = 0 let spy: () -> Void @@ -319,12 +367,13 @@ private struct SegmentedControlTestView: View { } } -@available(iOS 14.0, tvOS 13.0, macOS 11.0, *) +#if os(iOS) +@available(iOS 14.0, *) @available(tvOS, unavailable) private struct ColorWellTestView: View { @State private var color = Color.black let spy: () -> Void - + var body: some View { ColorPicker("Picker", selection: $color) .introspectColorWell { colorWell in @@ -332,8 +381,22 @@ private struct ColorWellTestView: View { } } } +#endif + +import MapKit +@available(iOS 14, tvOS 14, *) +private struct MapTestView: View { + @State private var coordinateRegion = MKCoordinateRegion(.world) + let spy: () -> Void + + var body: some View { + Map(coordinateRegion: $coordinateRegion) + .introspectMapView { mapView in + self.spy() + } + } +} -@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *) class UIKitTests: XCTestCase { func testNavigation() { @@ -376,7 +439,10 @@ class UIKitTests: XCTestCase { } func testList() { - + if #available(tvOS 16, *) { + return // TODO: verify whether List still uses NSTableView under the hood in tvOS 16 + } + let expectation1 = XCTestExpectation() let expectation2 = XCTestExpectation() let cellExpectation1 = XCTestExpectation() @@ -445,6 +511,34 @@ class UIKitTests: XCTestCase { XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2) } + + func testMaskedScrollView() throws { + + let expectation1 = XCTestExpectation() + let expectation2 = XCTestExpectation() + + var scrollView1: UIScrollView? + var scrollView2: UIScrollView? + + let view = MaskedScrollTestView( + spy1: { scrollView in + scrollView1 = scrollView + expectation1.fulfill() + }, + spy2: { scrollView in + scrollView2 = scrollView + expectation2.fulfill() + } + ) + + TestUtils.present(view: view) + wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout) + + let unwrappedScrollView1 = try XCTUnwrap(scrollView1) + let unwrappedScrollView2 = try XCTUnwrap(scrollView2) + + XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2) + } func testTextField() throws { @@ -491,28 +585,28 @@ class UIKitTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - - #if os(iOS) - func testSplitNavigation() { + + func testRootNavigation() { let expectation = XCTestExpectation() - let view = SplitNavigationTestView(spy: { + let view = NavigationRootTestView(spy: { expectation.fulfill() }) TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - - func testRootNavigation() { - + + #if !os(tvOS) + func testSplitNavigation() { + let expectation = XCTestExpectation() - let view = NavigationRootTestView(spy: { + let view = SplitNavigationTestView(spy: { expectation.fulfill() }) TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - + func testToggle() { let expectation = XCTestExpectation() @@ -522,7 +616,7 @@ class UIKitTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - + func testSlider() { let expectation = XCTestExpectation() @@ -532,7 +626,7 @@ class UIKitTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - + func testStepper() { let expectation = XCTestExpectation() @@ -542,7 +636,7 @@ class UIKitTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - + func testDatePicker() { let expectation = XCTestExpectation() @@ -553,8 +647,7 @@ class UIKitTests: XCTestCase { wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - @available(iOS 14.0, macCatalyst 14.0, macOS 11.0, *) - @available(tvOS, unavailable, message: "TextEditor is not available in tvOS.") + @available(iOS 14, *) func testTextEditor() { let expectation = XCTestExpectation() @@ -565,8 +658,7 @@ class UIKitTests: XCTestCase { wait(for: [expectation], timeout: TestUtils.Constants.timeout) } - @available(iOS 14.0, macCatalyst 14.0, macOS 11.0, *) - @available(tvOS, unavailable, message: "ColorPicker is not available in tvOS.") + @available(iOS 14, *) func testColorPicker() { let expectation = XCTestExpectation() @@ -576,6 +668,72 @@ class UIKitTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: TestUtils.Constants.timeout) } + + @available(iOS 14, tvOS 14, *) + func testPagedTabView() throws { + + var collectionView1: UICollectionView? + var scrollView1: UIScrollView? + + let expectation = XCTestExpectation() + let view = PageTabViewStyleTestView(spy: { collectionView, scrollView in + collectionView1 = collectionView + scrollView1 = scrollView + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: TestUtils.Constants.timeout) + + let unwrappedCollectionView = try XCTUnwrap(collectionView1) + let unwrappedScrollView = try XCTUnwrap(scrollView1) + + if #available(iOS 16, tvOS 16, *) { + XCTAssertTrue(unwrappedCollectionView == unwrappedScrollView) + } else { + XCTAssertTrue(unwrappedCollectionView.subviews.contains(where: { $0 === unwrappedScrollView })) + } + } + + @available(tvOS, unavailable) + func testSearchController() { + guard #available(iOS 15, *) else { + return + } + struct SearchControllerTestView: View { + @State var searchText = "" + let spy: () -> Void + + var body: some View { + NavigationView { + EmptyView() + .searchable(text: $searchText) + .introspectSearchController { searchController in + self.spy() + } + } + .introspectSplitViewController { splitViewController in + splitViewController.preferredDisplayMode = .oneOverSecondary + } + } + } + + let expectation = XCTestExpectation() + let view = SearchControllerTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: TestUtils.Constants.timeout) + } #endif + + @available(iOS 14, tvOS 14, *) + func testMapView() { + let expectation = XCTestExpectation() + let view = MapTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: TestUtils.Constants.timeout) + } } #endif diff --git a/Package.swift b/Package.swift index 574ee13df..e5f668283 100644 --- a/Package.swift +++ b/Package.swift @@ -1,31 +1,28 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.5 import PackageDescription let package = Package( name: "Introspect", platforms: [ - .macOS(.v10_13), - .iOS(.v11), - .tvOS(.v11) + .iOS(.v13), + .tvOS(.v13), + .macOS(.v10_15), ], products: [ - .library( - name: "Introspect", - targets: ["Introspect"] - ) + .library(name: "Introspect", targets: ["Introspect"]), + .library(name: "Introspect-Static", type: .static, targets: ["Introspect"]), + .library(name: "Introspect-Dynamic", type: .dynamic, targets: ["Introspect"]), ], - dependencies: [], targets: [ .target( name: "Introspect", - dependencies: [], path: "Introspect" ), .testTarget( name: "IntrospectTests", dependencies: ["Introspect"], path: "IntrospectTests" - ) + ), ] -) \ No newline at end of file +) diff --git a/Package@swift-5.7.swift b/Package@swift-5.7.swift new file mode 100644 index 000000000..1d3b33d5b --- /dev/null +++ b/Package@swift-5.7.swift @@ -0,0 +1,39 @@ +// swift-tools-version:5.7 + +import PackageDescription + +let package = Package( + name: "swiftui-introspect", + platforms: [ + .iOS(.v13), + .tvOS(.v13), + .macOS(.v10_15), + ], + products: [ + // new module + .library(name: "SwiftUIIntrospect", targets: ["SwiftUIIntrospect"]), + .library(name: "SwiftUIIntrospect-Static", type: .static, targets: ["SwiftUIIntrospect"]), + .library(name: "SwiftUIIntrospect-Dynamic", type: .dynamic, targets: ["SwiftUIIntrospect"]), + + // old module + .library(name: "Introspect", targets: ["Introspect"]), + .library(name: "Introspect-Static", type: .static, targets: ["Introspect"]), + .library(name: "Introspect-Dynamic", type: .dynamic, targets: ["Introspect"]), + ], + targets: [ + .target( + name: "Introspect", + path: "Introspect" + ), + .testTarget( + name: "IntrospectTests", + dependencies: ["Introspect"], + path: "IntrospectTests" + ), + + .target( + name: "SwiftUIIntrospect", + path: "Sources" + ), + ] +) diff --git a/Podfile b/Podfile deleted file mode 100644 index 08bdf02ce..000000000 --- a/Podfile +++ /dev/null @@ -1,9 +0,0 @@ -platform :ios, "13.0" -inhibit_all_warnings! -use_frameworks! - -# Disable sending stats (takes too much time) -ENV["COCOAPODS_DISABLE_STATS"] = "true" - -target "IntrospectExamples" -pod "Reveal-SDK", "24", :configurations => ['Debug'] diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index bcf762566..000000000 --- a/Podfile.lock +++ /dev/null @@ -1,16 +0,0 @@ -PODS: - - Reveal-SDK (24) - -DEPENDENCIES: - - Reveal-SDK (= 24) - -SPEC REPOS: - trunk: - - Reveal-SDK - -SPEC CHECKSUMS: - Reveal-SDK: 5d7e56b8f018c0a88b3a2c10bf68d598bbd3b071 - -PODFILE CHECKSUM: 3558c3b60c01e8cff4f7b0ac1bd6ef19441afc81 - -COCOAPODS: 1.10.0 diff --git a/README.md b/README.md index d58c97586..8f68dae6e 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,83 @@ -Introspect for SwiftUI -====================== +SwiftUI Introspect +================= -[![CircleCI_Status]][CircleCI_URL]  [![GithubCI_Status]][GithubCI_URL] [![Siteline_Badge]](https://siteline.com) [![Quintschaf_Badge]](https://quintschaf.com) +[![CI Status Badge](https://github.com/siteline/SwiftUI-Introspect/actions/workflows/ci.yml/badge.svg)](https://github.com/siteline/SwiftUI-Introspect/actions/workflows/ci.yml) +[![Platform Compatibility Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fsiteline%2FSwiftUI-Introspect%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/siteline/SwiftUI-Introspect) -> Introspect allows you to get the underlying UIKit or AppKit element of a SwiftUI view. +> **Note** +> +> [`SwiftUIIntrospect`](./Package@swift-5.7.swift#L14) is an all-new module based off the original [`Introspect`](./Package.swift#L13) module that improves on stability, predictability, and ergonomics. +> +> Both modules currently live together under this repo, but the plan is to ultimately obsolete `Introspect` in favor of `SwiftUIIntrospect` as part of a 1.0 release. +> +> While `Introspect` supports Swift 5.5 or higher, `SwiftUIIntrospect` requires Swift 5.7 or higher due to the use of more recent language features which partially enable the aforementioned improvements over the original. -For instance, with Introspect you can access `UITableView` to modify separators, or `UINavigationController` to customize the tab bar. +SwiftUIIntrospect allows you to get the underlying UIKit or AppKit element of a SwiftUI view. + +For instance, with SwiftUIIntrospect you can access `UITableView` to modify separators, or `UINavigationController` to customize the tab bar. How it works ------------ -Introspect works by adding a custom `IntrospectionView` to the view hierarchy, then looking into the UIKit hierarchy to find the relevant view. +SwiftUIIntrospect works by adding an invisible `IntrospectionView` on top of the selected view, and an invisible "anchor" view underneath it, then looking through the UIKit/AppKit view hierarchy between the two to find the relevant view. -![](./docs/diagram.png) +For instance, when introspecting a `ScrollView`... -For instance, when introspecting a `TextField`, it will: +```swift +ScrollView { + Text("Item 1") +} +.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { scrollView in + // do something with UIScrollView +} +``` - - Add `IntrospectionView` as an overlay of `TextField` - - Get the view host of the introspection view (which is alongside the view host of the `UITextField`) - - Get the previous sibling containing `UITextField` +... it will: -**Please note that this introspection method might break in future SwiftUI releases.** Future implementations might not use the same hierarchy, or might not use UIKit elements that are being looked for. Though the library is unlikely to crash, the `.introspect()` method will not be called in those cases. +1. Add marker views in front and behind `ScrollView`. +2. Traverse through all subviews between both marker views until a `UIScrollView` instance (if any) is found. -### Usage in production +> **Warning** +> +> Although this introspection method is very solid and unlikely to break in itself, future OS releases require explicit opt-in for introspection (`.iOS(.vXYZ)`), given potential differences in underlying UIKit/AppKit view types between major OS versions. -`Introspect` is meant to be used in production. It does not use any private API. It only inspects the view hierarchy using publicly available methods. The library takes a defensive approach to inspecting the view hierarchy: there is no hard assumption that elements are laid out a certain way, there is no force-cast to UIKit classes, and the `introspect()` methods are simply ignored if UIKit views cannot be found. +By default, `.introspect` works directly on its _receiver_. This means calling `.introspect` from inside the view you're trying to introspect won't have any effect. This is different to the original `Introspect` module in which some views would implicitly allow introspection from within. This is because most of the time it's more stable and predictable to introspect views directly, but there are times when it's not possible or simply too inflexible for library developers. You **can** introspect an _ancestor_ with `SwiftUIIntrospect`, but you must opt into this explicitly by overriding the introspection `scope`: +```swift +ScrollView { + Text("Item 1") + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor) { scrollView in + // do something with UIScrollView + } +} +``` + +### Usage in production + +`SwiftUIIntrospect` is meant to be used in production. It does not use any private API. It only inspects the view hierarchy using publicly available methods. The library takes a defensive approach to inspecting the view hierarchy: there is no hard assumption that elements are laid out a certain way, there is no force-cast to UIKit/AppKit classes, and the `.introspect` modifier is simply ignored if UIKit/AppKit views cannot be found. Install ------- -### SwiftPM +### Swift Package Manager -``` -https://github.com/siteline/SwiftUI-Introspect.git +```swift +let package = Package( + dependencies: [ + .package(url: "https://github.com/siteline/swiftui-introspect", from: "0.12.0"), + ], + targets: [ + .target(name: <#Target Name#>, dependencies: [ + .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), + ]), + ] +) ``` -### Cocoapods +### CocoaPods -``` -pod 'Introspect' +```ruby +pod 'SwiftUIIntrospect' ``` Introspection @@ -47,27 +85,59 @@ Introspection ### Implemented -SwiftUI | UIKit | AppKit | Introspect ---- | --- | --- | --- -NavigationView (StackNavigationViewStyle) | UINavigationController | _N/A_ | `.introspectNavigationController()` -NavigationView (DoubleColumnNavigationViewStyle) | UISplitViewController | _N/A_ | `.introspectSplitViewController()` -_Any embedded view_ | UIViewController | _N/A_ | `.introspectViewController()` -ScrollView | UIScrollView | NSScrollView | `.introspectScrollView()` -List | UITableView | NSTableView | `.introspectTableView()` -View in List | UITableViewCell | NSTableCellView | `introspectTableViewCell()` -TabView | UITabBarController | NSTabView | `.introspectTabBarController()` (iOS)
`.introspectTabView()` (macOS) -TextField | UITextField | NSTextField | `.introspectTextField()` -Toggle | UISwitch | NSButton | `.introspectSwitch()` (iOS)
`.introspectButton()` (macOS) -Slider | UISlider | NSSlider | `.introspectSlider()` -Stepper | UIStepper | NSStepper | `.introspectStepper()` -DatePicker | UIDatePicker | NSDatePicker | `.introspectDatePicker()` -Picker (SegmentedPickerStyle) | UISegmentedControl | NSSegmentedControl | `.introspectSegmentedControl()` -Button | _N/A_ | NSButton | `.introspectButton()` -ColorPicker | UIColorWell | NSColorWell | `.introspectColorWell()` -TextEditor | UITextView | NSTextView | `.introspectTextView()` - - -**Missing an element?** Please [create an issue](https://github.com/timbersoftware/SwiftUI-Introspect/issues). As a temporary solution, you can [implement your own selector](#implement-your-own-selector). +- [`Button`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/buttontype) +- [`ColorPicker`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/colorpickertype) +- [`DatePicker`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/datepickertype) +- [`DatePicker` with `.compact` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/datepickerwithcompactstyletype) +- [`DatePicker` with `.field` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/datepickerwithfieldstyletype) +- [`DatePicker` with `.graphical` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/datepickerwithgraphicalstyletype) +- [`DatePicker` with `.stepperField` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/datepickerwithstepperfieldstyletype) +- [`DatePicker` with `.wheel` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/datepickerwithwheelstyletype) +- [`Form`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/formtype) +- [`Form` with `.grouped` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/formwithgroupedstyletype) +- [`.fullScreenCover`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/fullScreenCovertype) +- [`List`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/listtype) +- [`List` with `.bordered` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/listwithborderedstyletype) +- [`List` with `.grouped` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/listwithgroupedstyletype) +- [`List` with `.insetGrouped` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/listwithinsetgroupedstyletype) +- [`List` with `.inset` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/listwithinsetstyletype) +- [`List` with `.sidebar` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/listwithsidebarstyletype) +- [`ListCell`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/listcelltype) +- [`Map`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/maptype) +- [`NavigationSplitView`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/navigationsplitviewtype) +- [`NavigationStack`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/navigationstacktype) +- [`NavigationView` with `.columns` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/NavigationViewWithColumnsStyleType) +- [`NavigationView` with `.stack` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/NavigationViewWithStackStyleType) +- [`PageControl`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/pagecontroltype) +- [`Picker` with `.menu` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/pickerwithmenustyletype) +- [`Picker` with `.segmented` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/pickerwithsegmentedstyletype) +- [`Picker` with `.wheel` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/pickerwithwheelstyletype) +- [`.popover`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/popovertype) +- [`ProgressView` with `.circular` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/progressviewwithcircularstyletype) +- [`ProgressView` with `.linear` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/progressviewwithlinearstyletype) +- [`ScrollView`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/scrollviewtype) +- [`.searchable`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/searchfieldtype) +- [`SecureField`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/securefieldtype) +- [`.sheet`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/sheettype) +- [`SignInWithAppleButton`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/SignInWithAppleButtonType) +- [`Slider`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/slidertype) +- [`Stepper`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/steppertype) +- [`Table`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/tabletype) +- [`TabView`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/tabviewtype) +- [`TabView` with `.page` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/TabViewWithPageStyleType) +- [`TextEditor`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/texteditortype) +- [`TextField`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/textfieldtype) +- [`TextField` with `.vertical` axis](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/TextFieldWithVerticalAxisType) +- [`Toggle`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/toggletype) +- [`Toggle` with `button` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/togglewithbuttonstyletype) +- [`Toggle` with `checkbox` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/togglewithcheckboxstyletype) +- [`Toggle` with `switch` style](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/togglewithswitchstyletype) +- [`VideoPlayer`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/videoplayertype) +- [`View`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/viewtype) +- [`ViewController`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/viewcontrollertype) +- [`Window`](https://swiftpackageindex.com/siteline/swiftui-introspect/master/documentation/swiftuiintrospect/windowtype) + +**Missing an element?** Please [create an issue](https://github.com/timbersoftware/SwiftUI-Introspect/issues). As a temporary solution, you can [implement your own introspectable view type](#implement-your-own-view-type). ### Cannot implement @@ -84,16 +154,15 @@ Examples ```swift List { - Text("Item 1") - Text("Item 2") + Text("Item") } -.introspectTableView { tableView in - tableView.separatorStyle = .none +.introspect(.list, on: .iOS(.v13, .v14, .v15)) { tableView in + tableView.backgroundView = UIView() + tableView.backgroundColor = .cyan } -.introspectTableViewCell { cell in - let backgroundView = UIView() - backgroundView.backgroundColor = .clear - cell.selectedBackgroundView = backgroundView +.introspect(.list, on: .iOS(.v16, .v17)) { collectionView in + collectionView.backgroundView = UIView() + collectionView.subviews.dropFirst(1).first?.backgroundColor = .cyan } ``` @@ -101,10 +170,10 @@ List { ```swift ScrollView { - Text("Item 2") + Text("Item") } -.introspectScrollView { scrollView in - scrollView.refreshControl = UIRefreshControl() +.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { scrollView in + scrollView.backgroundColor = .red } ``` @@ -112,90 +181,127 @@ ScrollView { ```swift NavigationView { - Text("Item 2") - .introspectNavigationController { navigationController in - navigationController.navigationBar.backgroundColor = .red - } + Text("Item") +} +.navigationViewStyle(.stack) +.introspect(.navigationView(style: .stack), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { navigationController in + navigationController.navigationBar.backgroundColor = .cyan } ``` ### TextField ```swift -TextField("Text Field", text: $textFieldValue) -.introspectTextField { textField in - textField.layer.backgroundColor = UIColor.red.cgColor -} +TextField("Text Field", text: <#Binding#>) + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { textField in + textField.backgroundColor = .red + } ``` -Implement your own selector ---------------------------- +Advanced usage +-------------- + +### Implement your own introspectable type **Missing an element?** Please [create an issue](https://github.com/timbersoftware/SwiftUI-Introspect/issues). -In case Introspect doesn't support the SwiftUI element that you're looking for, you can implement your own selector. For example, to look for a `UITextField`: +In case SwiftUIIntrospect (unlikely) doesn't support the SwiftUI element that you're looking for, you can implement your own introspectable type. + +For example, here's how the library implements the introspectable `TextField` type: ```swift -extension View { - public func introspectTextField(customize: @escaping (UITextField) -> ()) -> some View { - return inject(UIKitIntrospectionView( - selector: { introspectionView in - guard let viewHost = Introspect.findViewHost(from: introspectionView) else { - return nil - } - return Introspect.previousSibling(containing: UITextField.self, from: viewHost) - }, - customize: customize - )) - } -} -``` +import SwiftUI +@_spi(Advanced) import SwiftUIIntrospect -You can use any of the following [methods](https://github.com/timbersoftware/SwiftUI-Introspect/blob/master/Introspect/Introspect.swift#L3-L71) to inspect the hierarchy: +public struct TextFieldType: IntrospectableViewType {} - - `Introspect.findChild(ofType:in:)` - - `Introspect.findChildUsingFrame(ofType:in:from:)` - - `Introspect.previousSibling(containing:from:)` - - `Introspect.nextSibling(containing:from:)` - - `Introspect.findAncestor(ofType:from:)` - - `Introspect.findHostingView(from:)` - - `Introspect.findViewHost(from:)` +extension IntrospectableViewType where Self == TextFieldType { + public static var textField: Self { .init() } +} -Releasing ---------- +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} - - Increment version number: +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} -``` -$ bundle exec fastlane run increment_version_number bump_type:minor # major|minor|patch +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif ``` - - Update changelog with new version - - Bump version in `Introspect.podspec` - - Commit and push changes - - Tag new version: +### Introspect on future platform versions -``` -$ git tag -a -m "" -$ git push origin --tags -``` +By default, introspection applies per specific platform version. This is a sensible default for maximum predictability in regularly maintained codebases, but it's not always a good fit for e.g. library developers who may want to cover as many future platform versions as possible in order to provide the best chance for long-term future functionality of their library without regular maintenance. - - Push to cocoapods trunk: +For such cases, SwiftUI Introspect offers range-based platform version predicates behind the Advanced SPI: -``` -$ bundle exec pod trunk push . +```swift +import SwiftUI +@_spi(Advanced) import SwiftUIIntrospect + +struct ContentView: View { + var body: some View { + ScrollView { + // ... + } + .introspect(.scrollView, on: .iOS(.v13...)) { scrollView in + // ... + } + } +} ``` +Bear in mind this should be used cautiosly, and with full knowledge that any future OS version might break the expected introspection types unless explicitly available. For instance, if in the example above hypothetically iOS 18 stops using UIScrollView under the hood, the customization closure will never be called on said platform. - -[CircleCI_Status]: https://circleci.com/gh/siteline/SwiftUI-Introspect.svg?style=svg&circle-token=6f995f204d4d417d31f79e7257f6e1ecf430ae07 +### Keep instances outside the customize closure -[CircleCI_URL]: https://circleci.com/gh/siteline/SwiftUI-Introspect +Sometimes, you might need to keep your introspected instance around for longer than the customization closure lifetime. In such cases, `@State` is not a good option because it produces retain cycles. Instead, SwiftUI Introspect offers a `@Weak` property wrapper behind the Advanced SPI: + +```swift +import SwiftUI +@_spi(Advanced) import SwiftUIIntrospect + +struct ContentView: View { + @Weak var scrollView: UIScrollView? + + var body: some View { + ScrollView { + // ... + } + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { scrollView in + self.scrollView = scrollView + } + } +} +``` -[GithubCI_Status]: https://github.com/siteline/swiftui-introspect/actions/workflows/build-and-test.yml/badge.svg?branch=master +Community projects +------------------ -[GithubCI_URL]: https://github.com/siteline/SwiftUI-Introspect/actions/workflows/build-and-test.yml +Here's a list of open source libraries powered by the SwiftUI Introspect library: -[Siteline_Badge]: https://badgen.net/badge/Built%20by/Siteline/blue?icon=https://uploads-ssl.webflow.com/5f4513afbbfc64c4777fcccf/5f525b122370d681879e170e_siteline-icon.svg +- [CustomKeyboardKit](https://github.com/paescebu/CustomKeyboardKit) +- [NavigationTransitions](https://github.com/davdroman/swiftui-navigation-transitions) -[Quintschaf_Badge]: https://badgen.net/badge/Maintained%20by/Quintschaf/cyan?icon=https://quintschaf.com/assets/logo.svg +If you're working on a library built on SwiftUI Introspect or know of one, feel free to submit a PR adding it to the list. diff --git a/Sources/Introspect.swift b/Sources/Introspect.swift new file mode 100644 index 000000000..58fcae894 --- /dev/null +++ b/Sources/Introspect.swift @@ -0,0 +1,216 @@ +import SwiftUI + +/// The scope of introspection i.e. where introspect should look to find +/// the desired target view relative to the applied `.introspect(...)` +/// modifier. +public struct IntrospectionScope: OptionSet { + /// Look within the `receiver` of the `.introspect(...)` modifier. + public static let receiver = Self(rawValue: 1 << 0) + /// Look for an `ancestor` relative to the `.introspect(...)` modifier. + public static let ancestor = Self(rawValue: 1 << 1) + + @_spi(Internals) public let rawValue: UInt + + @_spi(Internals) public init(rawValue: UInt) { + self.rawValue = rawValue + } +} + +extension View { + /// Introspects a SwiftUI view to find its underlying UIKit/AppKit instance. + /// + /// - Parameters: + /// - viewType: The type of view to be introspected. + /// - platforms: A list of version predicates that specify platform-specific entities associated with the view. + /// - scope: Optionally overrides the view's default scope of introspection. + /// - customize: A closure that hands over the underlying UIKit/AppKit instance ready for customization. + /// + /// Here's an example usage: + /// + /// ```swift + /// struct ContentView: View { + /// @State var text = "" + /// + /// var body: some View { + /// TextField("Placeholder", text: $text) + /// .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { + /// print(type(of: $0)) // UITextField + /// } + /// } + /// } + /// ``` + public func introspect( + _ viewType: SwiftUIViewType, + on platforms: (PlatformViewVersionPredicate)..., + scope: IntrospectionScope? = nil, + customize: @escaping (PlatformSpecificEntity) -> Void + ) -> some View { + self.modifier(IntrospectModifier(viewType, platforms: platforms, scope: scope, customize: customize)) + } +} + +struct IntrospectModifier: ViewModifier { + let id = IntrospectionViewID() + let scope: IntrospectionScope + let selector: IntrospectionSelector? + let customize: (PlatformSpecificEntity) -> Void + + init( + _ viewType: SwiftUIViewType, + platforms: [PlatformViewVersionPredicate], + scope: IntrospectionScope?, + customize: @escaping (PlatformSpecificEntity) -> Void + ) { + self.scope = scope ?? viewType.scope + self.selector = platforms.lazy.compactMap(\.selector).first + self.customize = customize + } + + func body(content: Content) -> some View { + if let selector { + content + .background( + Group { + // box up content for more accurate `.view` introspection + if SwiftUIViewType.self == ViewType.self { + Color.white + .opacity(0) + .accessibility(hidden: true) + } + } + ) + .background( + IntrospectionAnchorView(id: id) + .frame(width: 0, height: 0) + .accessibility(hidden: true) + ) + .overlay( + IntrospectionView(id: id, selector: { selector($0, scope) }, customize: customize) + .frame(width: 0, height: 0) + .accessibility(hidden: true) + ) + } else { + content + } + } +} + +public protocol PlatformEntity: AnyObject { + associatedtype Base: PlatformEntity + + @_spi(Internals) + var ancestor: Base? { get } + + @_spi(Internals) + var descendants: [Base] { get } + + @_spi(Internals) + func isDescendant(of other: Base) -> Bool +} + +extension PlatformEntity { + @_spi(Internals) + public var ancestor: Base? { nil } + + @_spi(Internals) + public var descendants: [Base] { [] } + + @_spi(Internals) + public func isDescendant(of other: Base) -> Bool { false } +} + +extension PlatformEntity { + @_spi(Internals) + public var ancestors: some Sequence { + sequence(first: self~, next: { $0.ancestor~ }).dropFirst() + } + + @_spi(Internals) + public var allDescendants: some Sequence { + recursiveSequence([self~], children: { $0.descendants~ }).dropFirst() + } + + func nearestCommonAncestor(with other: Base) -> Base? { + var nearestAncestor: Base? = self~ + + while let currentEntity = nearestAncestor, !other.isDescendant(of: currentEntity~) { + nearestAncestor = currentEntity.ancestor~ + } + + return nearestAncestor + } + + func allDescendants(between bottomEntity: Base, and topEntity: Base) -> some Sequence { + self.allDescendants + .lazy + .drop(while: { $0 !== bottomEntity }) + .prefix(while: { $0 !== topEntity }) + } + + func receiver( + ofType type: PlatformSpecificEntity.Type + ) -> PlatformSpecificEntity? { + let frontEntity = self + guard + let backEntity = frontEntity.introspectionAnchorEntity, + let commonAncestor = backEntity.nearestCommonAncestor(with: frontEntity~) + else { + return nil + } + + return commonAncestor + .allDescendants(between: backEntity~, and: frontEntity~) + .filter { !$0.isIntrospectionPlatformEntity } + .compactMap { $0 as? PlatformSpecificEntity } + .first + } + + func ancestor( + ofType type: PlatformSpecificEntity.Type + ) -> PlatformSpecificEntity? { + self.ancestors + .lazy + .filter { !$0.isIntrospectionPlatformEntity } + .compactMap { $0 as? PlatformSpecificEntity } + .first + } +} + +extension PlatformView: PlatformEntity { + @_spi(Internals) + public var ancestor: PlatformView? { + superview + } + + @_spi(Internals) + public var descendants: [PlatformView] { + subviews + } +} + +extension PlatformViewController: PlatformEntity { + @_spi(Internals) + public var ancestor: PlatformViewController? { + parent + } + + @_spi(Internals) + public var descendants: [PlatformViewController] { + children + } + + @_spi(Internals) + public func isDescendant(of other: PlatformViewController) -> Bool { + self.ancestors.contains(other) + } +} + +#if canImport(UIKit) +extension UIPresentationController: PlatformEntity { + public typealias Base = UIPresentationController +} +#elseif canImport(AppKit) +extension NSWindow: PlatformEntity { + public typealias Base = NSWindow +} +#endif diff --git a/Sources/IntrospectableViewType.swift b/Sources/IntrospectableViewType.swift new file mode 100644 index 000000000..5424c602e --- /dev/null +++ b/Sources/IntrospectableViewType.swift @@ -0,0 +1,17 @@ +public protocol IntrospectableViewType { + /// The scope of introspection for this particular view type, i.e. where introspect + /// should look to find the desired target view relative to the applied + /// `.introspect(...)` modifier. + /// + /// While the scope can be overridden by the user in their `.introspect(...)` call, + /// most of the time it's preferable to defer to the view type's own scope, + /// as it guarantees introspection is working as intended by the vendor. + /// + /// Defaults to `.receiver` if left unimplemented, which is a sensible one in + /// most cases if you're looking to implement your own view type. + var scope: IntrospectionScope { get } +} + +extension IntrospectableViewType { + public var scope: IntrospectionScope { .receiver } +} diff --git a/Sources/IntrospectionSelector.swift b/Sources/IntrospectionSelector.swift new file mode 100644 index 000000000..6574054ff --- /dev/null +++ b/Sources/IntrospectionSelector.swift @@ -0,0 +1,73 @@ +@_spi(Advanced) +public struct IntrospectionSelector { + @_spi(Advanced) + public static var `default`: Self { .from(Target.self, selector: { $0 }) } + + @_spi(Advanced) + public static func from(_ entryType: Entry.Type, selector: @escaping (Entry) -> Target?) -> Self { + .init( + receiverSelector: { controller in + controller.as(Entry.Base.self)?.receiver(ofType: Entry.self).flatMap(selector) + }, + ancestorSelector: { controller in + controller.as(Entry.Base.self)?.ancestor(ofType: Entry.self).flatMap(selector) + } + ) + } + + private var receiverSelector: (IntrospectionPlatformViewController) -> Target? + private var ancestorSelector: (IntrospectionPlatformViewController) -> Target? + + private init( + receiverSelector: @escaping (IntrospectionPlatformViewController) -> Target?, + ancestorSelector: @escaping (IntrospectionPlatformViewController) -> Target? + ) { + self.receiverSelector = receiverSelector + self.ancestorSelector = ancestorSelector + } + + @_spi(Advanced) + public func withReceiverSelector(_ selector: @escaping (PlatformViewController) -> Target?) -> Self { + var copy = self + copy.receiverSelector = selector + return copy + } + + @_spi(Advanced) + public func withAncestorSelector(_ selector: @escaping (PlatformViewController) -> Target?) -> Self { + var copy = self + copy.ancestorSelector = selector + return copy + } + + func callAsFunction(_ controller: IntrospectionPlatformViewController, _ scope: IntrospectionScope) -> Target? { + if + scope.contains(.receiver), + let target = receiverSelector(controller) + { + return target + } + if + scope.contains(.ancestor), + let target = ancestorSelector(controller) + { + return target + } + return nil + } +} + +extension PlatformViewController { + func `as`(_ baseType: Base.Type) -> (any PlatformEntity)? { + if Base.self == PlatformView.self { + #if canImport(UIKit) + return viewIfLoaded + #elseif canImport(AppKit) + return isViewLoaded ? view : nil + #endif + } else if Base.self == PlatformViewController.self { + return self + } + return nil + } +} diff --git a/Sources/IntrospectionView.swift b/Sources/IntrospectionView.swift new file mode 100644 index 000000000..fc2304756 --- /dev/null +++ b/Sources/IntrospectionView.swift @@ -0,0 +1,244 @@ +import SwiftUI + +typealias IntrospectionViewID = UUID + +fileprivate enum IntrospectionStore { + static var shared: [IntrospectionViewID: Pair] = [:] + + struct Pair { + weak var controller: IntrospectionPlatformViewController? + weak var anchor: IntrospectionAnchorPlatformViewController? + } +} + +extension PlatformEntity { + var introspectionAnchorEntity: Base? { + if let introspectionController = self as? IntrospectionPlatformViewController { + return IntrospectionStore.shared[introspectionController.id]?.anchor~ + } + if + let view = self as? PlatformView, + let introspectionController = view.introspectionController + { + return IntrospectionStore.shared[introspectionController.id]?.anchor?.view~ + } + return nil + } +} + +/// ⚓️ +struct IntrospectionAnchorView: PlatformViewControllerRepresentable { + #if canImport(UIKit) + typealias UIViewControllerType = IntrospectionAnchorPlatformViewController + #elseif canImport(AppKit) + typealias NSViewControllerType = IntrospectionAnchorPlatformViewController + #endif + + @Binding + private var observed: Void // workaround for state changes not triggering view updates + + let id: IntrospectionViewID + + init(id: IntrospectionViewID) { + self._observed = .constant(()) + self.id = id + } + + func makePlatformViewController(context: Context) -> IntrospectionAnchorPlatformViewController { + IntrospectionAnchorPlatformViewController(id: id) + } + + func updatePlatformViewController(_ controller: IntrospectionAnchorPlatformViewController, context: Context) {} + + static func dismantlePlatformViewController(_ controller: IntrospectionAnchorPlatformViewController, coordinator: Coordinator) {} +} + +final class IntrospectionAnchorPlatformViewController: PlatformViewController { + init(id: IntrospectionViewID) { + super.init(nibName: nil, bundle: nil) + self.isIntrospectionPlatformEntity = true + IntrospectionStore.shared[id, default: .init()].anchor = self + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + #if canImport(UIKit) + override func viewDidLoad() { + super.viewDidLoad() + view.isIntrospectionPlatformEntity = true + } + #elseif canImport(AppKit) + override func loadView() { + view = NSView() + view.isIntrospectionPlatformEntity = true + } + #endif +} + +struct IntrospectionView: PlatformViewControllerRepresentable { + #if canImport(UIKit) + typealias UIViewControllerType = IntrospectionPlatformViewController + #elseif canImport(AppKit) + typealias NSViewControllerType = IntrospectionPlatformViewController + #endif + + final class TargetCache { + weak var target: Target? + } + + @Binding + private var observed: Void // workaround for state changes not triggering view updates + private let id: IntrospectionViewID + private let selector: (IntrospectionPlatformViewController) -> Target? + private let customize: (Target) -> Void + + init( + id: IntrospectionViewID, + selector: @escaping (IntrospectionPlatformViewController) -> Target?, + customize: @escaping (Target) -> Void + ) { + self._observed = .constant(()) + self.id = id + self.selector = selector + self.customize = customize + } + + func makeCoordinator() -> TargetCache { + TargetCache() + } + + func makePlatformViewController(context: Context) -> IntrospectionPlatformViewController { + let controller = IntrospectionPlatformViewController(id: id) { controller in + guard let target = selector(controller) else { + return + } + context.coordinator.target = target + customize(target) + controller.handler = nil + } + + // - Workaround - + // iOS/tvOS 13 sometimes need a nudge on the next run loop. + if #available(iOS 14, tvOS 14, *) {} else { + DispatchQueue.main.async { [weak controller] in + controller?.handler?() + } + } + + return controller + } + + func updatePlatformViewController(_ controller: IntrospectionPlatformViewController, context: Context) { + guard let target = context.coordinator.target ?? selector(controller) else { + return + } + customize(target) + } + + static func dismantlePlatformViewController(_ controller: IntrospectionPlatformViewController, coordinator: Coordinator) { + controller.handler = nil + } +} + +final class IntrospectionPlatformViewController: PlatformViewController { + let id: IntrospectionViewID + var handler: (() -> Void)? = nil + + fileprivate init( + id: IntrospectionViewID, + handler: ((IntrospectionPlatformViewController) -> Void)? + ) { + self.id = id + super.init(nibName: nil, bundle: nil) + self.handler = { [weak self] in + guard let self else { + return + } + handler?(self) + } + self.isIntrospectionPlatformEntity = true + IntrospectionStore.shared[id, default: .init()].controller = self + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + #if canImport(UIKit) + #if os(iOS) + override var preferredStatusBarStyle: UIStatusBarStyle { + parent?.preferredStatusBarStyle ?? super.preferredStatusBarStyle + } + #endif + + override func viewDidLoad() { + super.viewDidLoad() + view.introspectionController = self + view.isIntrospectionPlatformEntity = true + handler?() + } + + override func didMove(toParent parent: UIViewController?) { + super.didMove(toParent: parent) + handler?() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + handler?() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + handler?() + } + #elseif canImport(AppKit) + override func loadView() { + view = NSView() + view.introspectionController = self + view.isIntrospectionPlatformEntity = true + } + + override func viewDidLoad() { + super.viewDidLoad() + handler?() + } + + override func viewDidAppear() { + super.viewDidAppear() + handler?() + } + #endif +} + +import ObjectiveC + +extension PlatformView { + fileprivate var introspectionController: IntrospectionPlatformViewController? { + get { + let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self) + return objc_getAssociatedObject(self, key) as? IntrospectionPlatformViewController + } + set { + let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self) + objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_ASSIGN) + } + } +} + +extension PlatformEntity { + var isIntrospectionPlatformEntity: Bool { + get { + let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self) + return objc_getAssociatedObject(self, key) as? Bool ?? false + } + set { + let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self) + objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} diff --git a/Sources/PlatformVersion.swift b/Sources/PlatformVersion.swift new file mode 100644 index 000000000..93552cf82 --- /dev/null +++ b/Sources/PlatformVersion.swift @@ -0,0 +1,299 @@ +import Foundation + +public enum PlatformVersionCondition { + case past + case current + case future +} + +public protocol PlatformVersion { + var condition: PlatformVersionCondition? { get } +} + +extension PlatformVersion { + public var isCurrent: Bool { + condition == .current + } + + public var isCurrentOrPast: Bool { + condition == .current || condition == .past + } +} + +public struct iOSVersion: PlatformVersion { + public let condition: PlatformVersionCondition? + + public init(condition: () -> PlatformVersionCondition?) { + self.condition = condition() + } +} + +extension iOSVersion { + public static let v13 = iOSVersion { + #if os(iOS) + if #available(iOS 14, *) { + return .past + } + if #available(iOS 13, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v14 = iOSVersion { + #if os(iOS) + if #available(iOS 15, *) { + return .past + } + if #available(iOS 14, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v15 = iOSVersion { + #if os(iOS) + if #available(iOS 16, *) { + return .past + } + if #available(iOS 15, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v16 = iOSVersion { + #if os(iOS) + if #available(iOS 17, *) { + return .past + } + if #available(iOS 16, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v17 = iOSVersion { + #if os(iOS) + if #available(iOS 18, *) { + return .past + } + if #available(iOS 17, *) { + return .current + } + return .future + #else + return nil + #endif + } +} + +public struct tvOSVersion: PlatformVersion { + public let condition: PlatformVersionCondition? + + public init(condition: () -> PlatformVersionCondition?) { + self.condition = condition() + } +} + +extension tvOSVersion { + public static let v13 = tvOSVersion { + #if os(tvOS) + if #available(tvOS 14, *) { + return .past + } + if #available(tvOS 13, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v14 = tvOSVersion { + #if os(tvOS) + if #available(tvOS 15, *) { + return .past + } + if #available(tvOS 14, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v15 = tvOSVersion { + #if os(tvOS) + if #available(tvOS 16, *) { + return .past + } + if #available(tvOS 15, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v16 = tvOSVersion { + #if os(tvOS) + if #available(tvOS 17, *) { + return .past + } + if #available(tvOS 16, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v17 = tvOSVersion { + #if os(tvOS) + if #available(tvOS 18, *) { + return .past + } + if #available(tvOS 17, *) { + return .current + } + return .future + #else + return nil + #endif + } +} + +public struct macOSVersion: PlatformVersion { + public let condition: PlatformVersionCondition? + + public init(condition: () -> PlatformVersionCondition?) { + self.condition = condition() + } +} + +extension macOSVersion { + public static let v10_15 = macOSVersion { + #if os(macOS) + if #available(macOS 11, *) { + return .past + } + if #available(macOS 10.15, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v10_15_4 = macOSVersion { + #if os(macOS) + if #available(macOS 11, *) { + return .past + } + if #available(macOS 10.15.4, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v11 = macOSVersion { + #if os(macOS) + if #available(macOS 12, *) { + return .past + } + if #available(macOS 11, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v12 = macOSVersion { + #if os(macOS) + if #available(macOS 13, *) { + return .past + } + if #available(macOS 12, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v13 = macOSVersion { + #if os(macOS) + if #available(macOS 14, *) { + return .past + } + if #available(macOS 13, *) { + return .current + } + return .future + #else + return nil + #endif + } + + public static let v14 = macOSVersion { + #if os(macOS) + if #available(macOS 15, *) { + return .past + } + if #available(macOS 14, *) { + return .current + } + return .future + #else + return nil + #endif + } +} + +public struct visionOSVersion: PlatformVersion { + public let condition: PlatformVersionCondition? + + public init(condition: () -> PlatformVersionCondition?) { + self.condition = condition() + } +} + +extension visionOSVersion { + public static let v1 = visionOSVersion { + #if os(visionOS) + if #available(visionOS 2, *) { + return .past + } + if #available(visionOS 1, *) { + return .current + } + return .future + #else + return nil + #endif + } +} diff --git a/Sources/PlatformView.swift b/Sources/PlatformView.swift new file mode 100644 index 000000000..0863186fa --- /dev/null +++ b/Sources/PlatformView.swift @@ -0,0 +1,55 @@ +import SwiftUI + +#if canImport(UIKit) +public typealias PlatformView = UIView +#elseif canImport(AppKit) +public typealias PlatformView = NSView +#endif + +#if canImport(UIKit) +public typealias PlatformViewController = UIViewController +#elseif canImport(AppKit) +public typealias PlatformViewController = NSViewController +#endif + +#if canImport(UIKit) +typealias _PlatformViewControllerRepresentable = UIViewControllerRepresentable +#elseif canImport(AppKit) +typealias _PlatformViewControllerRepresentable = NSViewControllerRepresentable +#endif + +protocol PlatformViewControllerRepresentable: _PlatformViewControllerRepresentable { + #if canImport(UIKit) + typealias ViewController = UIViewControllerType + #elseif canImport(AppKit) + typealias ViewController = NSViewControllerType + #endif + + func makePlatformViewController(context: Context) -> ViewController + func updatePlatformViewController(_ controller: ViewController, context: Context) + static func dismantlePlatformViewController(_ controller: ViewController, coordinator: Coordinator) +} + +extension PlatformViewControllerRepresentable { + #if canImport(UIKit) + func makeUIViewController(context: Context) -> ViewController { + makePlatformViewController(context: context) + } + func updateUIViewController(_ controller: ViewController, context: Context) { + updatePlatformViewController(controller, context: context) + } + static func dismantleUIViewController(_ controller: ViewController, coordinator: Coordinator) { + dismantlePlatformViewController(controller, coordinator: coordinator) + } + #elseif canImport(AppKit) + func makeNSViewController(context: Context) -> ViewController { + makePlatformViewController(context: context) + } + func updateNSViewController(_ controller: ViewController, context: Context) { + updatePlatformViewController(controller, context: context) + } + static func dismantleNSViewController(_ controller: ViewController, coordinator: Coordinator) { + dismantlePlatformViewController(controller, coordinator: coordinator) + } + #endif +} diff --git a/Sources/PlatformViewVersion.swift b/Sources/PlatformViewVersion.swift new file mode 100644 index 000000000..3d331363b --- /dev/null +++ b/Sources/PlatformViewVersion.swift @@ -0,0 +1,123 @@ +import SwiftUI + +public struct PlatformViewVersionPredicate { + let selector: IntrospectionSelector? + + private init( + _ versions: [PlatformViewVersion], + matches: (PlatformViewVersion) -> Bool + ) { + if let matchingVersion = versions.first(where: matches) { + self.selector = matchingVersion.selector ?? .default + } else { + self.selector = nil + } + } + + public static func iOS(_ versions: (iOSViewVersion)...) -> Self { + Self(versions, matches: \.isCurrent) + } + + @_spi(Advanced) + public static func iOS(_ versions: PartialRangeFrom>) -> Self { + Self([versions.lowerBound], matches: \.isCurrentOrPast) + } + + public static func tvOS(_ versions: (tvOSViewVersion)...) -> Self { + Self(versions, matches: \.isCurrent) + } + + @_spi(Advanced) + public static func tvOS(_ versions: PartialRangeFrom>) -> Self { + Self([versions.lowerBound], matches: \.isCurrentOrPast) + } + + public static func macOS(_ versions: (macOSViewVersion)...) -> Self { + Self(versions, matches: \.isCurrent) + } + + @_spi(Advanced) + public static func macOS(_ versions: PartialRangeFrom>) -> Self { + Self([versions.lowerBound], matches: \.isCurrentOrPast) + } + + public static func visionOS(_ versions: (visionOSViewVersion)...) -> Self { + Self(versions, matches: \.isCurrent) + } + + @_spi(Advanced) + public static func visionOS(_ versions: PartialRangeFrom>) -> Self { + Self([versions.lowerBound], matches: \.isCurrentOrPast) + } +} + +public typealias iOSViewVersion = + PlatformViewVersion +public typealias tvOSViewVersion = + PlatformViewVersion +public typealias macOSViewVersion = + PlatformViewVersion +public typealias visionOSViewVersion = + PlatformViewVersion + +public enum PlatformViewVersion { + @_spi(Internals) case available(Version, IntrospectionSelector?) + @_spi(Internals) case unavailable + + @_spi(Advanced) public init(for version: Version, selector: IntrospectionSelector? = nil) { + self = .available(version, selector) + } + + @_spi(Advanced) public static func unavailable(file: StaticString = #file, line: UInt = #line) -> Self { + let filePath = file.withUTF8Buffer { String(decoding: $0, as: UTF8.self) } + let fileName = URL(fileURLWithPath: filePath).lastPathComponent + runtimeWarn( + """ + If you're seeing this, someone forgot to mark \(fileName):\(line) as unavailable. + + This won't have any effect, but it should be disallowed altogether. + + Please report it upstream so we can properly fix it by using the following link: + + https://github.com/siteline/swiftui-introspect/issues/new?title=`\(fileName):\(line)`+should+be+marked+unavailable + """ + ) + return .unavailable + } + + private var version: Version? { + if case .available(let version, _) = self { + return version + } else { + return nil + } + } + + fileprivate var selector: IntrospectionSelector? { + if case .available(_, let selector) = self { + return selector + } else { + return nil + } + } + + fileprivate var isCurrent: Bool { + version?.isCurrent ?? false + } + + fileprivate var isCurrentOrPast: Bool { + version?.isCurrentOrPast ?? false + } +} + +// This conformance isn't meant to be used directly by the user, +// it's only to satisfy requirements for forming ranges (e.g. `.v15...`). +extension PlatformViewVersion: Comparable { + public static func == (lhs: Self, rhs: Self) -> Bool { + true + } + + public static func < (lhs: Self, rhs: Self) -> Bool { + true + } +} diff --git a/Sources/RuntimeWarnings.swift b/Sources/RuntimeWarnings.swift new file mode 100644 index 000000000..bee247691 --- /dev/null +++ b/Sources/RuntimeWarnings.swift @@ -0,0 +1,83 @@ +// MIT License +// +// Copyright (c) 2020 Point-Free, Inc. +// +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// 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. + +import Foundation + +@_transparent +@usableFromInline +@inline(__always) +func runtimeWarn( + _ message: @autoclosure () -> String, + category: String? = "SwiftUIIntrospect" +) { + #if DEBUG + let message = message() + let category = category ?? "Runtime Warning" + #if canImport(os) + os_log( + .fault, + dso: dso, + log: OSLog(subsystem: "com.apple.runtime-issues", category: category), + "%@", + message + ) + #else + fputs("\(formatter.string(from: Date())) [\(category)] \(message)\n", stderr) + #endif + #endif +} + +#if DEBUG + #if canImport(os) + import os + + // NB: Xcode runtime warnings offer a much better experience than traditional assertions and + // breakpoints, but Apple provides no means of creating custom runtime warnings ourselves. + // To work around this, we hook into SwiftUI's runtime issue delivery mechanism, instead. + // + // Feedback filed: https://gist.github.com/stephencelis/a8d06383ed6ccde3e5ef5d1b3ad52bbc + @usableFromInline + let dso = { () -> UnsafeMutableRawPointer in + let count = _dyld_image_count() + for i in 0..(lhs: LHS) -> T { + lhs as! T +} + +postfix func ~ (lhs: LHS?) -> T? { + lhs as? T +} + +func recursiveSequence(_ sequence: S, children: @escaping (S.Element) -> S) -> AnySequence { + AnySequence { + var mainIterator = sequence.makeIterator() + // Current iterator, or `nil` if all sequences are exhausted: + var iterator: AnyIterator? + + return AnyIterator { + guard let iterator, let element = iterator.next() else { + if let element = mainIterator.next() { + iterator = recursiveSequence(children(element), children: children).makeIterator() + return element + } + return nil + } + return element + } + } +} diff --git a/Sources/ViewTypes/Button.swift b/Sources/ViewTypes/Button.swift new file mode 100644 index 000000000..363540606 --- /dev/null +++ b/Sources/ViewTypes/Button.swift @@ -0,0 +1,45 @@ +import SwiftUI + +/// An abstract representation of the `Button` type in SwiftUI. +/// +/// ### iOS +/// +/// Not available. +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Button("Action", action: {}) +/// .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSButton +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct ButtonType: IntrospectableViewType {} + +#if !os(iOS) && !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == ButtonType { + public static var button: Self { .init() } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/ColorPicker.swift b/Sources/ViewTypes/ColorPicker.swift new file mode 100644 index 000000000..2c8764834 --- /dev/null +++ b/Sources/ViewTypes/ColorPicker.swift @@ -0,0 +1,86 @@ +import SwiftUI + +/// An abstract representation of the `ColorPicker` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var color = Color.red +/// +/// var body: some View { +/// ColorPicker("Pick a color", selection: $color) +/// .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIColorPicker +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var color = Color.red +/// +/// var body: some View { +/// ColorPicker("Pick a color", selection: $color) +/// .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSColorPicker +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var color = Color.red +/// +/// var body: some View { +/// ColorPicker("Pick a color", selection: $color) +/// .introspect(.colorPicker, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIColorPicker +/// } +/// } +/// } +/// ``` +public struct ColorPickerType: IntrospectableViewType {} + +#if !os(tvOS) +extension IntrospectableViewType where Self == ColorPickerType { + public static var colorPicker: Self { .init() } +} + +#if canImport(UIKit) +@available(iOS 14, *) +extension iOSViewVersion { + @available(*, unavailable, message: "ColorPicker isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +@available(iOS 14, *) +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +@available(macOS 11, *) +extension macOSViewVersion { + @available(*, unavailable, message: "ColorPicker isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/DatePicker.swift b/Sources/ViewTypes/DatePicker.swift new file mode 100644 index 000000000..f298f0252 --- /dev/null +++ b/Sources/ViewTypes/DatePicker.swift @@ -0,0 +1,79 @@ +import SwiftUI + +/// An abstract representation of the `DatePicker` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .introspect(.datePicker, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +public struct DatePickerType: IntrospectableViewType {} + +#if !os(tvOS) +extension IntrospectableViewType where Self == DatePickerType { + public static var datePicker: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/DatePickerWithCompactStyle.swift b/Sources/ViewTypes/DatePickerWithCompactStyle.swift new file mode 100644 index 000000000..120f886b5 --- /dev/null +++ b/Sources/ViewTypes/DatePickerWithCompactStyle.swift @@ -0,0 +1,91 @@ +import SwiftUI + +/// An abstract representation of the `DatePicker` type in SwiftUI, with `.compact` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.compact) +/// .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.compact) +/// .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.compact) +/// .introspect(.datePicker(style: .compact), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +public struct DatePickerWithCompactStyleType: IntrospectableViewType { + public enum Style { + case compact + } +} + +#if !os(tvOS) +extension IntrospectableViewType where Self == DatePickerWithCompactStyleType { + public static func datePicker(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".datePickerStyle(.compact) isn't available on iOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + @available(*, unavailable, message: ".datePickerStyle(.compact) isn't available on macOS 10.15") + public static let v10_15 = Self(for: .v10_15) + public static let v10_15_4 = Self(for: .v10_15_4) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/DatePickerWithFieldStyle.swift b/Sources/ViewTypes/DatePickerWithFieldStyle.swift new file mode 100644 index 000000000..ec1afcced --- /dev/null +++ b/Sources/ViewTypes/DatePickerWithFieldStyle.swift @@ -0,0 +1,52 @@ +import SwiftUI + +/// An abstract representation of the `DatePicker` type in SwiftUI, with `.field` style. +/// +/// ### iOS +/// +/// Not available. +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.field) +/// .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct DatePickerWithFieldStyleType: IntrospectableViewType { + public enum Style { + case field + } +} + +#if !os(iOS) && !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == DatePickerWithFieldStyleType { + public static func datePicker(style: Self.Style) -> Self { .init() } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/DatePickerWithGraphicalStyle.swift b/Sources/ViewTypes/DatePickerWithGraphicalStyle.swift new file mode 100644 index 000000000..dfe1f20f9 --- /dev/null +++ b/Sources/ViewTypes/DatePickerWithGraphicalStyle.swift @@ -0,0 +1,89 @@ +import SwiftUI + +/// An abstract representation of the `DatePicker` type in SwiftUI, with `.graphical` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.graphical) +/// .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.graphical) +/// .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.graphical) +/// .introspect(.datePicker(style: .graphical), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +public struct DatePickerWithGraphicalStyleType: IntrospectableViewType { + public enum Style { + case graphical + } +} + +#if !os(tvOS) +extension IntrospectableViewType where Self == DatePickerWithGraphicalStyleType { + public static func datePicker(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".datePickerStyle(.graphical) isn't available on iOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/DatePickerWithStepperFieldStyle.swift b/Sources/ViewTypes/DatePickerWithStepperFieldStyle.swift new file mode 100644 index 000000000..047fd45ac --- /dev/null +++ b/Sources/ViewTypes/DatePickerWithStepperFieldStyle.swift @@ -0,0 +1,52 @@ +import SwiftUI + +/// An abstract representation of the `DatePicker` type in SwiftUI, with `.stepperField` style. +/// +/// ### iOS +/// +/// Not available. +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.stepperField) +/// .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct DatePickerWithStepperFieldStyleType: IntrospectableViewType { + public enum Style { + case stepperField + } +} + +#if !os(iOS) && !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == DatePickerWithStepperFieldStyleType { + public static func datePicker(style: Self.Style) -> Self { .init() } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/DatePickerWithWheelStyle.swift b/Sources/ViewTypes/DatePickerWithWheelStyle.swift new file mode 100644 index 000000000..b5801db97 --- /dev/null +++ b/Sources/ViewTypes/DatePickerWithWheelStyle.swift @@ -0,0 +1,68 @@ +import SwiftUI + +/// An abstract representation of the `DatePicker` type in SwiftUI, with `.wheel` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.wheel) +/// .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var date = Date() +/// +/// var body: some View { +/// DatePicker("Pick a date", selection: $date) +/// .datePickerStyle(.wheel) +/// .introspect(.datePicker(style: .wheel), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIDatePicker +/// } +/// } +/// } +/// ``` +public struct DatePickerWithWheelStyleType: IntrospectableViewType { + public enum Style { + case wheel + } +} + +#if !os(tvOS) && !os(macOS) +extension IntrospectableViewType where Self == DatePickerWithWheelStyleType { + public static func datePicker(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif +#endif diff --git a/Sources/ViewTypes/Form.swift b/Sources/ViewTypes/Form.swift new file mode 100644 index 000000000..5957522f3 --- /dev/null +++ b/Sources/ViewTypes/Form.swift @@ -0,0 +1,93 @@ +import SwiftUI + +/// An abstract representation of the `Form` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Form { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .introspect(.form, on: .iOS(.v13, .v14, .v15)) { +/// print(type(of: $0)) // UITableView +/// } +/// .introspect(.form, on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Form { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .introspect(.form, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITableView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Form { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .introspect(.form, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct FormType: IntrospectableViewType {} + +#if !os(macOS) +extension IntrospectableViewType where Self == FormType { + public static var form: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif +#endif diff --git a/Sources/ViewTypes/FormWithGroupedStyle.swift b/Sources/ViewTypes/FormWithGroupedStyle.swift new file mode 100644 index 000000000..dec129432 --- /dev/null +++ b/Sources/ViewTypes/FormWithGroupedStyle.swift @@ -0,0 +1,126 @@ +import SwiftUI + +/// An abstract representation of the `Form` type in SwiftUI, with `.grouped` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Form { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .formStyle(.grouped) +/// .introspect(.form(style: .grouped), on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Form { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .formStyle(.grouped) +/// .introspect(.form(style: .grouped), on: .tvOS(.v16, .v17)) { +/// print(type(of: $0)) // UITableView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Form { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .formStyle(.grouped) +/// .introspect(.form(style: .grouped), on: .macOS(.v13, .v14)) { +/// print(type(of: $0)) // NSScrollView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Form { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .formStyle(.grouped) +/// .introspect(.form(style: .grouped), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct FormWithGroupedStyleType: IntrospectableViewType { + public enum Style { + case grouped + } +} + +extension IntrospectableViewType where Self == FormWithGroupedStyleType { + public static func form(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on iOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on iOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on iOS 15") + public static let v15 = Self.unavailable() +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on tvOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on tvOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on tvOS 15") + public static let v15 = Self.unavailable() + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on macOS 11") + public static let v11 = Self.unavailable() + @available(*, unavailable, message: ".formStyle(.grouped) isn't available on macOS 12") + public static let v12 = Self.unavailable() + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/FullScreenCover.swift b/Sources/ViewTypes/FullScreenCover.swift new file mode 100644 index 000000000..3dea536c3 --- /dev/null +++ b/Sources/ViewTypes/FullScreenCover.swift @@ -0,0 +1,106 @@ +import SwiftUI + +/// An abstract representation of `.fullScreenCover` in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .fullScreenCover(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.fullScreenCover, on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPresentationController +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .fullScreenCover(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.fullScreenCover, on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPresentationController +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .fullScreenCover(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.fullScreenCover, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIPresentationController +/// } +/// } +/// } +/// } +/// ``` +public struct FullScreenCoverType: IntrospectableViewType { + public var scope: IntrospectionScope { .ancestor } +} + +#if !os(macOS) +extension IntrospectableViewType where Self == FullScreenCoverType { + public static var fullScreenCover: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".fullScreenCover isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.presentationController) + } +} + +extension tvOSViewVersion { + @available(*, unavailable, message: ".fullScreenCover isn't available on tvOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.presentationController) + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.presentationController) + } +} +#endif +#endif diff --git a/Sources/ViewTypes/List.swift b/Sources/ViewTypes/List.swift new file mode 100644 index 000000000..a770131a6 --- /dev/null +++ b/Sources/ViewTypes/List.swift @@ -0,0 +1,117 @@ +import SwiftUI + +/// An abstract representation of the `List` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .introspect(.list, on: .iOS(.v13, .v14, .v15)) { +/// print(type(of: $0)) // UITableView +/// } +/// .introspect(.list, on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .introspect(.list, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITableView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .introspect(.list, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTableView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .introspect(.list, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct ListType: IntrospectableViewType { + public enum Style { + case plain + } +} + +extension IntrospectableViewType where Self == ListType { + public static var list: Self { .init() } + public static func list(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/ListCell.swift b/Sources/ViewTypes/ListCell.swift new file mode 100644 index 000000000..daa0947d2 --- /dev/null +++ b/Sources/ViewTypes/ListCell.swift @@ -0,0 +1,114 @@ +import SwiftUI + +/// An abstract representation of a `List` cell type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// ForEach(1...3, id: \.self) { int in +/// Text("Item \(int)") +/// .introspect(.listCell, on: .iOS(.v13, .v14, .v15)) { +/// print(type(of: $0)) // UITableViewCell +/// } +/// .introspect(.listCell, on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionViewCell +/// } +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// ForEach(1...3, id: \.self) { int in +/// Text("Item \(int)") +/// .introspect(.listCell, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITableViewCell +/// } +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// ForEach(1...3, id: \.self) { int in +/// Text("Item \(int)") +/// .introspect(.listCell, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTableCellView +/// } +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// ForEach(1...3, id: \.self) { int in +/// Text("Item \(int)") +/// .introspect(.listCell, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionViewCell +/// } +/// } +/// } +/// } +/// } +/// ``` +public struct ListCellType: IntrospectableViewType { + public var scope: IntrospectionScope { .ancestor } +} + +extension IntrospectableViewType where Self == ListCellType { + public static var listCell: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/ListWithBorderedStyle.swift b/Sources/ViewTypes/ListWithBorderedStyle.swift new file mode 100644 index 000000000..294bc6381 --- /dev/null +++ b/Sources/ViewTypes/ListWithBorderedStyle.swift @@ -0,0 +1,56 @@ +import SwiftUI + +/// An abstract representation of the `List` type in SwiftUI, with `.bordered` style. +/// +/// ### iOS +/// +/// Not available. +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.bordered) +/// .introspect(.list(style: .bordered), on: .macOS(.v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTableView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct ListWithBorderedStyleType: IntrospectableViewType { + public enum Style { + case bordered + } +} + +#if !os(iOS) && !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == ListWithBorderedStyleType { + public static func list(style: Self.Style) -> Self { .init() } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + @available(*, unavailable, message: ".listStyle(.insetGrouped) isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + @available(*, unavailable, message: ".listStyle(.insetGrouped) isn't available on macOS 11") + public static let v11 = Self.unavailable() + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/ListWithGroupedStyle.swift b/Sources/ViewTypes/ListWithGroupedStyle.swift new file mode 100644 index 000000000..e1213e73e --- /dev/null +++ b/Sources/ViewTypes/ListWithGroupedStyle.swift @@ -0,0 +1,100 @@ +import SwiftUI + +/// An abstract representation of the `List` type in SwiftUI, with `.grouped` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.grouped) +/// .introspect(.list(style: .grouped), on: .iOS(.v13, .v14, .v15)) { +/// print(type(of: $0)) // UITableView +/// } +/// .introspect(.list(style: .grouped), on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.grouped) +/// .introspect(.list(style: .grouped), on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITableView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.grouped) +/// .introspect(.list(style: .grouped), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct ListWithGroupedStyleType: IntrospectableViewType { + public enum Style { + case grouped + } +} + +#if !os(macOS) +extension IntrospectableViewType where Self == ListWithGroupedStyleType { + public static func list(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif +#endif diff --git a/Sources/ViewTypes/ListWithInsetGroupedStyle.swift b/Sources/ViewTypes/ListWithInsetGroupedStyle.swift new file mode 100644 index 000000000..8b5bd7485 --- /dev/null +++ b/Sources/ViewTypes/ListWithInsetGroupedStyle.swift @@ -0,0 +1,79 @@ +import SwiftUI + +/// An abstract representation of the `List` type in SwiftUI, with `.insetGrouped` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.insetGrouped) +/// .introspect(.list(style: .insetGrouped), on: .iOS(.v14, .v15)) { +/// print(type(of: $0)) // UITableView +/// } +/// .introspect(.list(style: .insetGrouped), on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.insetGrouped) +/// .introspect(.list(style: .insetGrouped), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct ListWithInsetGroupedStyleType: IntrospectableViewType { + public enum Style { + case insetGrouped + } +} + +#if !os(tvOS) && !os(macOS) +extension IntrospectableViewType where Self == ListWithInsetGroupedStyleType { + public static func list(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".listStyle(.insetGrouped) isn't available on iOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif +#endif diff --git a/Sources/ViewTypes/ListWithInsetStyle.swift b/Sources/ViewTypes/ListWithInsetStyle.swift new file mode 100644 index 000000000..1ae24014d --- /dev/null +++ b/Sources/ViewTypes/ListWithInsetStyle.swift @@ -0,0 +1,102 @@ +import SwiftUI + +/// An abstract representation of the `List` type in SwiftUI, with `.inset` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.inset) +/// .introspect(.list(style: .inset), on: .iOS(.v14, .v15)) { +/// print(type(of: $0)) // UITableView +/// } +/// .introspect(.list(style: .inset), on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.inset) +/// .introspect(.list(style: .inset), on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTableView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.inset) +/// .introspect(.list(style: .inset), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct ListWithInsetStyleType: IntrospectableViewType { + public enum Style { + case inset + } +} + +#if !os(tvOS) +extension IntrospectableViewType where Self == ListWithInsetStyleType { + public static func list(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".listStyle(.inset) isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: ".listStyle(.inset) isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/ListWithSidebarStyle.swift b/Sources/ViewTypes/ListWithSidebarStyle.swift new file mode 100644 index 000000000..9a7a1eae9 --- /dev/null +++ b/Sources/ViewTypes/ListWithSidebarStyle.swift @@ -0,0 +1,101 @@ +import SwiftUI + +/// An abstract representation of the `List` type in SwiftUI, with `.sidebar` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.sidebar) +/// .introspect(.list(style: .sidebar), on: .iOS(.v14, .v15)) { +/// print(type(of: $0)) // UITableView +/// } +/// .introspect(.list(style: .sidebar), on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.sidebar) +/// .introspect(.list(style: .sidebar), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTableView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// List { +/// Text("Item 1") +/// Text("Item 2") +/// Text("Item 3") +/// } +/// .listStyle(.sidebar) +/// .introspect(.list(style: .sidebar), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct ListWithSidebarStyleType: IntrospectableViewType { + public enum Style { + case sidebar + } +} + +#if !os(tvOS) +extension IntrospectableViewType where Self == ListWithSidebarStyleType { + public static func list(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".listStyle(.sidebar) isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) +} + +extension iOSViewVersion { + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/Map.swift b/Sources/ViewTypes/Map.swift new file mode 100644 index 000000000..3344e03cf --- /dev/null +++ b/Sources/ViewTypes/Map.swift @@ -0,0 +1,103 @@ +import SwiftUI + +/// An abstract representation of the `Map` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)) +/// +/// var body: some View { +/// Map(coordinateRegion: $region) +/// .introspect(.map, on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // MKMapView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)) +/// +/// var body: some View { +/// Map(coordinateRegion: $region) +/// .introspect(.map, on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // MKMapView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)) +/// +/// var body: some View { +/// Map(coordinateRegion: $region) +/// .introspect(.map, on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // MKMapView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)) +/// +/// var body: some View { +/// Map(coordinateRegion: $region) +/// .introspect(.map, on: .visionOS(.v1)) { +/// print(type(of: $0)) // MKMapView +/// } +/// } +/// } +/// ``` +public struct MapType: IntrospectableViewType {} + +#if canImport(MapKit) +import MapKit + +extension IntrospectableViewType where Self == MapType { + public static var map: Self { .init() } +} + +extension iOSViewVersion { + @available(*, unavailable, message: "Map isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: "Map isn't available on tvOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension macOSViewVersion { + @available(*, unavailable, message: "Map isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif diff --git a/Sources/ViewTypes/NavigationSplitView.swift b/Sources/ViewTypes/NavigationSplitView.swift new file mode 100644 index 000000000..be8b5eacf --- /dev/null +++ b/Sources/ViewTypes/NavigationSplitView.swift @@ -0,0 +1,130 @@ +import SwiftUI + +/// An abstract representation of the `NavigationSplitView` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationSplitView { +/// Text("Root") +/// } detail: { +/// Text("Detail") +/// } +/// .introspect(.navigationSplitView, on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UISplitViewController +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationSplitView { +/// Text("Root") +/// } detail: { +/// Text("Detail") +/// } +/// .introspect(.navigationSplitView, on: .tvOS(.v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationSplitView { +/// Text("Root") +/// } detail: { +/// Text("Detail") +/// } +/// .introspect(.navigationSplitView, on: .macOS(.v13, .v14)) { +/// print(type(of: $0)) // NSSplitView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationSplitView { +/// Text("Root") +/// } detail: { +/// Text("Detail") +/// } +/// .introspect(.navigationSplitView, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISplitViewController +/// } +/// } +/// } +/// ``` +public struct NavigationSplitViewType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == NavigationSplitViewType { + public static var navigationSplitView: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: "NavigationSplitView isn't available on iOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: "NavigationSplitView isn't available on iOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: "NavigationSplitView isn't available on iOS 15") + public static let v15 = Self.unavailable() + + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.splitViewController) + } +} + +extension tvOSViewVersion { + @available(*, unavailable, message: "NavigationSplitView isn't available on tvOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: "NavigationSplitView isn't available on tvOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: "NavigationSplitView isn't available on tvOS 15") + public static let v15 = Self.unavailable() + + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.splitViewController) + } +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: "NavigationSplitView isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + @available(*, unavailable, message: "NavigationSplitView isn't available on macOS 11") + public static let v11 = Self.unavailable() + @available(*, unavailable, message: "NavigationSplitView isn't available on macOS 12") + public static let v12 = Self.unavailable() + + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/NavigationStack.swift b/Sources/ViewTypes/NavigationStack.swift new file mode 100644 index 000000000..69f82ab1e --- /dev/null +++ b/Sources/ViewTypes/NavigationStack.swift @@ -0,0 +1,99 @@ +import SwiftUI + +/// An abstract representation of the `NavigationStack` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationStack { +/// Text("Root") +/// } +/// .introspect(.navigationStack, on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationStack { +/// Text("Root") +/// } +/// .introspect(.navigationStack, on: .tvOS(.v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationStack { +/// Text("Root") +/// } +/// .introspect(.navigationStack, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +public struct NavigationStackType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == NavigationStackType { + public static var navigationStack: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: "NavigationStack isn't available on iOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: "NavigationStack isn't available on iOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: "NavigationStack isn't available on iOS 15") + public static let v15 = Self.unavailable() + + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} + +extension tvOSViewVersion { + @available(*, unavailable, message: "NavigationStack isn't available on tvOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: "NavigationStack isn't available on tvOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: "NavigationStack isn't available on tvOS 15") + public static let v15 = Self.unavailable() + + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} +#endif diff --git a/Sources/ViewTypes/NavigationViewWithColumnsStyle.swift b/Sources/ViewTypes/NavigationViewWithColumnsStyle.swift new file mode 100644 index 000000000..f5fcbbce5 --- /dev/null +++ b/Sources/ViewTypes/NavigationViewWithColumnsStyle.swift @@ -0,0 +1,118 @@ +import SwiftUI + +/// An abstract representation of the `NavigationView` type in SwiftUI, with `.columns` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// } +/// .navigationViewStyle(DoubleColumnNavigationViewStyle()) +/// .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISplitViewController +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// } +/// .navigationViewStyle(DoubleColumnNavigationViewStyle()) +/// .introspect(.navigationView(style: .columns), on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// } +/// .navigationViewStyle(DoubleColumnNavigationViewStyle()) +/// .introspect(.navigationView(style: .columns), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSSplitView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// } +/// .navigationViewStyle(DoubleColumnNavigationViewStyle()) +/// .introspect(.navigationView(style: .columns), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISplitViewController +/// } +/// } +/// } +/// ``` +public struct NavigationViewWithColumnsStyleType: IntrospectableViewType { + public enum Style { + case columns + } +} + +extension IntrospectableViewType where Self == NavigationViewWithColumnsStyleType { + public static func navigationView(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.splitViewController) + } +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.splitViewController) + } +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/NavigationViewWithStackStyle.swift b/Sources/ViewTypes/NavigationViewWithStackStyle.swift new file mode 100644 index 000000000..a2319e8cc --- /dev/null +++ b/Sources/ViewTypes/NavigationViewWithStackStyle.swift @@ -0,0 +1,98 @@ +import SwiftUI + +/// An abstract representation of the `NavigationView` type in SwiftUI, with `.stack` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.navigationView(style: .stack), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.navigationView(style: .stack), on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.navigationView(style: .stack), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +public struct NavigationViewWithStackStyleType: IntrospectableViewType { + public enum Style { + case stack + } +} + +extension IntrospectableViewType where Self == NavigationViewWithStackStyleType { + public static func navigationView(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.navigationController) + } +} +#endif diff --git a/Sources/ViewTypes/PageControl.swift b/Sources/ViewTypes/PageControl.swift new file mode 100644 index 000000000..dff49dade --- /dev/null +++ b/Sources/ViewTypes/PageControl.swift @@ -0,0 +1,87 @@ +import SwiftUI + +/// An abstract representation of the page control type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) +/// } +/// .tabViewStyle(.page(indexDisplayMode: .always)) +/// .introspect(.pageControl, on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPageControl +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) +/// } +/// .tabViewStyle(.page(indexDisplayMode: .always)) +/// .introspect(.pageControl, on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPageControl +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) +/// } +/// .tabViewStyle(.page(indexDisplayMode: .always)) +/// .introspect(.pageControl, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIPageControl +/// } +/// } +/// } +/// ``` +public struct PageControlType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == PageControlType { + public static var pageControl: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".tabViewStyle(.page) isn't available on iOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: ".tabViewStyle(.page) isn't available on tvOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif diff --git a/Sources/ViewTypes/PickerWithMenuStyle.swift b/Sources/ViewTypes/PickerWithMenuStyle.swift new file mode 100644 index 000000000..17f8b05ed --- /dev/null +++ b/Sources/ViewTypes/PickerWithMenuStyle.swift @@ -0,0 +1,57 @@ +import SwiftUI + +/// An abstract representation of the `Picker` type in SwiftUI, with `.menu` style. +/// +/// ### iOS +/// +/// Not available. +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = "1" +/// +/// var body: some View { +/// Picker("Pick a number", selection: $selection) { +/// Text("1").tag("1") +/// Text("2").tag("2") +/// Text("3").tag("3") +/// } +/// .pickerStyle(.menu) +/// .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSPopUpButton +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct PickerWithMenuStyleType: IntrospectableViewType { + public enum Style { + case menu + } +} + +#if !os(iOS) && !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == PickerWithMenuStyleType { + public static func picker(style: Self.Style) -> Self { .init() } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + @available(*, unavailable, message: ".pickerStyle(.menu) isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/PickerWithSegmentedStyle.swift b/Sources/ViewTypes/PickerWithSegmentedStyle.swift new file mode 100644 index 000000000..c6a83d9bc --- /dev/null +++ b/Sources/ViewTypes/PickerWithSegmentedStyle.swift @@ -0,0 +1,122 @@ +import SwiftUI + +/// An abstract representation of the `Picker` type in SwiftUI, with `.segmented` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = "1" +/// +/// var body: some View { +/// Picker("Pick a number", selection: $selection) { +/// Text("1").tag("1") +/// Text("2").tag("2") +/// Text("3").tag("3") +/// } +/// .pickerStyle(.segmented) +/// .introspect(.picker(style: .segmented), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISegmentedControl +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = "1" +/// +/// var body: some View { +/// Picker("Pick a number", selection: $selection) { +/// Text("1").tag("1") +/// Text("2").tag("2") +/// Text("3").tag("3") +/// } +/// .pickerStyle(.segmented) +/// .introspect(.picker(style: .segmented), on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISegmentedControl +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = "1" +/// +/// var body: some View { +/// Picker("Pick a number", selection: $selection) { +/// Text("1").tag("1") +/// Text("2").tag("2") +/// Text("3").tag("3") +/// } +/// .pickerStyle(.segmented) +/// .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSSegmentedControl +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = "1" +/// +/// var body: some View { +/// Picker("Pick a number", selection: $selection) { +/// Text("1").tag("1") +/// Text("2").tag("2") +/// Text("3").tag("3") +/// } +/// .pickerStyle(.segmented) +/// .introspect(.picker(style: .segmented), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISegmentedControl +/// } +/// } +/// } +/// ``` +public struct PickerWithSegmentedStyleType: IntrospectableViewType { + public enum Style { + case segmented + } +} + +extension IntrospectableViewType where Self == PickerWithSegmentedStyleType { + public static func picker(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/PickerWithWheelStyle.swift b/Sources/ViewTypes/PickerWithWheelStyle.swift new file mode 100644 index 000000000..4a826910d --- /dev/null +++ b/Sources/ViewTypes/PickerWithWheelStyle.swift @@ -0,0 +1,76 @@ +import SwiftUI + +/// An abstract representation of the `Picker` type in SwiftUI, with `.wheel` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = "1" +/// +/// var body: some View { +/// Picker("Pick a number", selection: $selection) { +/// Text("1").tag("1") +/// Text("2").tag("2") +/// Text("3").tag("3") +/// } +/// .pickerStyle(.wheel) +/// .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPickerView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = "1" +/// +/// var body: some View { +/// Picker("Pick a number", selection: $selection) { +/// Text("1").tag("1") +/// Text("2").tag("2") +/// Text("3").tag("3") +/// } +/// .pickerStyle(.wheel) +/// .introspect(.picker(style: .wheel), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIPickerView +/// } +/// } +/// } +/// ``` +public struct PickerWithWheelStyleType: IntrospectableViewType { + public enum Style { + case wheel + } +} + +#if !os(tvOS) && !os(macOS) +extension IntrospectableViewType where Self == PickerWithWheelStyleType { + public static func picker(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif +#endif diff --git a/Sources/ViewTypes/Popover.swift b/Sources/ViewTypes/Popover.swift new file mode 100644 index 000000000..bce74f354 --- /dev/null +++ b/Sources/ViewTypes/Popover.swift @@ -0,0 +1,78 @@ +import SwiftUI + +/// An abstract representation of `.popover` in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .popover(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.popover, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPopoverPresentationController +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .popover(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.popover, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIPopoverPresentationController +/// } +/// } +/// } +/// } +/// ``` +public struct PopoverType: IntrospectableViewType { + public var scope: IntrospectionScope { .ancestor } +} + +#if !os(tvOS) && !os(macOS) +extension IntrospectableViewType where Self == PopoverType { + public static var popover: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.popoverPresentationController) + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.popoverPresentationController) + } +} +#endif +#endif diff --git a/Sources/ViewTypes/ProgressViewWithCircularStyle.swift b/Sources/ViewTypes/ProgressViewWithCircularStyle.swift new file mode 100644 index 000000000..c88f9d02a --- /dev/null +++ b/Sources/ViewTypes/ProgressViewWithCircularStyle.swift @@ -0,0 +1,101 @@ +import SwiftUI + +/// An abstract representation of the `ProgressView` type in SwiftUI, with `.circular` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.circular) +/// .introspect(.progressView(style: .circular), on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIActivityIndicatorView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.circular) +/// .introspect(.progressView(style: .circular), on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIActivityIndicatorView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.circular) +/// .introspect(.progressView(style: .circular), on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSProgressIndicator +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.circular) +/// .introspect(.progressView(style: .circular), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIActivityIndicatorView +/// } +/// } +/// } +/// ``` +public struct ProgressViewWithCircularStyleType: IntrospectableViewType { + public enum Style { + case circular + } +} + +extension IntrospectableViewType where Self == ProgressViewWithCircularStyleType { + public static func progressView(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".progressViewStyle(.circular) isn't available on iOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: ".progressViewStyle(.circular) isn't available on tvOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: ".progressViewStyle(.circular) isn't available on macOS 10.15") + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/ProgressViewWithLinearStyle.swift b/Sources/ViewTypes/ProgressViewWithLinearStyle.swift new file mode 100644 index 000000000..cff927107 --- /dev/null +++ b/Sources/ViewTypes/ProgressViewWithLinearStyle.swift @@ -0,0 +1,101 @@ +import SwiftUI + +/// An abstract representation of the `ProgressView` type in SwiftUI, with `.linear` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.linear) +/// .introspect(.progressView(style: .linear), on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIProgressView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.linear) +/// .introspect(.progressView(style: .linear), on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIProgressView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.linear) +/// .introspect(.progressView(style: .linear), on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSProgressIndicator +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ProgressView(value: 0.5) +/// .progressViewStyle(.linear) +/// .introspect(.progressView(style: .linear), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIProgressView +/// } +/// } +/// } +/// ``` +public struct ProgressViewWithLinearStyleType: IntrospectableViewType { + public enum Style { + case linear + } +} + +extension IntrospectableViewType where Self == ProgressViewWithLinearStyleType { + public static func progressView(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".progressViewStyle(.linear) isn't available on iOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: ".progressViewStyle(.linear) isn't available on tvOS 13") + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: ".progressViewStyle(.linear) isn't available on macOS 10.15") + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/ScrollView.swift b/Sources/ViewTypes/ScrollView.swift new file mode 100644 index 000000000..0bd33e626 --- /dev/null +++ b/Sources/ViewTypes/ScrollView.swift @@ -0,0 +1,98 @@ +import SwiftUI + +/// An abstract representation of the `ScrollView` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ScrollView { +/// Text("Item") +/// } +/// .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIScrollView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ScrollView { +/// Text("Item") +/// } +/// .introspect(.scrollView, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIScrollView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ScrollView { +/// Text("Item") +/// } +/// .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSScrollView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// ScrollView { +/// Text("Item") +/// } +/// .introspect(.scrollView, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIScrollView +/// } +/// } +/// } +/// ``` +public struct ScrollViewType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == ScrollViewType { + public static var scrollView: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/SearchField.swift b/Sources/ViewTypes/SearchField.swift new file mode 100644 index 000000000..fb14f3e57 --- /dev/null +++ b/Sources/ViewTypes/SearchField.swift @@ -0,0 +1,113 @@ +import SwiftUI + +/// An abstract representation of the search field displayed via the `.searchable` modifier in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var searchTerm = "" +/// +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// .searchable(text: $searchTerm) +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.searchField, on: .iOS(.v15, .v16, .v17)) { +/// print(type(of: $0)) // UISearchBar +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var searchTerm = "" +/// +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// .searchable(text: $searchTerm) +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.searchField, on: .tvOS(.v15, .v16, .v17)) { +/// print(type(of: $0)) // UISearchBar +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var searchTerm = "" +/// +/// var body: some View { +/// NavigationView { +/// Text("Root") +/// .searchable(text: $searchTerm) +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.searchField, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISearchBar +/// } +/// } +/// } +/// ``` +public struct SearchFieldType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == SearchFieldType { + public static var searchField: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".searchable isn't available on iOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: ".searchable isn't available on iOS 14") + public static let v14 = Self.unavailable() + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UINavigationController.self) { + $0.viewIfLoaded?.allDescendants.lazy.compactMap { $0 as? UISearchBar }.first + } + } +} + +extension tvOSViewVersion { + @available(*, unavailable, message: ".searchable isn't available on tvOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: ".searchable isn't available on tvOS 14") + public static let v14 = Self.unavailable() + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UINavigationController.self) { + $0.viewIfLoaded?.allDescendants.lazy.compactMap { $0 as? UISearchBar }.first + } + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UINavigationController.self) { + $0.viewIfLoaded?.allDescendants.lazy.compactMap { $0 as? UISearchBar }.first + } + } +} +#endif diff --git a/Sources/ViewTypes/SecureField.swift b/Sources/ViewTypes/SecureField.swift new file mode 100644 index 000000000..29cb34c9c --- /dev/null +++ b/Sources/ViewTypes/SecureField.swift @@ -0,0 +1,98 @@ +import SwiftUI + +/// An abstract representation of the `SecureField` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// SecureField("Secure Field", text: $text) +/// .introspect(.secureField, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISecureField +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// SecureField("Secure Field", text: $text) +/// .introspect(.secureField, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISecureField +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// SecureField("Secure Field", text: $text) +/// .introspect(.secureField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSSecureField +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// SecureField("Secure Field", text: $text) +/// .introspect(.secureField, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISecureField +/// } +/// } +/// } +/// ``` +public struct SecureFieldType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == SecureFieldType { + public static var secureField: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/Sheet.swift b/Sources/ViewTypes/Sheet.swift new file mode 100644 index 000000000..a751a9d83 --- /dev/null +++ b/Sources/ViewTypes/Sheet.swift @@ -0,0 +1,121 @@ +import SwiftUI + +/// An abstract representation of `.sheet` in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .sheet(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.sheet, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPresentationController +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .sheet(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.sheet, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIPresentationController +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isPresented = false +/// +/// var body: some View { +/// Button("Present", action: { isPresented = true }) +/// .sheet(isPresented: $isPresented) { +/// Button("Dismiss", action: { isPresented = false }) +/// .introspect(.sheet, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISheetPresentationController +/// } +/// } +/// } +/// } +/// ``` +public struct SheetType: IntrospectableViewType { + public var scope: IntrospectionScope { .ancestor } +} + +#if !os(macOS) +extension IntrospectableViewType where Self == SheetType { + public static var sheet: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.presentationController) + } +} + +#if !os(tvOS) +@available(iOS 15, *) +extension iOSViewVersion { + @_disfavoredOverload + public static let v15 = Self(for: .v15, selector: selector) + @_disfavoredOverload + public static let v16 = Self(for: .v16, selector: selector) + @_disfavoredOverload + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.sheetPresentationController) + } +} + +@available(iOS 15, *) +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.sheetPresentationController) + } +} +#endif + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIViewController.self, selector: \.presentationController) + } +} +#endif +#endif diff --git a/Sources/ViewTypes/SignInWithAppleButton.swift b/Sources/ViewTypes/SignInWithAppleButton.swift new file mode 100644 index 000000000..a11edaa21 --- /dev/null +++ b/Sources/ViewTypes/SignInWithAppleButton.swift @@ -0,0 +1,111 @@ +import SwiftUI + +/// An abstract representation of the `SignInWithAppleButton` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// SignInWithAppleButton(.signIn) { request in +/// request.requestedScopes = [.fullName, .email] +/// } onCompletion: { result in +/// // do something with result +/// } +/// .introspect(.signInWithAppleButton, on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // ASAuthorizationAppleIDButton +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// SignInWithAppleButton(.signIn) { request in +/// request.requestedScopes = [.fullName, .email] +/// } onCompletion: { result in +/// // do something with result +/// } +/// .introspect(.signInWithAppleButton, on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // ASAuthorizationAppleIDButton +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// SignInWithAppleButton(.signIn) { request in +/// request.requestedScopes = [.fullName, .email] +/// } onCompletion: { result in +/// // do something with result +/// } +/// .introspect(.signInWithAppleButton, on: .macOS(.v11, .v12, .v13, .v14),) { +/// print(type(of: $0)) // ASAuthorizationAppleIDButton +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// SignInWithAppleButton(.signIn) { request in +/// request.requestedScopes = [.fullName, .email] +/// } onCompletion: { result in +/// // do something with result +/// } +/// .introspect(.signInWithAppleButton, on: .visionOS(.v1)) { +/// print(type(of: $0)) // ASAuthorizationAppleIDButton +/// } +/// } +/// } +/// ``` +public struct SignInWithAppleButtonType: IntrospectableViewType {} + +#if canImport(AuthenticationServices) +import AuthenticationServices + +extension IntrospectableViewType where Self == SignInWithAppleButtonType { + public static var signInWithAppleButton: Self { .init() } +} + +extension iOSViewVersion { + @available(*, unavailable, message: "SignInWithAppleButton isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: "SignInWithAppleButton isn't available on tvOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension macOSViewVersion { + @available(*, unavailable, message: "SignInWithAppleButton isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif diff --git a/Sources/ViewTypes/Slider.swift b/Sources/ViewTypes/Slider.swift new file mode 100644 index 000000000..1e4b35ff8 --- /dev/null +++ b/Sources/ViewTypes/Slider.swift @@ -0,0 +1,66 @@ +import SwiftUI + +/// An abstract representation of the `Slider` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = 0.5 +/// +/// var body: some View { +/// Slider(value: $selection, in: 0...1) +/// .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISlider +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = 0.5 +/// +/// var body: some View { +/// Slider(value: $selection, in: 0...1) +/// .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSSlider +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct SliderType: IntrospectableViewType {} + +#if !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == SliderType { + public static var slider: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/Stepper.swift b/Sources/ViewTypes/Stepper.swift new file mode 100644 index 000000000..961203512 --- /dev/null +++ b/Sources/ViewTypes/Stepper.swift @@ -0,0 +1,66 @@ +import SwiftUI + +/// An abstract representation of the `Stepper` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = 5 +/// +/// var body: some View { +/// Stepper("Select a number", value: $selection, in: 0...10) +/// .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIStepper +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var selection = 5 +/// +/// var body: some View { +/// Stepper("Select a number", value: $selection, in: 0...10) +/// .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSStepper +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct StepperType: IntrospectableViewType {} + +#if !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == StepperType { + public static var stepper: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/TabView.swift b/Sources/ViewTypes/TabView.swift new file mode 100644 index 000000000..c201ae491 --- /dev/null +++ b/Sources/ViewTypes/TabView.swift @@ -0,0 +1,96 @@ +import SwiftUI + +/// An abstract representation of the `TabView` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Tab 1").tabItem { Text("Tab 1") } +/// Text("Tab 2").tabItem { Text("Tab 2") } +/// } +/// .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITabBarController +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Tab 1").tabItem { Text("Tab 1") } +/// Text("Tab 2").tabItem { Text("Tab 2") } +/// } +/// .introspect(.tabView, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITabBarController +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Tab 1").tabItem { Text("Tab 1") } +/// Text("Tab 2").tabItem { Text("Tab 2") } +/// } +/// .introspect(.tabView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTabView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct TabViewType: IntrospectableViewType {} + +#if !os(visionOS) +extension IntrospectableViewType where Self == TabViewType { + public static var tabView: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.tabBarController) + } +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .default.withAncestorSelector(\.tabBarController) + } +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/TabViewWithPageStyle.swift b/Sources/ViewTypes/TabViewWithPageStyle.swift new file mode 100644 index 000000000..73080ed8b --- /dev/null +++ b/Sources/ViewTypes/TabViewWithPageStyle.swift @@ -0,0 +1,93 @@ +import SwiftUI + +/// An abstract representation of the `TabView` type in SwiftUI, with `.page` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) +/// } +/// .tabViewStyle(.page(indexDisplayMode: .always)) +/// .introspect(.tabView(style: .page), on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) +/// } +/// .tabViewStyle(.page(indexDisplayMode: .always)) +/// .introspect(.tabView(style: .page), on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// TabView { +/// Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) +/// } +/// .tabViewStyle(.page(indexDisplayMode: .always)) +/// .introspect(.tabView(style: .page), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct TabViewWithPageStyleType: IntrospectableViewType { + public enum Style { + case page + } +} + +#if !os(macOS) +extension IntrospectableViewType where Self == TabViewWithPageStyleType { + public static func tabView(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: ".tabViewStyle(.page) isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: ".tabViewStyle(.page) isn't available on tvOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif +#endif diff --git a/Sources/ViewTypes/Table.swift b/Sources/ViewTypes/Table.swift new file mode 100644 index 000000000..1627209e2 --- /dev/null +++ b/Sources/ViewTypes/Table.swift @@ -0,0 +1,137 @@ +import SwiftUI + +/// An abstract representation of the `Table` type in SwiftUI, with any style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// struct Purchase: Identifiable { +/// let id = UUID() +/// let price: Decimal +/// } +/// +/// var body: some View { +/// Table(of: Purchase.self) { +/// TableColumn("Base price") { purchase in +/// Text(purchase.price, format: .currency(code: "USD")) +/// } +/// TableColumn("With 15% tip") { purchase in +/// Text(purchase.price * 1.15, format: .currency(code: "USD")) +/// } +/// TableColumn("With 20% tip") { purchase in +/// Text(purchase.price * 1.2, format: .currency(code: "USD")) +/// } +/// } rows: { +/// TableRow(Purchase(price: 20)) +/// TableRow(Purchase(price: 50)) +/// TableRow(Purchase(price: 75)) +/// } +/// .introspect(.table, on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// struct Purchase: Identifiable { +/// let id = UUID() +/// let price: Decimal +/// } +/// +/// var body: some View { +/// Table(of: Purchase.self) { +/// TableColumn("Base price") { purchase in +/// Text(purchase.price, format: .currency(code: "USD")) +/// } +/// TableColumn("With 15% tip") { purchase in +/// Text(purchase.price * 1.15, format: .currency(code: "USD")) +/// } +/// TableColumn("With 20% tip") { purchase in +/// Text(purchase.price * 1.2, format: .currency(code: "USD")) +/// } +/// } rows: { +/// TableRow(Purchase(price: 20)) +/// TableRow(Purchase(price: 50)) +/// TableRow(Purchase(price: 75)) +/// } +/// .introspect(.table, on: .macOS(.v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTableView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// struct Purchase: Identifiable { +/// let id = UUID() +/// let price: Decimal +/// } +/// +/// var body: some View { +/// Table(of: Purchase.self) { +/// TableColumn("Base price") { purchase in +/// Text(purchase.price, format: .currency(code: "USD")) +/// } +/// TableColumn("With 15% tip") { purchase in +/// Text(purchase.price * 1.15, format: .currency(code: "USD")) +/// } +/// TableColumn("With 20% tip") { purchase in +/// Text(purchase.price * 1.2, format: .currency(code: "USD")) +/// } +/// } rows: { +/// TableRow(Purchase(price: 20)) +/// TableRow(Purchase(price: 50)) +/// TableRow(Purchase(price: 75)) +/// } +/// .introspect(.table, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UICollectionView +/// } +/// } +/// } +/// ``` +public struct TableType: IntrospectableViewType {} + +#if !os(tvOS) +extension IntrospectableViewType where Self == TableType { + public static var table: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: "Table isn't available on iOS 13") + public static let v13 = Self(for: .v13) + @available(*, unavailable, message: "Table isn't available on iOS 14") + public static let v14 = Self(for: .v14) + @available(*, unavailable, message: "Table isn't available on iOS 15") + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: "Table isn't available on macOS 10.15") + public static let v10_15 = Self(for: .v10_15) + @available(*, unavailable, message: "Table isn't available on macOS 11") + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/TextEditor.swift b/Sources/ViewTypes/TextEditor.swift new file mode 100644 index 000000000..a49cbe70d --- /dev/null +++ b/Sources/ViewTypes/TextEditor.swift @@ -0,0 +1,83 @@ +import SwiftUI + +/// An abstract representation of the `TextEditor` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextEditor(text: $text) +/// .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITextView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextEditor(text: $text) +/// .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTextView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextEditor(text: $text) +/// .introspect(.textEditor, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UITextView +/// } +/// } +/// } +/// ``` +public struct TextEditorType: IntrospectableViewType {} + +#if !os(tvOS) +extension IntrospectableViewType where Self == TextEditorType { + public static var textEditor: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: "TextEditor isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: "TextEditor isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/TextField.swift b/Sources/ViewTypes/TextField.swift new file mode 100644 index 000000000..dd9907252 --- /dev/null +++ b/Sources/ViewTypes/TextField.swift @@ -0,0 +1,98 @@ +import SwiftUI + +/// An abstract representation of the `TextField` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text) +/// .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITextField +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text) +/// .introspect(.textField, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UITextField +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text) +/// .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSTextField +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text) +/// .introspect(.textField, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UITextField +/// } +/// } +/// } +/// ``` +public struct TextFieldType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == TextFieldType { + public static var textField: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/TextFieldWithVerticalAxis.swift b/Sources/ViewTypes/TextFieldWithVerticalAxis.swift new file mode 100644 index 000000000..8cf62f9bd --- /dev/null +++ b/Sources/ViewTypes/TextFieldWithVerticalAxis.swift @@ -0,0 +1,116 @@ +import SwiftUI + +/// An abstract representation of the `TextField` type in SwiftUI, with `.vertical` axis. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text, axis: .vertical) +/// .introspect(.textField(axis: .vertical), on: .iOS(.v16, .v17)) { +/// print(type(of: $0)) // UITextView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text, axis: .vertical) +/// .introspect(.textField(axis: .vertical), on: .tvOS(.v16, .v17)) { +/// print(type(of: $0)) // UITextField +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text, axis: .vertical) +/// .introspect(.textField(axis: .vertical), on: .macOS(.v13, .v14)) { +/// print(type(of: $0)) // NSTextField +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var text = "Lorem ipsum" +/// +/// var body: some View { +/// TextField("Text Field", text: $text, axis: .vertical) +/// .introspect(.textField(axis: .vertical), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UITextView +/// } +/// } +/// } +/// ``` +public struct TextFieldWithVerticalAxisType: IntrospectableViewType { + public enum Axis { + case vertical + } +} + +extension IntrospectableViewType where Self == TextFieldWithVerticalAxisType { + public static func textField(axis: Self.Axis) -> Self { .init() } +} + +// MARK: SwiftUI.TextField(..., axis: .vertical) - iOS + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on iOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on iOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on iOS 15") + public static let v15 = Self.unavailable() + + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on tvOS 13") + public static let v13 = Self.unavailable() + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on tvOS 14") + public static let v14 = Self.unavailable() + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on tvOS 15") + public static let v15 = Self.unavailable() + + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on macOS 11") + public static let v11 = Self.unavailable() + @available(*, unavailable, message: "TextField(..., axis: .vertical) isn't available on macOS 12") + public static let v12 = Self.unavailable() + + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/Toggle.swift b/Sources/ViewTypes/Toggle.swift new file mode 100644 index 000000000..0bedff39c --- /dev/null +++ b/Sources/ViewTypes/Toggle.swift @@ -0,0 +1,81 @@ +import SwiftUI + +/// An abstract representation of the `Toggle` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Toggle", isOn: $isOn) +/// .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISwitch +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Toggle", isOn: $isOn) +/// .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSButton +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Toggle", isOn: $isOn) +/// .introspect(.toggle, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISwitch +/// } +/// } +/// } +/// ``` +public struct ToggleType: IntrospectableViewType {} + +#if !os(tvOS) +extension IntrospectableViewType where Self == ToggleType { + public static var toggle: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/ToggleWithButtonStyle.swift b/Sources/ViewTypes/ToggleWithButtonStyle.swift new file mode 100644 index 000000000..f15dcb4c4 --- /dev/null +++ b/Sources/ViewTypes/ToggleWithButtonStyle.swift @@ -0,0 +1,54 @@ +import SwiftUI + +/// An abstract representation of the `Toggle` type in SwiftUI, with `.button` style. +/// +/// ### iOS +/// +/// Not available. +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Toggle", isOn: $isOn) +/// .toggleStyle(.button) +/// .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14)) { +/// print(type(of: $0)) // NSButton +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct ToggleWithButtonStyleType: IntrospectableViewType { + public enum Style { + case button + } +} + +#if !os(iOS) && !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == ToggleWithButtonStyleType { + public static func toggle(style: Self.Style) -> Self { .init() } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + @available(*, unavailable, message: ".toggleStyle(.button) isn't available on macOS 10.15") + public static let v10_15 = Self.unavailable() + @available(*, unavailable, message: ".toggleStyle(.button) isn't available on macOS 11") + public static let v11 = Self.unavailable() + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/ToggleWithCheckboxStyle.swift b/Sources/ViewTypes/ToggleWithCheckboxStyle.swift new file mode 100644 index 000000000..afc93346b --- /dev/null +++ b/Sources/ViewTypes/ToggleWithCheckboxStyle.swift @@ -0,0 +1,52 @@ +import SwiftUI + +/// An abstract representation of the `Toggle` type in SwiftUI, with `.checkbox` style. +/// +/// ### iOS +/// +/// Not available. +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Checkbox", isOn: $isOn) +/// .toggleStyle(.checkbox) +/// .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSButton +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// Not available. +public struct ToggleWithCheckboxStyleType: IntrospectableViewType { + public enum Style { + case checkbox + } +} + +#if !os(iOS) && !os(tvOS) && !os(visionOS) +extension IntrospectableViewType where Self == ToggleWithCheckboxStyleType { + public static func toggle(style: Self.Style) -> Self { .init() } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/ToggleWithSwitchStyle.swift b/Sources/ViewTypes/ToggleWithSwitchStyle.swift new file mode 100644 index 000000000..36c0ebcd6 --- /dev/null +++ b/Sources/ViewTypes/ToggleWithSwitchStyle.swift @@ -0,0 +1,88 @@ +import SwiftUI + +/// An abstract representation of the `Toggle` type in SwiftUI, with `.switch` style. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Switch", isOn: $isOn) +/// .toggleStyle(.switch) +/// .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UISwitch +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// Not available. +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Switch", isOn: $isOn) +/// .toggleStyle(.switch) +/// .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSSwitch +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// @State var isOn = false +/// +/// var body: some View { +/// Toggle("Switch", isOn: $isOn) +/// .toggleStyle(.switch) +/// .introspect(.toggle(style: .switch), on: .visionOS(.v1)) { +/// print(type(of: $0)) // UISwitch +/// } +/// } +/// } +/// ``` +public struct ToggleWithSwitchStyleType: IntrospectableViewType { + public enum Style { + case `switch` + } +} + +#if !os(tvOS) +extension IntrospectableViewType where Self == ToggleWithSwitchStyleType { + public static func toggle(style: Self.Style) -> Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/VideoPlayer.swift b/Sources/ViewTypes/VideoPlayer.swift new file mode 100644 index 000000000..d91038c42 --- /dev/null +++ b/Sources/ViewTypes/VideoPlayer.swift @@ -0,0 +1,97 @@ +import SwiftUI + +/// An abstract representation of the `VideoPlayer` type in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// VideoPlayer(player: AVPlayer(url: URL(string: "https://bit.ly/swswift")!)) +/// .introspect(.videoPlayer, on: .iOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // AVPlayerViewController +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// VideoPlayer(player: AVPlayer(url: URL(string: "https://bit.ly/swswift")!)) +/// .introspect(.videoPlayer, on: .tvOS(.v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // AVPlayerViewController +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// VideoPlayer(player: AVPlayer(url: URL(string: "https://bit.ly/swswift")!)) +/// .introspect(.videoPlayer, on: .macOS(.v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // AVPlayerView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// VideoPlayer(player: AVPlayer(url: URL(string: "https://bit.ly/swswift")!)) +/// .introspect(.videoPlayer, on: .visionOS(.v1)) { +/// print(type(of: $0)) // AVPlayerViewController +/// } +/// } +/// } +/// ``` +public struct VideoPlayerType: IntrospectableViewType {} + +#if canImport(AVKit) +import AVKit + +extension IntrospectableViewType where Self == VideoPlayerType { + public static var videoPlayer: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + @available(*, unavailable, message: "VideoPlayer isn't available on iOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + @available(*, unavailable, message: "VideoPlayer isn't available on tvOS 13") + public static let v13 = Self.unavailable() + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + @available(*, unavailable, message: "VideoPlayer isn't available on macOS 10.15") + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif +#endif diff --git a/Sources/ViewTypes/View.swift b/Sources/ViewTypes/View.swift new file mode 100644 index 000000000..ca2ec07b1 --- /dev/null +++ b/Sources/ViewTypes/View.swift @@ -0,0 +1,102 @@ +import SwiftUI + +/// An abstract representation of a generic SwiftUI view type. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// HStack { +/// Image(systemName: "scribble") +/// Text("Some text") +/// } +/// .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // some subclass of UIView +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// HStack { +/// Image(systemName: "scribble") +/// Text("Some text") +/// } +/// .introspect(.view, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // some subclass of UIView +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// HStack { +/// Image(systemName: "scribble") +/// Text("Some text") +/// } +/// .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // some subclass of NSView +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// HStack { +/// Image(systemName: "scribble") +/// Text("Some text") +/// } +/// .introspect(.view, on: .visionOS(.v1)) { +/// print(type(of: $0)) // some subclass of UIView +/// } +/// } +/// } +/// ``` +public struct ViewType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == ViewType { + public static var view: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15) + public static let v11 = Self(for: .v11) + public static let v12 = Self(for: .v12) + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) +} +#endif diff --git a/Sources/ViewTypes/ViewController.swift b/Sources/ViewTypes/ViewController.swift new file mode 100644 index 000000000..0477064b1 --- /dev/null +++ b/Sources/ViewTypes/ViewController.swift @@ -0,0 +1,94 @@ +import SwiftUI + +/// An abstract representation of the receiving SwiftUI view's view controller, +/// or the closest ancestor view controller if missing. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// .introspect(.viewController, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // some subclass of UIHostingController +/// } +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.viewController, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// .introspect(.viewController, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // some subclass of UIHostingController +/// } +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.viewController, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// Not available. +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// NavigationView { +/// Text("Root").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) +/// .introspect(.viewController, on: .visionOS(.v1)) { +/// print(type(of: $0)) // some subclass of UIHostingController +/// } +/// } +/// .navigationViewStyle(.stack) +/// .introspect(.viewController, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UINavigationController +/// } +/// } +/// } +/// ``` +public struct ViewControllerType: IntrospectableViewType { + public var scope: IntrospectionScope { [.receiver, .ancestor] } +} + +extension IntrospectableViewType where Self == ViewControllerType { + public static var viewController: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13) + public static let v14 = Self(for: .v14) + public static let v15 = Self(for: .v15) + public static let v16 = Self(for: .v16) + public static let v17 = Self(for: .v17) +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1) +} +#endif diff --git a/Sources/ViewTypes/Window.swift b/Sources/ViewTypes/Window.swift new file mode 100644 index 000000000..0cb2e2a48 --- /dev/null +++ b/Sources/ViewTypes/Window.swift @@ -0,0 +1,106 @@ +import SwiftUI + +/// An abstract representation of a view's window in SwiftUI. +/// +/// ### iOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Text("Content") +/// .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIWindow +/// } +/// } +/// } +/// ``` +/// +/// ### tvOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Text("Content") +/// .introspect(.window, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) { +/// print(type(of: $0)) // UIWindow +/// } +/// } +/// } +/// ``` +/// +/// ### macOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Text("Content") +/// .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { +/// print(type(of: $0)) // NSWindow +/// } +/// } +/// } +/// ``` +/// +/// ### visionOS +/// +/// ```swift +/// struct ContentView: View { +/// var body: some View { +/// Text("Content") +/// .introspect(.window, on: .visionOS(.v1)) { +/// print(type(of: $0)) // UIWindow +/// } +/// } +/// } +/// ``` +public struct WindowType: IntrospectableViewType {} + +extension IntrospectableViewType where Self == WindowType { + public static var window: Self { .init() } +} + +#if canImport(UIKit) +extension iOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIView.self, selector: \.window) + } +} + +extension tvOSViewVersion { + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + public static let v15 = Self(for: .v15, selector: selector) + public static let v16 = Self(for: .v16, selector: selector) + public static let v17 = Self(for: .v17, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIView.self, selector: \.window) + } +} + +extension visionOSViewVersion { + public static let v1 = Self(for: .v1, selector: selector) + + private static var selector: IntrospectionSelector { + .from(UIView.self, selector: \.window) + } +} +#elseif canImport(AppKit) +extension macOSViewVersion { + public static let v10_15 = Self(for: .v10_15, selector: selector) + public static let v11 = Self(for: .v11, selector: selector) + public static let v12 = Self(for: .v12, selector: selector) + public static let v13 = Self(for: .v13, selector: selector) + public static let v14 = Self(for: .v14, selector: selector) + + private static var selector: IntrospectionSelector { + .from(NSView.self, selector: \.window) + } +} +#endif diff --git a/Sources/Weak.swift b/Sources/Weak.swift new file mode 100644 index 000000000..f8ec57738 --- /dev/null +++ b/Sources/Weak.swift @@ -0,0 +1,14 @@ +@_spi(Advanced) +@propertyWrapper +public final class Weak { + private weak var _wrappedValue: T? + + public var wrappedValue: T? { + get { _wrappedValue } + set { _wrappedValue = newValue } + } + + public init(wrappedValue: T? = nil) { + self._wrappedValue = wrappedValue + } +} diff --git a/SwiftUIIntrospect.podspec b/SwiftUIIntrospect.podspec new file mode 100644 index 000000000..d947ab799 --- /dev/null +++ b/SwiftUIIntrospect.podspec @@ -0,0 +1,19 @@ +Pod::Spec.new do |spec| + spec.name = 'SwiftUIIntrospect' + spec.version = ENV['LIB_VERSION'] + spec.license = { type: 'MIT' } + spec.homepage = 'https://github.com/siteline/swiftui-introspect' + spec.author = 'David Roman' + spec.summary = 'Introspect underlying UIKit/AppKit components from SwiftUI.' + spec.source = { + git: 'https://github.com/siteline/swiftui-introspect.git', + tag: spec.version + } + + spec.source_files = 'Sources/**/*.swift' + + spec.swift_version = '5.7' + spec.ios.deployment_target = '13.0' + spec.tvos.deployment_target = '13.0' + spec.osx.deployment_target = '10.15' +end diff --git a/SwiftUIIntrospect.xcworkspace/contents.xcworkspacedata b/SwiftUIIntrospect.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..b36954942 --- /dev/null +++ b/SwiftUIIntrospect.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Introspect.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftUIIntrospect.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Introspect.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to SwiftUIIntrospect.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/SwiftUIIntrospect.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SwiftUIIntrospect.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..08de0be8d --- /dev/null +++ b/SwiftUIIntrospect.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/SwiftUIIntrospect.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftUIIntrospect.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..5d7d67f23 --- /dev/null +++ b/SwiftUIIntrospect.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "simulatorstatusmagic", + "kind" : "remoteSourceControl", + "location" : "https://github.com/shinydevelopment/SimulatorStatusMagic", + "state" : { + "revision" : "76095ec820674457a11dc3cc621d8a5c369eea0e", + "version" : "2.7.0" + } + }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", + "state" : { + "revision" : "dc46eeb3928a75390651fac6c1ef7f93ad59a73b", + "version" : "1.11.1" + } + } + ], + "version" : 2 +} diff --git a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect iOS.xcscheme b/SwiftUIIntrospect.xcworkspace/xcshareddata/xcschemes/Introspect.xcscheme similarity index 73% rename from Introspect.xcodeproj/xcshareddata/xcschemes/Introspect iOS.xcscheme rename to SwiftUIIntrospect.xcworkspace/xcshareddata/xcschemes/Introspect.xcscheme index 15086f80c..d3295bf28 100644 --- a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect iOS.xcscheme +++ b/SwiftUIIntrospect.xcworkspace/xcshareddata/xcschemes/Introspect.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "Introspect" + BuildableName = "Introspect" + BlueprintName = "Introspect" + ReferencedContainer = "container:"> @@ -32,10 +32,10 @@ skipped = "NO"> + BlueprintIdentifier = "IntrospectTests" + BuildableName = "IntrospectTests" + BlueprintName = "IntrospectTests" + ReferencedContainer = "container:"> @@ -60,10 +60,10 @@ + BlueprintIdentifier = "Introspect" + BuildableName = "Introspect" + BlueprintName = "Introspect" + ReferencedContainer = "container:"> diff --git a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect tvOS.xcscheme b/SwiftUIIntrospect.xcworkspace/xcshareddata/xcschemes/SwiftUIIntrospect.xcscheme similarity index 66% rename from Introspect.xcodeproj/xcshareddata/xcschemes/Introspect tvOS.xcscheme rename to SwiftUIIntrospect.xcworkspace/xcshareddata/xcschemes/SwiftUIIntrospect.xcscheme index baa2bfc44..be4348bc3 100644 --- a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect tvOS.xcscheme +++ b/SwiftUIIntrospect.xcworkspace/xcshareddata/xcschemes/SwiftUIIntrospect.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "SwiftUIIntrospect" + BuildableName = "SwiftUIIntrospect" + BlueprintName = "SwiftUIIntrospect" + ReferencedContainer = "container:"> @@ -28,16 +28,6 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + BlueprintIdentifier = "SwiftUIIntrospect" + BuildableName = "SwiftUIIntrospect" + BlueprintName = "SwiftUIIntrospect" + ReferencedContainer = "container:"> diff --git a/Tests/LegacyTestsHostApp/LegacyTestsHostApp.swift b/Tests/LegacyTestsHostApp/LegacyTestsHostApp.swift new file mode 100644 index 000000000..226f57886 --- /dev/null +++ b/Tests/LegacyTestsHostApp/LegacyTestsHostApp.swift @@ -0,0 +1,14 @@ +import SwiftUI + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = UIHostingController(rootView: EmptyView()) + window?.makeKeyAndVisible() + return true + } +} diff --git a/Tests/Package.swift b/Tests/Package.swift new file mode 100644 index 000000000..a70085090 --- /dev/null +++ b/Tests/Package.swift @@ -0,0 +1,9 @@ +// swift-tools-version:5.5 + +import PackageDescription + +let package = Package( + name: "Tests", + products: [], + targets: [] +) diff --git a/Tests/Tests.xcodeproj/project.pbxproj b/Tests/Tests.xcodeproj/project.pbxproj new file mode 100644 index 000000000..17ab762e2 --- /dev/null +++ b/Tests/Tests.xcodeproj/project.pbxproj @@ -0,0 +1,2062 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + D503B2AC2A49BFE300027F5F /* VideoPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D503B2AB2A49BFE300027F5F /* VideoPlayerTests.swift */; }; + D50E2F582A2B9EFB00BAFB03 /* LegacyTestsHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50E2F572A2B9EFB00BAFB03 /* LegacyTestsHostApp.swift */; }; + D50E2F5E2A2B9F6600BAFB03 /* ScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50FFE8D2A17E2A400C32641 /* ScrollViewTests.swift */; }; + D50E2F5F2A2B9F6600BAFB03 /* NavigationStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58547F72A1CDD740068ADF4 /* NavigationStackTests.swift */; }; + D50E2F602A2B9F6600BAFB03 /* DatePickerWithGraphicalStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506972A27F32800A628E4 /* DatePickerWithGraphicalStyleTests.swift */; }; + D50E2F612A2B9F6600BAFB03 /* DatePickerWithCompactFieldStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506952A27F0E200A628E4 /* DatePickerWithCompactFieldStyleTests.swift */; }; + D50E2F622A2B9F6600BAFB03 /* ToggleWithCheckboxStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575068F2A27D69600A628E4 /* ToggleWithCheckboxStyleTests.swift */; }; + D50E2F632A2B9F6600BAFB03 /* TabViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119CB2A239F100081F853 /* TabViewTests.swift */; }; + D50E2F642A2B9F6600BAFB03 /* ListWithInsetStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575067F2A27C55600A628E4 /* ListWithInsetStyleTests.swift */; }; + D50E2F652A2B9F6600BAFB03 /* PickerWithMenuStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506792A27BF6C00A628E4 /* PickerWithMenuStyleTests.swift */; }; + D50E2F662A2B9F6600BAFB03 /* DatePickerWithWheelStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506912A27EE4700A628E4 /* DatePickerWithWheelStyleTests.swift */; }; + D50E2F672A2B9F6600BAFB03 /* ListWithInsetGroupedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506812A27C74600A628E4 /* ListWithInsetGroupedStyleTests.swift */; }; + D50E2F682A2B9F6600BAFB03 /* FormWithGroupedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506892A27CE7900A628E4 /* FormWithGroupedStyleTests.swift */; }; + D50E2F692A2B9F6600BAFB03 /* ListWithPlainStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575067B2A27C24600A628E4 /* ListWithPlainStyleTests.swift */; }; + D50E2F6A2A2B9F6600BAFB03 /* TextEditorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C92A239BAC0081F853 /* TextEditorTests.swift */; }; + D50E2F6B2A2B9F6600BAFB03 /* ListWithSidebarStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506832A27C8D400A628E4 /* ListWithSidebarStyleTests.swift */; }; + D50E2F6C2A2B9F6600BAFB03 /* ProgressViewWithLinearStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575069D2A27F80E00A628E4 /* ProgressViewWithLinearStyleTests.swift */; }; + D50E2F6D2A2B9F6600BAFB03 /* ListWithBorderedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506852A27CA4100A628E4 /* ListWithBorderedStyleTests.swift */; }; + D50E2F6E2A2B9F6600BAFB03 /* NavigationViewWithStackStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F8D5EC2A1E7B490054E9AB /* NavigationViewWithStackStyleTests.swift */; }; + D50E2F6F2A2B9F6600BAFB03 /* DatePickerWithStepperFieldStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506932A27EED200A628E4 /* DatePickerWithStepperFieldStyleTests.swift */; }; + D50E2F702A2B9F6600BAFB03 /* TextFieldWithVerticalAxisTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AD0D902A114B98003D8DEC /* TextFieldWithVerticalAxisTests.swift */; }; + D50E2F712A2B9F6600BAFB03 /* SliderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119CF2A23A62C0081F853 /* SliderTests.swift */; }; + D50E2F722A2B9F6600BAFB03 /* ButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D72A23B3B00081F853 /* ButtonTests.swift */; }; + D50E2F732A2B9F6600BAFB03 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55F448C2A1FF209003381E4 /* ListTests.swift */; }; + D50E2F742A2B9F6600BAFB03 /* NavigationSplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58547F92A1D12270068ADF4 /* NavigationSplitViewTests.swift */; }; + D50E2F752A2B9F6600BAFB03 /* NavigationViewWithColumnsStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F8D5EE2A1E87950054E9AB /* NavigationViewWithColumnsStyleTests.swift */; }; + D50E2F762A2B9F6600BAFB03 /* FormTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506872A27CB9800A628E4 /* FormTests.swift */; }; + D50E2F772A2B9F6600BAFB03 /* ToggleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C72A22AC130081F853 /* ToggleTests.swift */; }; + D50E2F782A2B9F6600BAFB03 /* StepperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D12A23A77C0081F853 /* StepperTests.swift */; }; + D50E2F792A2B9F6600BAFB03 /* ColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D92A23B7700081F853 /* ColorPickerTests.swift */; }; + D50E2F7A2A2B9F6600BAFB03 /* ToggleWithButtonStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575068D2A27D4DC00A628E4 /* ToggleWithButtonStyleTests.swift */; }; + D50E2F7B2A2B9F6600BAFB03 /* PlatformVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F0BE6729C0DC4900AD95AB /* PlatformVersionTests.swift */; }; + D50E2F7C2A2B9F6600BAFB03 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58CE15729C621DD0081BFB0 /* TestUtils.swift */; }; + D50E2F7D2A2B9F6600BAFB03 /* PickerWithSegmentedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506772A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift */; }; + D50E2F7E2A2B9F6600BAFB03 /* TabViewWithPageStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119CD2A23A4A70081F853 /* TabViewWithPageStyleTests.swift */; }; + D50E2F7F2A2B9F6600BAFB03 /* DatePickerWithFieldStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506992A27F48D00A628E4 /* DatePickerWithFieldStyleTests.swift */; }; + D50E2F802A2B9F6600BAFB03 /* TableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575069F2A27FC0400A628E4 /* TableTests.swift */; }; + D50E2F812A2B9F6600BAFB03 /* DatePickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D32A23AC100081F853 /* DatePickerTests.swift */; }; + D50E2F822A2B9F6600BAFB03 /* ToggleWithSwitchStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575068B2A27D40500A628E4 /* ToggleWithSwitchStyleTests.swift */; }; + D50E2F832A2B9F6600BAFB03 /* ListCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C32A211B8A0081F853 /* ListCellTests.swift */; }; + D50E2F842A2B9F6600BAFB03 /* SearchFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506A12A281B9C00A628E4 /* SearchFieldTests.swift */; }; + D50E2F852A2B9F6600BAFB03 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C52A227E930081F853 /* ViewTests.swift */; }; + D50E2F862A2B9F6600BAFB03 /* ListWithGroupedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575067D2A27C43400A628E4 /* ListWithGroupedStyleTests.swift */; }; + D50E2F872A2B9F6600BAFB03 /* ProgressViewWithCircularStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575069B2A27F68700A628E4 /* ProgressViewWithCircularStyleTests.swift */; }; + D50E2F882A2B9F6600BAFB03 /* PickerWithWheelStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D52A23AED70081F853 /* PickerWithWheelStyleTests.swift */; }; + D50E2F892A2B9F6600BAFB03 /* TextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B67B832A0D318F007D5D9B /* TextFieldTests.swift */; }; + D50E2F8B2A2B9F6600BAFB03 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D50E2F5C2A2B9F6600BAFB03 /* SwiftUIIntrospect */; }; + D50FFE8E2A17E2A400C32641 /* ScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50FFE8D2A17E2A400C32641 /* ScrollViewTests.swift */; }; + D534D4DC2A4A596200218BFB /* WindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534D4DB2A4A596200218BFB /* WindowTests.swift */; }; + D534D4DD2A4A596200218BFB /* WindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534D4DB2A4A596200218BFB /* WindowTests.swift */; }; + D55F448D2A1FF209003381E4 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55F448C2A1FF209003381E4 /* ListTests.swift */; }; + D57506782A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506772A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift */; }; + D575067A2A27BF6C00A628E4 /* PickerWithMenuStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506792A27BF6C00A628E4 /* PickerWithMenuStyleTests.swift */; }; + D575067C2A27C24600A628E4 /* ListWithPlainStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575067B2A27C24600A628E4 /* ListWithPlainStyleTests.swift */; }; + D575067E2A27C43400A628E4 /* ListWithGroupedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575067D2A27C43400A628E4 /* ListWithGroupedStyleTests.swift */; }; + D57506802A27C55600A628E4 /* ListWithInsetStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575067F2A27C55600A628E4 /* ListWithInsetStyleTests.swift */; }; + D57506822A27C74600A628E4 /* ListWithInsetGroupedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506812A27C74600A628E4 /* ListWithInsetGroupedStyleTests.swift */; }; + D57506842A27C8D400A628E4 /* ListWithSidebarStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506832A27C8D400A628E4 /* ListWithSidebarStyleTests.swift */; }; + D57506862A27CA4100A628E4 /* ListWithBorderedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506852A27CA4100A628E4 /* ListWithBorderedStyleTests.swift */; }; + D57506882A27CB9800A628E4 /* FormTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506872A27CB9800A628E4 /* FormTests.swift */; }; + D575068A2A27CE7900A628E4 /* FormWithGroupedStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506892A27CE7900A628E4 /* FormWithGroupedStyleTests.swift */; }; + D575068C2A27D40500A628E4 /* ToggleWithSwitchStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575068B2A27D40500A628E4 /* ToggleWithSwitchStyleTests.swift */; }; + D575068E2A27D4DC00A628E4 /* ToggleWithButtonStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575068D2A27D4DC00A628E4 /* ToggleWithButtonStyleTests.swift */; }; + D57506902A27D69600A628E4 /* ToggleWithCheckboxStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575068F2A27D69600A628E4 /* ToggleWithCheckboxStyleTests.swift */; }; + D57506922A27EE4700A628E4 /* DatePickerWithWheelStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506912A27EE4700A628E4 /* DatePickerWithWheelStyleTests.swift */; }; + D57506942A27EED200A628E4 /* DatePickerWithStepperFieldStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506932A27EED200A628E4 /* DatePickerWithStepperFieldStyleTests.swift */; }; + D57506962A27F0E200A628E4 /* DatePickerWithCompactFieldStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506952A27F0E200A628E4 /* DatePickerWithCompactFieldStyleTests.swift */; }; + D57506982A27F32800A628E4 /* DatePickerWithGraphicalStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506972A27F32800A628E4 /* DatePickerWithGraphicalStyleTests.swift */; }; + D575069A2A27F48D00A628E4 /* DatePickerWithFieldStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506992A27F48D00A628E4 /* DatePickerWithFieldStyleTests.swift */; }; + D575069C2A27F68700A628E4 /* ProgressViewWithCircularStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575069B2A27F68700A628E4 /* ProgressViewWithCircularStyleTests.swift */; }; + D575069E2A27F80E00A628E4 /* ProgressViewWithLinearStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575069D2A27F80E00A628E4 /* ProgressViewWithLinearStyleTests.swift */; }; + D57506A02A27FC0400A628E4 /* TableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D575069F2A27FC0400A628E4 /* TableTests.swift */; }; + D57506A22A281B9C00A628E4 /* SearchFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57506A12A281B9C00A628E4 /* SearchFieldTests.swift */; }; + D57E66FA2A6956EB0092F43E /* SecureFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57E66F92A6956EB0092F43E /* SecureFieldTests.swift */; }; + D58119C42A211B8A0081F853 /* ListCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C32A211B8A0081F853 /* ListCellTests.swift */; }; + D58119C62A227E930081F853 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C52A227E930081F853 /* ViewTests.swift */; }; + D58119C82A22AC130081F853 /* ToggleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C72A22AC130081F853 /* ToggleTests.swift */; }; + D58119CA2A239BAC0081F853 /* TextEditorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119C92A239BAC0081F853 /* TextEditorTests.swift */; }; + D58119CC2A239F100081F853 /* TabViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119CB2A239F100081F853 /* TabViewTests.swift */; }; + D58119CE2A23A4A70081F853 /* TabViewWithPageStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119CD2A23A4A70081F853 /* TabViewWithPageStyleTests.swift */; }; + D58119D02A23A62C0081F853 /* SliderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119CF2A23A62C0081F853 /* SliderTests.swift */; }; + D58119D22A23A77C0081F853 /* StepperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D12A23A77C0081F853 /* StepperTests.swift */; }; + D58119D42A23AC100081F853 /* DatePickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D32A23AC100081F853 /* DatePickerTests.swift */; }; + D58119D62A23AED70081F853 /* PickerWithWheelStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D52A23AED70081F853 /* PickerWithWheelStyleTests.swift */; }; + D58119D82A23B3B00081F853 /* ButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D72A23B3B00081F853 /* ButtonTests.swift */; }; + D58119DA2A23B7700081F853 /* ColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58119D92A23B7700081F853 /* ColorPickerTests.swift */; }; + D58547F82A1CDD740068ADF4 /* NavigationStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58547F72A1CDD740068ADF4 /* NavigationStackTests.swift */; }; + D58547FA2A1D12270068ADF4 /* NavigationSplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58547F92A1D12270068ADF4 /* NavigationSplitViewTests.swift */; }; + D58CE15629C621B30081BFB0 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D58CE15529C621B30081BFB0 /* SwiftUIIntrospect */; }; + D58CE15829C621DD0081BFB0 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58CE15729C621DD0081BFB0 /* TestUtils.swift */; }; + D58D832E2A66BDD500A203BE /* StatusBarStyleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58D832D2A66BDD500A203BE /* StatusBarStyleUITests.swift */; }; + D58D83382A66C01300A203BE /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D58D83372A66C01300A203BE /* SnapshotTesting */; }; + D58D833A2A66C04600A203BE /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D58D83392A66C04600A203BE /* SwiftUIIntrospect */; }; + D58D83422A66C5EC00A203BE /* UITestsHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58D83412A66C5EC00A203BE /* UITestsHostApp.swift */; }; + D58D83462A66C5EF00A203BE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D58D83452A66C5EF00A203BE /* Assets.xcassets */; }; + D58D83492A66C5EF00A203BE /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D58D83482A66C5EF00A203BE /* Preview Assets.xcassets */; }; + D58D83502A66C67A00A203BE /* TestCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58D834F2A66C67A00A203BE /* TestCases.swift */; }; + D591D1122A9CC2FF00AE05E8 /* WeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D591D1112A9CC2FF00AE05E8 /* WeakTests.swift */; }; + D591D1132A9CC2FF00AE05E8 /* WeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D591D1112A9CC2FF00AE05E8 /* WeakTests.swift */; }; + D591D1142A9CC30B00AE05E8 /* PageControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F26E012A561130001209E6 /* PageControlTests.swift */; }; + D591D1152A9CC30B00AE05E8 /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AAF56E2A502EF000CAFFB6 /* MapTests.swift */; }; + D591D1162A9CC30B00AE05E8 /* ViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F26E032A56E74B001209E6 /* ViewControllerTests.swift */; }; + D591D1172A9CC30B00AE05E8 /* SecureFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57E66F92A6956EB0092F43E /* SecureFieldTests.swift */; }; + D5983E7D2A66FD3F00C50953 /* TestCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58D834F2A66C67A00A203BE /* TestCases.swift */; }; + D5AAF56F2A502EF000CAFFB6 /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AAF56E2A502EF000CAFFB6 /* MapTests.swift */; }; + D5AD0D912A114B98003D8DEC /* TextFieldWithVerticalAxisTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AD0D902A114B98003D8DEC /* TextFieldWithVerticalAxisTests.swift */; }; + D5ADFACC2A4A22AE009494FD /* SheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFACB2A4A22AE009494FD /* SheetTests.swift */; }; + D5ADFACE2A4A3482009494FD /* FullScreenCoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFACD2A4A3482009494FD /* FullScreenCoverTests.swift */; }; + D5ADFAD02A4A3E54009494FD /* PopoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFACF2A4A3E54009494FD /* PopoverTests.swift */; }; + D5ADFAD22A4A41CB009494FD /* SignInWithAppleButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFAD12A4A41CB009494FD /* SignInWithAppleButtonTests.swift */; }; + D5ADFAD32A4A4649009494FD /* SignInWithAppleButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFAD12A4A41CB009494FD /* SignInWithAppleButtonTests.swift */; }; + D5ADFAD42A4A4653009494FD /* FullScreenCoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFACD2A4A3482009494FD /* FullScreenCoverTests.swift */; }; + D5ADFAD52A4A4653009494FD /* SheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFACB2A4A22AE009494FD /* SheetTests.swift */; }; + D5ADFAD62A4A4653009494FD /* VideoPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D503B2AB2A49BFE300027F5F /* VideoPlayerTests.swift */; }; + D5ADFAD72A4A4653009494FD /* PopoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADFACF2A4A3E54009494FD /* PopoverTests.swift */; }; + D5AEC33F2A66F31F0015AC1D /* UITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AEC33E2A66F31F0015AC1D /* UITestCase.swift */; }; + D5AEC3442A66F6470015AC1D /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AEC3412A66F6470015AC1D /* RootView.swift */; }; + D5AEC3452A66F6470015AC1D /* HostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AEC3422A66F6470015AC1D /* HostingController.swift */; }; + D5AEC3462A66F6470015AC1D /* NavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AEC3432A66F6470015AC1D /* NavigationView.swift */; }; + D5AEC3482A66F6AA0015AC1D /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D5AEC3472A66F6AA0015AC1D /* SwiftUIIntrospect */; }; + D5B67B842A0D318F007D5D9B /* TextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B67B832A0D318F007D5D9B /* TextFieldTests.swift */; }; + D5F0BE4D29C0DBE800AD95AB /* TestsHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F0BE4C29C0DBE800AD95AB /* TestsHostApp.swift */; }; + D5F0BE6A29C0DC4900AD95AB /* PlatformVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F0BE6729C0DC4900AD95AB /* PlatformVersionTests.swift */; }; + D5F210812A66DDCB002B8385 /* SimulatorStatusMagic in Frameworks */ = {isa = PBXBuildFile; productRef = D5F210802A66DDCB002B8385 /* SimulatorStatusMagic */; }; + D5F26E022A561130001209E6 /* PageControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F26E012A561130001209E6 /* PageControlTests.swift */; }; + D5F26E042A56E74B001209E6 /* ViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F26E032A56E74B001209E6 /* ViewControllerTests.swift */; }; + D5F8D5ED2A1E7B490054E9AB /* NavigationViewWithStackStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F8D5EC2A1E7B490054E9AB /* NavigationViewWithStackStyleTests.swift */; }; + D5F8D5EF2A1E87950054E9AB /* NavigationViewWithColumnsStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F8D5EE2A1E87950054E9AB /* NavigationViewWithColumnsStyleTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + D50E2F912A2B9FDE00BAFB03 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D5F0BE3F29C0DB9700AD95AB /* Project object */; + proxyType = 1; + remoteGlobalIDString = D50E2F4D2A2B9DEE00BAFB03; + remoteInfo = LegacyTestsHostApp; + }; + D58D834D2A66C61700A203BE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D5F0BE3F29C0DB9700AD95AB /* Project object */; + proxyType = 1; + remoteGlobalIDString = D58D833E2A66C5EC00A203BE; + remoteInfo = UITestsHostApp; + }; + D5F0BE6129C0DC0000AD95AB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D5F0BE3F29C0DB9700AD95AB /* Project object */; + proxyType = 1; + remoteGlobalIDString = D5F0BE4829C0DBE800AD95AB; + remoteInfo = TestsHostApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + D503B2AB2A49BFE300027F5F /* VideoPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerTests.swift; sourceTree = ""; }; + D50E2F552A2B9DEE00BAFB03 /* LegacyTestsHostApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LegacyTestsHostApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D50E2F572A2B9EFB00BAFB03 /* LegacyTestsHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyTestsHostApp.swift; sourceTree = ""; }; + D50E2F902A2B9F6600BAFB03 /* LegacyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LegacyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D50FFE8D2A17E2A400C32641 /* ScrollViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewTests.swift; sourceTree = ""; }; + D534D4DB2A4A596200218BFB /* WindowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowTests.swift; sourceTree = ""; }; + D549D9732A66D876005D4FB5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + D55F448C2A1FF209003381E4 /* ListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTests.swift; sourceTree = ""; }; + D57506772A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerWithSegmentedStyleTests.swift; sourceTree = ""; }; + D57506792A27BF6C00A628E4 /* PickerWithMenuStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerWithMenuStyleTests.swift; sourceTree = ""; }; + D575067B2A27C24600A628E4 /* ListWithPlainStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWithPlainStyleTests.swift; sourceTree = ""; }; + D575067D2A27C43400A628E4 /* ListWithGroupedStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWithGroupedStyleTests.swift; sourceTree = ""; }; + D575067F2A27C55600A628E4 /* ListWithInsetStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWithInsetStyleTests.swift; sourceTree = ""; }; + D57506812A27C74600A628E4 /* ListWithInsetGroupedStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWithInsetGroupedStyleTests.swift; sourceTree = ""; }; + D57506832A27C8D400A628E4 /* ListWithSidebarStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWithSidebarStyleTests.swift; sourceTree = ""; }; + D57506852A27CA4100A628E4 /* ListWithBorderedStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWithBorderedStyleTests.swift; sourceTree = ""; }; + D57506872A27CB9800A628E4 /* FormTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormTests.swift; sourceTree = ""; }; + D57506892A27CE7900A628E4 /* FormWithGroupedStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormWithGroupedStyleTests.swift; sourceTree = ""; }; + D575068B2A27D40500A628E4 /* ToggleWithSwitchStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleWithSwitchStyleTests.swift; sourceTree = ""; }; + D575068D2A27D4DC00A628E4 /* ToggleWithButtonStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleWithButtonStyleTests.swift; sourceTree = ""; }; + D575068F2A27D69600A628E4 /* ToggleWithCheckboxStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleWithCheckboxStyleTests.swift; sourceTree = ""; }; + D57506912A27EE4700A628E4 /* DatePickerWithWheelStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerWithWheelStyleTests.swift; sourceTree = ""; }; + D57506932A27EED200A628E4 /* DatePickerWithStepperFieldStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerWithStepperFieldStyleTests.swift; sourceTree = ""; }; + D57506952A27F0E200A628E4 /* DatePickerWithCompactFieldStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerWithCompactFieldStyleTests.swift; sourceTree = ""; }; + D57506972A27F32800A628E4 /* DatePickerWithGraphicalStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerWithGraphicalStyleTests.swift; sourceTree = ""; }; + D57506992A27F48D00A628E4 /* DatePickerWithFieldStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerWithFieldStyleTests.swift; sourceTree = ""; }; + D575069B2A27F68700A628E4 /* ProgressViewWithCircularStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressViewWithCircularStyleTests.swift; sourceTree = ""; }; + D575069D2A27F80E00A628E4 /* ProgressViewWithLinearStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressViewWithLinearStyleTests.swift; sourceTree = ""; }; + D575069F2A27FC0400A628E4 /* TableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableTests.swift; sourceTree = ""; }; + D57506A12A281B9C00A628E4 /* SearchFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFieldTests.swift; sourceTree = ""; }; + D57E66F92A6956EB0092F43E /* SecureFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureFieldTests.swift; sourceTree = ""; }; + D58119C32A211B8A0081F853 /* ListCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCellTests.swift; sourceTree = ""; }; + D58119C52A227E930081F853 /* ViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTests.swift; sourceTree = ""; }; + D58119C72A22AC130081F853 /* ToggleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleTests.swift; sourceTree = ""; }; + D58119C92A239BAC0081F853 /* TextEditorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEditorTests.swift; sourceTree = ""; }; + D58119CB2A239F100081F853 /* TabViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewTests.swift; sourceTree = ""; }; + D58119CD2A23A4A70081F853 /* TabViewWithPageStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewWithPageStyleTests.swift; sourceTree = ""; }; + D58119CF2A23A62C0081F853 /* SliderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderTests.swift; sourceTree = ""; }; + D58119D12A23A77C0081F853 /* StepperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepperTests.swift; sourceTree = ""; }; + D58119D32A23AC100081F853 /* DatePickerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerTests.swift; sourceTree = ""; }; + D58119D52A23AED70081F853 /* PickerWithWheelStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerWithWheelStyleTests.swift; sourceTree = ""; }; + D58119D72A23B3B00081F853 /* ButtonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTests.swift; sourceTree = ""; }; + D58119D92A23B7700081F853 /* ColorPickerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerTests.swift; sourceTree = ""; }; + D58547F72A1CDD740068ADF4 /* NavigationStackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackTests.swift; sourceTree = ""; }; + D58547F92A1D12270068ADF4 /* NavigationSplitViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewTests.swift; sourceTree = ""; }; + D58CE15729C621DD0081BFB0 /* TestUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; + D58D832B2A66BDD500A203BE /* UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D58D832D2A66BDD500A203BE /* StatusBarStyleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarStyleUITests.swift; sourceTree = ""; }; + D58D833F2A66C5EC00A203BE /* UITestsHostApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UITestsHostApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D58D83412A66C5EC00A203BE /* UITestsHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsHostApp.swift; sourceTree = ""; }; + D58D83452A66C5EF00A203BE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D58D83482A66C5EF00A203BE /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + D58D834F2A66C67A00A203BE /* TestCases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCases.swift; sourceTree = ""; }; + D591D1112A9CC2FF00AE05E8 /* WeakTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakTests.swift; sourceTree = ""; }; + D5AAF56E2A502EF000CAFFB6 /* MapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTests.swift; sourceTree = ""; }; + D5AD0D902A114B98003D8DEC /* TextFieldWithVerticalAxisTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithVerticalAxisTests.swift; sourceTree = ""; }; + D5ADFACB2A4A22AE009494FD /* SheetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetTests.swift; sourceTree = ""; }; + D5ADFACD2A4A3482009494FD /* FullScreenCoverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCoverTests.swift; sourceTree = ""; }; + D5ADFACF2A4A3E54009494FD /* PopoverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverTests.swift; sourceTree = ""; }; + D5ADFAD12A4A41CB009494FD /* SignInWithAppleButtonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignInWithAppleButtonTests.swift; sourceTree = ""; }; + D5AEC33E2A66F31F0015AC1D /* UITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestCase.swift; sourceTree = ""; }; + D5AEC3412A66F6470015AC1D /* RootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; + D5AEC3422A66F6470015AC1D /* HostingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostingController.swift; sourceTree = ""; }; + D5AEC3432A66F6470015AC1D /* NavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = ""; }; + D5B5B03E2A6725500086B9DE /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = UITests.xctestplan; path = "/Users/davdroman/Developer/davdroman/swiftui-introspect/Tests/UITests/UITests.xctestplan"; sourceTree = ""; }; + D5B67B832A0D318F007D5D9B /* TextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldTests.swift; sourceTree = ""; }; + D5F0BE4929C0DBE800AD95AB /* TestsHostApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestsHostApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D5F0BE4C29C0DBE800AD95AB /* TestsHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestsHostApp.swift; sourceTree = ""; }; + D5F0BE5D29C0DC0000AD95AB /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D5F0BE6729C0DC4900AD95AB /* PlatformVersionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlatformVersionTests.swift; sourceTree = ""; }; + D5F26E012A561130001209E6 /* PageControlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlTests.swift; sourceTree = ""; }; + D5F26E032A56E74B001209E6 /* ViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerTests.swift; sourceTree = ""; }; + D5F8D5EC2A1E7B490054E9AB /* NavigationViewWithStackStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewWithStackStyleTests.swift; sourceTree = ""; }; + D5F8D5EE2A1E87950054E9AB /* NavigationViewWithColumnsStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewWithColumnsStyleTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D50E2F502A2B9DEE00BAFB03 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D50E2F8A2A2B9F6600BAFB03 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D50E2F8B2A2B9F6600BAFB03 /* SwiftUIIntrospect in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D58D83282A66BDD500A203BE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D5F210812A66DDCB002B8385 /* SimulatorStatusMagic in Frameworks */, + D58D83382A66C01300A203BE /* SnapshotTesting in Frameworks */, + D58D833A2A66C04600A203BE /* SwiftUIIntrospect in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D58D833C2A66C5EC00A203BE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D5AEC3482A66F6AA0015AC1D /* SwiftUIIntrospect in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D5F0BE4629C0DBE800AD95AB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D5F0BE5A29C0DC0000AD95AB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D58CE15629C621B30081BFB0 /* SwiftUIIntrospect in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D50E2F562A2B9EB600BAFB03 /* LegacyTestsHostApp */ = { + isa = PBXGroup; + children = ( + D50E2F572A2B9EFB00BAFB03 /* LegacyTestsHostApp.swift */, + ); + path = LegacyTestsHostApp; + sourceTree = ""; + }; + D5459AA32A67072F00F0D737 /* __Snapshots__ */ = { + isa = PBXGroup; + children = ( + D5459AA42A67072F00F0D737 /* StatusBarStyleUITests */, + ); + path = __Snapshots__; + sourceTree = ""; + }; + D5459AA42A67072F00F0D737 /* StatusBarStyleUITests */ = { + isa = PBXGroup; + children = ( + ); + path = StatusBarStyleUITests; + sourceTree = ""; + }; + D58D832C2A66BDD500A203BE /* UITests */ = { + isa = PBXGroup; + children = ( + D5B5B03E2A6725500086B9DE /* UITests.xctestplan */, + D58D832D2A66BDD500A203BE /* StatusBarStyleUITests.swift */, + D5AEC33E2A66F31F0015AC1D /* UITestCase.swift */, + D5459AA32A67072F00F0D737 /* __Snapshots__ */, + ); + path = UITests; + sourceTree = ""; + }; + D58D83402A66C5EC00A203BE /* UITestsHostApp */ = { + isa = PBXGroup; + children = ( + D549D9732A66D876005D4FB5 /* Info.plist */, + D58D83412A66C5EC00A203BE /* UITestsHostApp.swift */, + D58D834F2A66C67A00A203BE /* TestCases.swift */, + D5AEC3402A66F6210015AC1D /* StatusBarStyle */, + D58D83452A66C5EF00A203BE /* Assets.xcassets */, + D58D83472A66C5EF00A203BE /* Preview Content */, + ); + path = UITestsHostApp; + sourceTree = ""; + }; + D58D83472A66C5EF00A203BE /* Preview Content */ = { + isa = PBXGroup; + children = ( + D58D83482A66C5EF00A203BE /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + D5AEC3402A66F6210015AC1D /* StatusBarStyle */ = { + isa = PBXGroup; + children = ( + D5AEC3422A66F6470015AC1D /* HostingController.swift */, + D5AEC3432A66F6470015AC1D /* NavigationView.swift */, + D5AEC3412A66F6470015AC1D /* RootView.swift */, + ); + path = StatusBarStyle; + sourceTree = ""; + }; + D5B67B852A0D3193007D5D9B /* ViewTypes */ = { + isa = PBXGroup; + children = ( + D58119D72A23B3B00081F853 /* ButtonTests.swift */, + D58119D92A23B7700081F853 /* ColorPickerTests.swift */, + D58119D32A23AC100081F853 /* DatePickerTests.swift */, + D57506952A27F0E200A628E4 /* DatePickerWithCompactFieldStyleTests.swift */, + D57506992A27F48D00A628E4 /* DatePickerWithFieldStyleTests.swift */, + D57506972A27F32800A628E4 /* DatePickerWithGraphicalStyleTests.swift */, + D57506932A27EED200A628E4 /* DatePickerWithStepperFieldStyleTests.swift */, + D57506912A27EE4700A628E4 /* DatePickerWithWheelStyleTests.swift */, + D57506872A27CB9800A628E4 /* FormTests.swift */, + D57506892A27CE7900A628E4 /* FormWithGroupedStyleTests.swift */, + D5ADFACD2A4A3482009494FD /* FullScreenCoverTests.swift */, + D58119C32A211B8A0081F853 /* ListCellTests.swift */, + D55F448C2A1FF209003381E4 /* ListTests.swift */, + D57506852A27CA4100A628E4 /* ListWithBorderedStyleTests.swift */, + D575067D2A27C43400A628E4 /* ListWithGroupedStyleTests.swift */, + D57506812A27C74600A628E4 /* ListWithInsetGroupedStyleTests.swift */, + D575067F2A27C55600A628E4 /* ListWithInsetStyleTests.swift */, + D575067B2A27C24600A628E4 /* ListWithPlainStyleTests.swift */, + D57506832A27C8D400A628E4 /* ListWithSidebarStyleTests.swift */, + D5AAF56E2A502EF000CAFFB6 /* MapTests.swift */, + D58547F92A1D12270068ADF4 /* NavigationSplitViewTests.swift */, + D58547F72A1CDD740068ADF4 /* NavigationStackTests.swift */, + D5F8D5EE2A1E87950054E9AB /* NavigationViewWithColumnsStyleTests.swift */, + D5F8D5EC2A1E7B490054E9AB /* NavigationViewWithStackStyleTests.swift */, + D5F26E012A561130001209E6 /* PageControlTests.swift */, + D57506792A27BF6C00A628E4 /* PickerWithMenuStyleTests.swift */, + D57506772A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift */, + D58119D52A23AED70081F853 /* PickerWithWheelStyleTests.swift */, + D5ADFACF2A4A3E54009494FD /* PopoverTests.swift */, + D575069B2A27F68700A628E4 /* ProgressViewWithCircularStyleTests.swift */, + D575069D2A27F80E00A628E4 /* ProgressViewWithLinearStyleTests.swift */, + D50FFE8D2A17E2A400C32641 /* ScrollViewTests.swift */, + D57506A12A281B9C00A628E4 /* SearchFieldTests.swift */, + D57E66F92A6956EB0092F43E /* SecureFieldTests.swift */, + D5ADFACB2A4A22AE009494FD /* SheetTests.swift */, + D5ADFAD12A4A41CB009494FD /* SignInWithAppleButtonTests.swift */, + D58119CF2A23A62C0081F853 /* SliderTests.swift */, + D58119D12A23A77C0081F853 /* StepperTests.swift */, + D575069F2A27FC0400A628E4 /* TableTests.swift */, + D58119CB2A239F100081F853 /* TabViewTests.swift */, + D58119CD2A23A4A70081F853 /* TabViewWithPageStyleTests.swift */, + D58119C92A239BAC0081F853 /* TextEditorTests.swift */, + D5B67B832A0D318F007D5D9B /* TextFieldTests.swift */, + D5AD0D902A114B98003D8DEC /* TextFieldWithVerticalAxisTests.swift */, + D58119C72A22AC130081F853 /* ToggleTests.swift */, + D575068D2A27D4DC00A628E4 /* ToggleWithButtonStyleTests.swift */, + D575068F2A27D69600A628E4 /* ToggleWithCheckboxStyleTests.swift */, + D575068B2A27D40500A628E4 /* ToggleWithSwitchStyleTests.swift */, + D503B2AB2A49BFE300027F5F /* VideoPlayerTests.swift */, + D58119C52A227E930081F853 /* ViewTests.swift */, + D5F26E032A56E74B001209E6 /* ViewControllerTests.swift */, + D534D4DB2A4A596200218BFB /* WindowTests.swift */, + ); + path = ViewTypes; + sourceTree = ""; + }; + D5F0BE3E29C0DB9700AD95AB = { + isa = PBXGroup; + children = ( + D5F0BE4B29C0DBE800AD95AB /* TestsHostApp */, + D50E2F562A2B9EB600BAFB03 /* LegacyTestsHostApp */, + D5F0BE5E29C0DC0000AD95AB /* Tests */, + D58D83402A66C5EC00A203BE /* UITestsHostApp */, + D58D832C2A66BDD500A203BE /* UITests */, + D5F0BE4A29C0DBE800AD95AB /* Products */, + D5F0BE7029C0E12300AD95AB /* Frameworks */, + ); + sourceTree = ""; + }; + D5F0BE4A29C0DBE800AD95AB /* Products */ = { + isa = PBXGroup; + children = ( + D5F0BE4929C0DBE800AD95AB /* TestsHostApp.app */, + D5F0BE5D29C0DC0000AD95AB /* Tests.xctest */, + D50E2F552A2B9DEE00BAFB03 /* LegacyTestsHostApp.app */, + D50E2F902A2B9F6600BAFB03 /* LegacyTests.xctest */, + D58D832B2A66BDD500A203BE /* UITests.xctest */, + D58D833F2A66C5EC00A203BE /* UITestsHostApp.app */, + ); + name = Products; + sourceTree = ""; + }; + D5F0BE4B29C0DBE800AD95AB /* TestsHostApp */ = { + isa = PBXGroup; + children = ( + D5F0BE4C29C0DBE800AD95AB /* TestsHostApp.swift */, + ); + path = TestsHostApp; + sourceTree = ""; + }; + D5F0BE5E29C0DC0000AD95AB /* Tests */ = { + isa = PBXGroup; + children = ( + D5B67B852A0D3193007D5D9B /* ViewTypes */, + D5F0BE6729C0DC4900AD95AB /* PlatformVersionTests.swift */, + D58CE15729C621DD0081BFB0 /* TestUtils.swift */, + D591D1112A9CC2FF00AE05E8 /* WeakTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; + D5F0BE7029C0E12300AD95AB /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D50E2F4D2A2B9DEE00BAFB03 /* LegacyTestsHostApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = D50E2F522A2B9DEE00BAFB03 /* Build configuration list for PBXNativeTarget "LegacyTestsHostApp" */; + buildPhases = ( + D50E2F4E2A2B9DEE00BAFB03 /* Sources */, + D50E2F502A2B9DEE00BAFB03 /* Frameworks */, + D50E2F512A2B9DEE00BAFB03 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LegacyTestsHostApp; + productName = TestsHostApp; + productReference = D50E2F552A2B9DEE00BAFB03 /* LegacyTestsHostApp.app */; + productType = "com.apple.product-type.application"; + }; + D50E2F592A2B9F6600BAFB03 /* LegacyTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D50E2F8D2A2B9F6600BAFB03 /* Build configuration list for PBXNativeTarget "LegacyTests" */; + buildPhases = ( + D50E2F5D2A2B9F6600BAFB03 /* Sources */, + D50E2F8A2A2B9F6600BAFB03 /* Frameworks */, + D50E2F8C2A2B9F6600BAFB03 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D50E2F922A2B9FDE00BAFB03 /* PBXTargetDependency */, + ); + name = LegacyTests; + packageProductDependencies = ( + D50E2F5C2A2B9F6600BAFB03 /* SwiftUIIntrospect */, + ); + productName = Tests; + productReference = D50E2F902A2B9F6600BAFB03 /* LegacyTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + D58D832A2A66BDD500A203BE /* UITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D58D83352A66BDD500A203BE /* Build configuration list for PBXNativeTarget "UITests" */; + buildPhases = ( + D58D83272A66BDD500A203BE /* Sources */, + D58D83282A66BDD500A203BE /* Frameworks */, + D58D83292A66BDD500A203BE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D58D834E2A66C61700A203BE /* PBXTargetDependency */, + ); + name = UITests; + packageProductDependencies = ( + D58D83372A66C01300A203BE /* SnapshotTesting */, + D58D83392A66C04600A203BE /* SwiftUIIntrospect */, + D5F210802A66DDCB002B8385 /* SimulatorStatusMagic */, + ); + productName = UITests; + productReference = D58D832B2A66BDD500A203BE /* UITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + D58D833E2A66C5EC00A203BE /* UITestsHostApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = D58D834C2A66C5EF00A203BE /* Build configuration list for PBXNativeTarget "UITestsHostApp" */; + buildPhases = ( + D58D833B2A66C5EC00A203BE /* Sources */, + D58D833C2A66C5EC00A203BE /* Frameworks */, + D58D833D2A66C5EC00A203BE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = UITestsHostApp; + packageProductDependencies = ( + D5AEC3472A66F6AA0015AC1D /* SwiftUIIntrospect */, + ); + productName = UITestsHostApp; + productReference = D58D833F2A66C5EC00A203BE /* UITestsHostApp.app */; + productType = "com.apple.product-type.application"; + }; + D5F0BE4829C0DBE800AD95AB /* TestsHostApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = D5F0BE5829C0DBE900AD95AB /* Build configuration list for PBXNativeTarget "TestsHostApp" */; + buildPhases = ( + D5F0BE4529C0DBE800AD95AB /* Sources */, + D5F0BE4629C0DBE800AD95AB /* Frameworks */, + D5F0BE4729C0DBE800AD95AB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TestsHostApp; + productName = TestsHostApp; + productReference = D5F0BE4929C0DBE800AD95AB /* TestsHostApp.app */; + productType = "com.apple.product-type.application"; + }; + D5F0BE5C29C0DC0000AD95AB /* Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D5F0BE6329C0DC0000AD95AB /* Build configuration list for PBXNativeTarget "Tests" */; + buildPhases = ( + D5F0BE5929C0DC0000AD95AB /* Sources */, + D5F0BE5A29C0DC0000AD95AB /* Frameworks */, + D5F0BE5B29C0DC0000AD95AB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D5F0BE6229C0DC0000AD95AB /* PBXTargetDependency */, + ); + name = Tests; + packageProductDependencies = ( + D58CE15529C621B30081BFB0 /* SwiftUIIntrospect */, + ); + productName = Tests; + productReference = D5F0BE5D29C0DC0000AD95AB /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D5F0BE3F29C0DB9700AD95AB /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + D50E2F592A2B9F6600BAFB03 = { + TestTargetID = D50E2F4D2A2B9DEE00BAFB03; + }; + D58D832A2A66BDD500A203BE = { + CreatedOnToolsVersion = 15.0; + TestTargetID = D58D833E2A66C5EC00A203BE; + }; + D58D833E2A66C5EC00A203BE = { + CreatedOnToolsVersion = 15.0; + }; + D5F0BE4829C0DBE800AD95AB = { + CreatedOnToolsVersion = 14.2; + }; + D5F0BE5C29C0DC0000AD95AB = { + CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1420; + TestTargetID = D5F0BE4829C0DBE800AD95AB; + }; + }; + }; + buildConfigurationList = D5F0BE4229C0DB9700AD95AB /* Build configuration list for PBXProject "Tests" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D5F0BE3E29C0DB9700AD95AB; + packageReferences = ( + D58D83362A66C01300A203BE /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, + D5F2107D2A66DC85002B8385 /* XCRemoteSwiftPackageReference "SimulatorStatusMagic" */, + ); + productRefGroup = D5F0BE4A29C0DBE800AD95AB /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D5F0BE4829C0DBE800AD95AB /* TestsHostApp */, + D5F0BE5C29C0DC0000AD95AB /* Tests */, + D50E2F4D2A2B9DEE00BAFB03 /* LegacyTestsHostApp */, + D50E2F592A2B9F6600BAFB03 /* LegacyTests */, + D58D833E2A66C5EC00A203BE /* UITestsHostApp */, + D58D832A2A66BDD500A203BE /* UITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D50E2F512A2B9DEE00BAFB03 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D50E2F8C2A2B9F6600BAFB03 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D58D83292A66BDD500A203BE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D58D833D2A66C5EC00A203BE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D58D83492A66C5EF00A203BE /* Preview Assets.xcassets in Resources */, + D58D83462A66C5EF00A203BE /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D5F0BE4729C0DBE800AD95AB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D5F0BE5B29C0DC0000AD95AB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D50E2F4E2A2B9DEE00BAFB03 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D50E2F582A2B9EFB00BAFB03 /* LegacyTestsHostApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D50E2F5D2A2B9F6600BAFB03 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D591D1172A9CC30B00AE05E8 /* SecureFieldTests.swift in Sources */, + D5ADFAD42A4A4653009494FD /* FullScreenCoverTests.swift in Sources */, + D50E2F5E2A2B9F6600BAFB03 /* ScrollViewTests.swift in Sources */, + D50E2F5F2A2B9F6600BAFB03 /* NavigationStackTests.swift in Sources */, + D50E2F602A2B9F6600BAFB03 /* DatePickerWithGraphicalStyleTests.swift in Sources */, + D591D1152A9CC30B00AE05E8 /* MapTests.swift in Sources */, + D591D1132A9CC2FF00AE05E8 /* WeakTests.swift in Sources */, + D50E2F612A2B9F6600BAFB03 /* DatePickerWithCompactFieldStyleTests.swift in Sources */, + D50E2F622A2B9F6600BAFB03 /* ToggleWithCheckboxStyleTests.swift in Sources */, + D50E2F632A2B9F6600BAFB03 /* TabViewTests.swift in Sources */, + D50E2F642A2B9F6600BAFB03 /* ListWithInsetStyleTests.swift in Sources */, + D50E2F652A2B9F6600BAFB03 /* PickerWithMenuStyleTests.swift in Sources */, + D534D4DD2A4A596200218BFB /* WindowTests.swift in Sources */, + D50E2F662A2B9F6600BAFB03 /* DatePickerWithWheelStyleTests.swift in Sources */, + D50E2F672A2B9F6600BAFB03 /* ListWithInsetGroupedStyleTests.swift in Sources */, + D5ADFAD32A4A4649009494FD /* SignInWithAppleButtonTests.swift in Sources */, + D50E2F682A2B9F6600BAFB03 /* FormWithGroupedStyleTests.swift in Sources */, + D5ADFAD52A4A4653009494FD /* SheetTests.swift in Sources */, + D50E2F692A2B9F6600BAFB03 /* ListWithPlainStyleTests.swift in Sources */, + D50E2F6A2A2B9F6600BAFB03 /* TextEditorTests.swift in Sources */, + D50E2F6B2A2B9F6600BAFB03 /* ListWithSidebarStyleTests.swift in Sources */, + D50E2F6C2A2B9F6600BAFB03 /* ProgressViewWithLinearStyleTests.swift in Sources */, + D50E2F6D2A2B9F6600BAFB03 /* ListWithBorderedStyleTests.swift in Sources */, + D50E2F6E2A2B9F6600BAFB03 /* NavigationViewWithStackStyleTests.swift in Sources */, + D50E2F6F2A2B9F6600BAFB03 /* DatePickerWithStepperFieldStyleTests.swift in Sources */, + D50E2F702A2B9F6600BAFB03 /* TextFieldWithVerticalAxisTests.swift in Sources */, + D5ADFAD72A4A4653009494FD /* PopoverTests.swift in Sources */, + D50E2F712A2B9F6600BAFB03 /* SliderTests.swift in Sources */, + D50E2F722A2B9F6600BAFB03 /* ButtonTests.swift in Sources */, + D50E2F732A2B9F6600BAFB03 /* ListTests.swift in Sources */, + D50E2F742A2B9F6600BAFB03 /* NavigationSplitViewTests.swift in Sources */, + D50E2F752A2B9F6600BAFB03 /* NavigationViewWithColumnsStyleTests.swift in Sources */, + D50E2F762A2B9F6600BAFB03 /* FormTests.swift in Sources */, + D50E2F772A2B9F6600BAFB03 /* ToggleTests.swift in Sources */, + D50E2F782A2B9F6600BAFB03 /* StepperTests.swift in Sources */, + D50E2F792A2B9F6600BAFB03 /* ColorPickerTests.swift in Sources */, + D50E2F7A2A2B9F6600BAFB03 /* ToggleWithButtonStyleTests.swift in Sources */, + D50E2F7B2A2B9F6600BAFB03 /* PlatformVersionTests.swift in Sources */, + D50E2F7C2A2B9F6600BAFB03 /* TestUtils.swift in Sources */, + D50E2F7D2A2B9F6600BAFB03 /* PickerWithSegmentedStyleTests.swift in Sources */, + D591D1142A9CC30B00AE05E8 /* PageControlTests.swift in Sources */, + D50E2F7E2A2B9F6600BAFB03 /* TabViewWithPageStyleTests.swift in Sources */, + D50E2F7F2A2B9F6600BAFB03 /* DatePickerWithFieldStyleTests.swift in Sources */, + D50E2F802A2B9F6600BAFB03 /* TableTests.swift in Sources */, + D50E2F812A2B9F6600BAFB03 /* DatePickerTests.swift in Sources */, + D50E2F822A2B9F6600BAFB03 /* ToggleWithSwitchStyleTests.swift in Sources */, + D5ADFAD62A4A4653009494FD /* VideoPlayerTests.swift in Sources */, + D50E2F832A2B9F6600BAFB03 /* ListCellTests.swift in Sources */, + D50E2F842A2B9F6600BAFB03 /* SearchFieldTests.swift in Sources */, + D591D1162A9CC30B00AE05E8 /* ViewControllerTests.swift in Sources */, + D50E2F852A2B9F6600BAFB03 /* ViewTests.swift in Sources */, + D50E2F862A2B9F6600BAFB03 /* ListWithGroupedStyleTests.swift in Sources */, + D50E2F872A2B9F6600BAFB03 /* ProgressViewWithCircularStyleTests.swift in Sources */, + D50E2F882A2B9F6600BAFB03 /* PickerWithWheelStyleTests.swift in Sources */, + D50E2F892A2B9F6600BAFB03 /* TextFieldTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D58D83272A66BDD500A203BE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D58D832E2A66BDD500A203BE /* StatusBarStyleUITests.swift in Sources */, + D5AEC33F2A66F31F0015AC1D /* UITestCase.swift in Sources */, + D5983E7D2A66FD3F00C50953 /* TestCases.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D58D833B2A66C5EC00A203BE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D58D83422A66C5EC00A203BE /* UITestsHostApp.swift in Sources */, + D58D83502A66C67A00A203BE /* TestCases.swift in Sources */, + D5AEC3462A66F6470015AC1D /* NavigationView.swift in Sources */, + D5AEC3452A66F6470015AC1D /* HostingController.swift in Sources */, + D5AEC3442A66F6470015AC1D /* RootView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D5F0BE4529C0DBE800AD95AB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D5F0BE4D29C0DBE800AD95AB /* TestsHostApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D5F0BE5929C0DC0000AD95AB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D57E66FA2A6956EB0092F43E /* SecureFieldTests.swift in Sources */, + D50FFE8E2A17E2A400C32641 /* ScrollViewTests.swift in Sources */, + D58547F82A1CDD740068ADF4 /* NavigationStackTests.swift in Sources */, + D57506982A27F32800A628E4 /* DatePickerWithGraphicalStyleTests.swift in Sources */, + D57506962A27F0E200A628E4 /* DatePickerWithCompactFieldStyleTests.swift in Sources */, + D57506902A27D69600A628E4 /* ToggleWithCheckboxStyleTests.swift in Sources */, + D58119CC2A239F100081F853 /* TabViewTests.swift in Sources */, + D57506802A27C55600A628E4 /* ListWithInsetStyleTests.swift in Sources */, + D575067A2A27BF6C00A628E4 /* PickerWithMenuStyleTests.swift in Sources */, + D57506922A27EE4700A628E4 /* DatePickerWithWheelStyleTests.swift in Sources */, + D57506822A27C74600A628E4 /* ListWithInsetGroupedStyleTests.swift in Sources */, + D5ADFACE2A4A3482009494FD /* FullScreenCoverTests.swift in Sources */, + D575068A2A27CE7900A628E4 /* FormWithGroupedStyleTests.swift in Sources */, + D575067C2A27C24600A628E4 /* ListWithPlainStyleTests.swift in Sources */, + D534D4DC2A4A596200218BFB /* WindowTests.swift in Sources */, + D58119CA2A239BAC0081F853 /* TextEditorTests.swift in Sources */, + D57506842A27C8D400A628E4 /* ListWithSidebarStyleTests.swift in Sources */, + D575069E2A27F80E00A628E4 /* ProgressViewWithLinearStyleTests.swift in Sources */, + D57506862A27CA4100A628E4 /* ListWithBorderedStyleTests.swift in Sources */, + D5F8D5ED2A1E7B490054E9AB /* NavigationViewWithStackStyleTests.swift in Sources */, + D591D1122A9CC2FF00AE05E8 /* WeakTests.swift in Sources */, + D57506942A27EED200A628E4 /* DatePickerWithStepperFieldStyleTests.swift in Sources */, + D5AD0D912A114B98003D8DEC /* TextFieldWithVerticalAxisTests.swift in Sources */, + D58119D02A23A62C0081F853 /* SliderTests.swift in Sources */, + D5ADFAD02A4A3E54009494FD /* PopoverTests.swift in Sources */, + D58119D82A23B3B00081F853 /* ButtonTests.swift in Sources */, + D5F26E022A561130001209E6 /* PageControlTests.swift in Sources */, + D55F448D2A1FF209003381E4 /* ListTests.swift in Sources */, + D5AAF56F2A502EF000CAFFB6 /* MapTests.swift in Sources */, + D58547FA2A1D12270068ADF4 /* NavigationSplitViewTests.swift in Sources */, + D5F8D5EF2A1E87950054E9AB /* NavigationViewWithColumnsStyleTests.swift in Sources */, + D57506882A27CB9800A628E4 /* FormTests.swift in Sources */, + D58119C82A22AC130081F853 /* ToggleTests.swift in Sources */, + D58119D22A23A77C0081F853 /* StepperTests.swift in Sources */, + D5ADFAD22A4A41CB009494FD /* SignInWithAppleButtonTests.swift in Sources */, + D58119DA2A23B7700081F853 /* ColorPickerTests.swift in Sources */, + D575068E2A27D4DC00A628E4 /* ToggleWithButtonStyleTests.swift in Sources */, + D5F0BE6A29C0DC4900AD95AB /* PlatformVersionTests.swift in Sources */, + D58CE15829C621DD0081BFB0 /* TestUtils.swift in Sources */, + D57506782A27BBBD00A628E4 /* PickerWithSegmentedStyleTests.swift in Sources */, + D58119CE2A23A4A70081F853 /* TabViewWithPageStyleTests.swift in Sources */, + D5ADFACC2A4A22AE009494FD /* SheetTests.swift in Sources */, + D575069A2A27F48D00A628E4 /* DatePickerWithFieldStyleTests.swift in Sources */, + D503B2AC2A49BFE300027F5F /* VideoPlayerTests.swift in Sources */, + D57506A02A27FC0400A628E4 /* TableTests.swift in Sources */, + D58119D42A23AC100081F853 /* DatePickerTests.swift in Sources */, + D575068C2A27D40500A628E4 /* ToggleWithSwitchStyleTests.swift in Sources */, + D58119C42A211B8A0081F853 /* ListCellTests.swift in Sources */, + D57506A22A281B9C00A628E4 /* SearchFieldTests.swift in Sources */, + D5F26E042A56E74B001209E6 /* ViewControllerTests.swift in Sources */, + D58119C62A227E930081F853 /* ViewTests.swift in Sources */, + D575067E2A27C43400A628E4 /* ListWithGroupedStyleTests.swift in Sources */, + D575069C2A27F68700A628E4 /* ProgressViewWithCircularStyleTests.swift in Sources */, + D58119D62A23AED70081F853 /* PickerWithWheelStyleTests.swift in Sources */, + D5B67B842A0D318F007D5D9B /* TextFieldTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + D50E2F922A2B9FDE00BAFB03 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D50E2F4D2A2B9DEE00BAFB03 /* LegacyTestsHostApp */; + targetProxy = D50E2F912A2B9FDE00BAFB03 /* PBXContainerItemProxy */; + }; + D58D834E2A66C61700A203BE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D58D833E2A66C5EC00A203BE /* UITestsHostApp */; + targetProxy = D58D834D2A66C61700A203BE /* PBXContainerItemProxy */; + }; + D5F0BE6229C0DC0000AD95AB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D5F0BE4829C0DBE800AD95AB /* TestsHostApp */; + targetProxy = D5F0BE6129C0DC0000AD95AB /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + D50E2F532A2B9DEE00BAFB03 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO; + ENABLE_PREVIEWS = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchStoryboardName = ""; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.TestsHostApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6"; + }; + name = Debug; + }; + D50E2F542A2B9DEE00BAFB03 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_PREVIEWS = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchStoryboardName = ""; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.TestsHostApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6"; + }; + name = Release; + }; + D50E2F8E2A2B9F6600BAFB03 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LegacyTestsHostApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/LegacyTestsHostApp"; + "TEST_HOST[sdk=macosx*]" = ""; + }; + name = Debug; + }; + D50E2F8F2A2B9F6600BAFB03 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LegacyTestsHostApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/LegacyTestsHostApp"; + "TEST_HOST[sdk=macosx*]" = ""; + }; + name = Release; + }; + D58D83332A66BDD500A203BE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.UITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = UITestsHostApp; + TVOS_DEPLOYMENT_TARGET = 14.0; + }; + name = Debug; + }; + D58D83342A66BDD500A203BE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.UITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = UITestsHostApp; + TVOS_DEPLOYMENT_TARGET = 14.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D58D834A2A66C5EF00A203BE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_ASSET_PATHS = "\"UITestsHostApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = UITestsHostApp/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.UITestsHostApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D58D834B2A66C5EF00A203BE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = "\"UITestsHostApp/Preview Content\""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_PREVIEWS = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = UITestsHostApp/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.UITestsHostApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D5F0BE4329C0DB9700AD95AB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + DEAD_CODE_STRIPPING = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; + ONLY_ACTIVE_ARCH = YES; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator"; + TARGETED_DEVICE_FAMILY = "1,3,2,6"; + TVOS_DEPLOYMENT_TARGET = 13.0; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Debug; + }; + D5F0BE4429C0DB9700AD95AB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + DEAD_CODE_STRIPPING = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator"; + TARGETED_DEVICE_FAMILY = "1,3,2,6"; + TVOS_DEPLOYMENT_TARGET = 13.0; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Release; + }; + D5F0BE5629C0DBE900AD95AB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO; + ENABLE_PREVIEWS = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchStoryboardName = ""; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.TestsHostApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; + TVOS_DEPLOYMENT_TARGET = 14.0; + }; + name = Debug; + }; + D5F0BE5729C0DBE900AD95AB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_PREVIEWS = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UILaunchStoryboardName = ""; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.TestsHostApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; + TVOS_DEPLOYMENT_TARGET = 14.0; + }; + name = Release; + }; + D5F0BE6429C0DC0000AD95AB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestsHostApp.app/TestsHostApp"; + "TEST_HOST[sdk=macosx*]" = ""; + TVOS_DEPLOYMENT_TARGET = 14.0; + }; + name = Debug; + }; + D5F0BE6529C0DC0000AD95AB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestsHostApp.app/TestsHostApp"; + "TEST_HOST[sdk=macosx*]" = ""; + TVOS_DEPLOYMENT_TARGET = 14.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D50E2F522A2B9DEE00BAFB03 /* Build configuration list for PBXNativeTarget "LegacyTestsHostApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D50E2F532A2B9DEE00BAFB03 /* Debug */, + D50E2F542A2B9DEE00BAFB03 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D50E2F8D2A2B9F6600BAFB03 /* Build configuration list for PBXNativeTarget "LegacyTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D50E2F8E2A2B9F6600BAFB03 /* Debug */, + D50E2F8F2A2B9F6600BAFB03 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D58D83352A66BDD500A203BE /* Build configuration list for PBXNativeTarget "UITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D58D83332A66BDD500A203BE /* Debug */, + D58D83342A66BDD500A203BE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D58D834C2A66C5EF00A203BE /* Build configuration list for PBXNativeTarget "UITestsHostApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D58D834A2A66C5EF00A203BE /* Debug */, + D58D834B2A66C5EF00A203BE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D5F0BE4229C0DB9700AD95AB /* Build configuration list for PBXProject "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5F0BE4329C0DB9700AD95AB /* Debug */, + D5F0BE4429C0DB9700AD95AB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D5F0BE5829C0DBE900AD95AB /* Build configuration list for PBXNativeTarget "TestsHostApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5F0BE5629C0DBE900AD95AB /* Debug */, + D5F0BE5729C0DBE900AD95AB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D5F0BE6329C0DC0000AD95AB /* Build configuration list for PBXNativeTarget "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5F0BE6429C0DC0000AD95AB /* Debug */, + D5F0BE6529C0DC0000AD95AB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + D58D83362A66C01300A203BE /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.11.1; + }; + }; + D5F2107D2A66DC85002B8385 /* XCRemoteSwiftPackageReference "SimulatorStatusMagic" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/shinydevelopment/SimulatorStatusMagic"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.7.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + D50E2F5C2A2B9F6600BAFB03 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + productName = SwiftUIIntrospect; + }; + D58CE15529C621B30081BFB0 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + productName = SwiftUIIntrospect; + }; + D58D83372A66C01300A203BE /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = D58D83362A66C01300A203BE /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + D58D83392A66C04600A203BE /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + productName = SwiftUIIntrospect; + }; + D5AEC3472A66F6AA0015AC1D /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + productName = SwiftUIIntrospect; + }; + D5F210802A66DDCB002B8385 /* SimulatorStatusMagic */ = { + isa = XCSwiftPackageProductDependency; + package = D5F2107D2A66DC85002B8385 /* XCRemoteSwiftPackageReference "SimulatorStatusMagic" */; + productName = SimulatorStatusMagic; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = D5F0BE3F29C0DB9700AD95AB /* Project object */; +} diff --git a/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Tests/Tests.xcodeproj/xcshareddata/xcschemes/LegacySwiftUIIntrospectTests.xcscheme b/Tests/Tests.xcodeproj/xcshareddata/xcschemes/LegacySwiftUIIntrospectTests.xcscheme new file mode 100644 index 000000000..d0c283ff7 --- /dev/null +++ b/Tests/Tests.xcodeproj/xcshareddata/xcschemes/LegacySwiftUIIntrospectTests.xcscheme @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Tests.xcodeproj/xcshareddata/xcschemes/SwiftUIIntrospectTests.xcscheme b/Tests/Tests.xcodeproj/xcshareddata/xcschemes/SwiftUIIntrospectTests.xcscheme new file mode 100644 index 000000000..ae540df31 --- /dev/null +++ b/Tests/Tests.xcodeproj/xcshareddata/xcschemes/SwiftUIIntrospectTests.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Tests.xcodeproj/xcshareddata/xcschemes/SwiftUIIntrospectUITests.xcscheme b/Tests/Tests.xcodeproj/xcshareddata/xcschemes/SwiftUIIntrospectUITests.xcscheme new file mode 100644 index 000000000..2745a9788 --- /dev/null +++ b/Tests/Tests.xcodeproj/xcshareddata/xcschemes/SwiftUIIntrospectUITests.xcscheme @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Tests/PlatformVersionTests.swift b/Tests/Tests/PlatformVersionTests.swift new file mode 100644 index 000000000..7a0bb953e --- /dev/null +++ b/Tests/Tests/PlatformVersionTests.swift @@ -0,0 +1,256 @@ +import SwiftUIIntrospect +import XCTest + +final class PlatformVersionTests: XCTestCase { + func test_iOS_isCurrent() { + #if os(iOS) + if #available(iOS 17, *) { + XCTAssertEqual(iOSVersion.v17.isCurrent, true) + XCTAssertEqual(iOSVersion.v16.isCurrent, false) + XCTAssertEqual(iOSVersion.v15.isCurrent, false) + XCTAssertEqual(iOSVersion.v14.isCurrent, false) + XCTAssertEqual(iOSVersion.v13.isCurrent, false) + } else if #available(iOS 16, *) { + XCTAssertEqual(iOSVersion.v17.isCurrent, false) + XCTAssertEqual(iOSVersion.v16.isCurrent, true) + XCTAssertEqual(iOSVersion.v15.isCurrent, false) + XCTAssertEqual(iOSVersion.v14.isCurrent, false) + XCTAssertEqual(iOSVersion.v13.isCurrent, false) + } else if #available(iOS 15, *) { + XCTAssertEqual(iOSVersion.v17.isCurrent, false) + XCTAssertEqual(iOSVersion.v16.isCurrent, false) + XCTAssertEqual(iOSVersion.v15.isCurrent, true) + XCTAssertEqual(iOSVersion.v14.isCurrent, false) + XCTAssertEqual(iOSVersion.v13.isCurrent, false) + } else if #available(iOS 14, *) { + XCTAssertEqual(iOSVersion.v17.isCurrent, false) + XCTAssertEqual(iOSVersion.v16.isCurrent, false) + XCTAssertEqual(iOSVersion.v15.isCurrent, false) + XCTAssertEqual(iOSVersion.v14.isCurrent, true) + XCTAssertEqual(iOSVersion.v13.isCurrent, false) + } else if #available(iOS 13, *) { + XCTAssertEqual(iOSVersion.v17.isCurrent, false) + XCTAssertEqual(iOSVersion.v16.isCurrent, false) + XCTAssertEqual(iOSVersion.v15.isCurrent, false) + XCTAssertEqual(iOSVersion.v14.isCurrent, false) + XCTAssertEqual(iOSVersion.v13.isCurrent, true) + } + #else + XCTAssertEqual(iOSVersion.v17.isCurrent, false) + XCTAssertEqual(iOSVersion.v16.isCurrent, false) + XCTAssertEqual(iOSVersion.v15.isCurrent, false) + XCTAssertEqual(iOSVersion.v14.isCurrent, false) + XCTAssertEqual(iOSVersion.v13.isCurrent, false) + #endif + } + + func test_iOS_isCurrentOrPast() { + #if os(iOS) + if #available(iOS 17, *) { + XCTAssertEqual(iOSVersion.v17.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v16.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v15.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v13.isCurrentOrPast, true) + } else if #available(iOS 16, *) { + XCTAssertEqual(iOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v16.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v15.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v13.isCurrentOrPast, true) + } else if #available(iOS 15, *) { + XCTAssertEqual(iOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v15.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v13.isCurrentOrPast, true) + } else if #available(iOS 14, *) { + XCTAssertEqual(iOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v15.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(iOSVersion.v13.isCurrentOrPast, true) + } else if #available(iOS 13, *) { + XCTAssertEqual(iOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v15.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v13.isCurrentOrPast, true) + } + #else + XCTAssertEqual(iOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v15.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(iOSVersion.v13.isCurrentOrPast, false) + #endif + } + + func test_macOS_isCurrent() { + #if os(macOS) + if #available(macOS 14, *) { + XCTAssertEqual(macOSVersion.v14.isCurrent, true) + XCTAssertEqual(macOSVersion.v13.isCurrent, false) + XCTAssertEqual(macOSVersion.v12.isCurrent, false) + XCTAssertEqual(macOSVersion.v11.isCurrent, false) + XCTAssertEqual(macOSVersion.v10_15.isCurrent, false) + } else if #available(macOS 13, *) { + XCTAssertEqual(macOSVersion.v14.isCurrent, false) + XCTAssertEqual(macOSVersion.v13.isCurrent, true) + XCTAssertEqual(macOSVersion.v12.isCurrent, false) + XCTAssertEqual(macOSVersion.v11.isCurrent, false) + XCTAssertEqual(macOSVersion.v10_15.isCurrent, false) + } else if #available(macOS 12, *) { + XCTAssertEqual(macOSVersion.v14.isCurrent, false) + XCTAssertEqual(macOSVersion.v13.isCurrent, false) + XCTAssertEqual(macOSVersion.v12.isCurrent, true) + XCTAssertEqual(macOSVersion.v11.isCurrent, false) + XCTAssertEqual(macOSVersion.v10_15.isCurrent, false) + } else if #available(macOS 11, *) { + XCTAssertEqual(macOSVersion.v14.isCurrent, false) + XCTAssertEqual(macOSVersion.v13.isCurrent, false) + XCTAssertEqual(macOSVersion.v12.isCurrent, false) + XCTAssertEqual(macOSVersion.v11.isCurrent, true) + XCTAssertEqual(macOSVersion.v10_15.isCurrent, false) + } else if #available(macOS 10.15, *) { + XCTAssertEqual(macOSVersion.v14.isCurrent, false) + XCTAssertEqual(macOSVersion.v13.isCurrent, false) + XCTAssertEqual(macOSVersion.v12.isCurrent, false) + XCTAssertEqual(macOSVersion.v11.isCurrent, false) + XCTAssertEqual(macOSVersion.v10_15.isCurrent, true) + } + #else + XCTAssertEqual(macOSVersion.v14.isCurrent, false) + XCTAssertEqual(macOSVersion.v13.isCurrent, false) + XCTAssertEqual(macOSVersion.v12.isCurrent, false) + XCTAssertEqual(macOSVersion.v11.isCurrent, false) + XCTAssertEqual(macOSVersion.v10_15.isCurrent, false) + #endif + } + + func test_macOS_isCurrentOrPast() { + #if os(macOS) + if #available(macOS 14, *) { + XCTAssertEqual(macOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v13.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v12.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v11.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v10_15.isCurrentOrPast, true) + } else if #available(macOS 13, *) { + XCTAssertEqual(macOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v13.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v12.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v11.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v10_15.isCurrentOrPast, true) + } else if #available(macOS 12, *) { + XCTAssertEqual(macOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v13.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v12.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v11.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v10_15.isCurrentOrPast, true) + } else if #available(macOS 11, *) { + XCTAssertEqual(macOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v13.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v12.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v11.isCurrentOrPast, true) + XCTAssertEqual(macOSVersion.v10_15.isCurrentOrPast, true) + } else if #available(macOS 10.15, *) { + XCTAssertEqual(macOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v13.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v12.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v11.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v10_15.isCurrentOrPast, true) + } + #else + XCTAssertEqual(macOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v13.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v12.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v11.isCurrentOrPast, false) + XCTAssertEqual(macOSVersion.v10_15.isCurrentOrPast, false) + #endif + } + + func test_tvOS_isCurrent() { + #if os(tvOS) + if #available(tvOS 17, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrent, true) + XCTAssertEqual(tvOSVersion.v16.isCurrent, false) + XCTAssertEqual(tvOSVersion.v15.isCurrent, false) + XCTAssertEqual(tvOSVersion.v14.isCurrent, false) + XCTAssertEqual(tvOSVersion.v13.isCurrent, false) + } else if #available(tvOS 16, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrent, false) + XCTAssertEqual(tvOSVersion.v16.isCurrent, true) + XCTAssertEqual(tvOSVersion.v15.isCurrent, false) + XCTAssertEqual(tvOSVersion.v14.isCurrent, false) + XCTAssertEqual(tvOSVersion.v13.isCurrent, false) + } else if #available(tvOS 15, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrent, false) + XCTAssertEqual(tvOSVersion.v16.isCurrent, false) + XCTAssertEqual(tvOSVersion.v15.isCurrent, true) + XCTAssertEqual(tvOSVersion.v14.isCurrent, false) + XCTAssertEqual(tvOSVersion.v13.isCurrent, false) + } else if #available(tvOS 14, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrent, false) + XCTAssertEqual(tvOSVersion.v16.isCurrent, false) + XCTAssertEqual(tvOSVersion.v15.isCurrent, false) + XCTAssertEqual(tvOSVersion.v14.isCurrent, true) + XCTAssertEqual(tvOSVersion.v13.isCurrent, false) + } else if #available(tvOS 13, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrent, false) + XCTAssertEqual(tvOSVersion.v16.isCurrent, false) + XCTAssertEqual(tvOSVersion.v15.isCurrent, false) + XCTAssertEqual(tvOSVersion.v14.isCurrent, false) + XCTAssertEqual(tvOSVersion.v13.isCurrent, true) + } + #else + XCTAssertEqual(tvOSVersion.v17.isCurrent, false) + XCTAssertEqual(tvOSVersion.v16.isCurrent, false) + XCTAssertEqual(tvOSVersion.v15.isCurrent, false) + XCTAssertEqual(tvOSVersion.v14.isCurrent, false) + XCTAssertEqual(tvOSVersion.v13.isCurrent, false) + #endif + } + + func test_tvOS_isCurrentOrPast() { + #if os(tvOS) + if #available(tvOS 17, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v16.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v15.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v13.isCurrentOrPast, true) + } else if #available(tvOS 16, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v16.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v15.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v13.isCurrentOrPast, true) + } else if #available(tvOS 15, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v15.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v13.isCurrentOrPast, true) + } else if #available(tvOS 14, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v15.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v14.isCurrentOrPast, true) + XCTAssertEqual(tvOSVersion.v13.isCurrentOrPast, true) + } else if #available(tvOS 13, *) { + XCTAssertEqual(tvOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v15.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v13.isCurrentOrPast, true) + } + #else + XCTAssertEqual(tvOSVersion.v17.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v16.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v15.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v14.isCurrentOrPast, false) + XCTAssertEqual(tvOSVersion.v13.isCurrentOrPast, false) + #endif + } +} diff --git a/Tests/Tests/TestUtils.swift b/Tests/Tests/TestUtils.swift new file mode 100644 index 000000000..50f3b902a --- /dev/null +++ b/Tests/Tests/TestUtils.swift @@ -0,0 +1,110 @@ +import SwiftUI +import XCTest + +#if canImport(UIKit) +enum TestUtils { + #if os(visionOS) + private static let window = UIWindow(frame: .init(x: 0, y: 0, width: 800, height: 800)) + #else + private static let window = UIWindow(frame: UIScreen.main.bounds) + #endif + + static func present(view: some View) { + window.rootViewController = UIHostingController(rootView: view) + window.makeKeyAndVisible() + window.layoutIfNeeded() + } +} +#elseif canImport(AppKit) +enum TestUtils { + private static let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, + defer: true + ) + + static func present(view: some View) { + window.contentView = NSHostingView(rootView: view) + window.makeKeyAndOrderFront(nil) + window.layoutIfNeeded() + } +} +#endif + +func XCTAssertViewIntrospection( + of type: Entity.Type, + @ViewBuilder view: (Spies) -> some View, + extraAssertions: ([Entity]) -> Void = { _ in }, + file: StaticString = #file, + line: UInt = #line +) { + let spies = Spies() + let view = view(spies) + TestUtils.present(view: view) + XCTWaiter(delegate: spies).wait(for: spies.expectations.values.map(\.0), timeout: 3) + extraAssertions(spies.entities.sorted(by: { $0.key < $1.key }).map(\.value)) +} + +final class Spies: NSObject, XCTWaiterDelegate { + private(set) var entities: [Int: Entity] = [:] + private(set) var expectations: [ObjectIdentifier: (XCTestExpectation, StaticString, UInt)] = [:] + + subscript( + number: Int, + file: StaticString = #file, + line: UInt = #line + ) -> (Entity) -> Void { + let expectation = XCTestExpectation() + expectations[ObjectIdentifier(expectation)] = (expectation, file, line) + return { [self] in + if let entity = entities[number] { + XCTAssert(entity === $0, "Found view was overriden by another view", file: file, line: line) + } + entities[number] = $0 + expectation.fulfill() + } + } + + func waiter( + _ waiter: XCTWaiter, + didTimeoutWithUnfulfilledExpectations unfulfilledExpectations: [XCTestExpectation] + ) { + for expectation in unfulfilledExpectations { + let (_, file, line) = expectations[ObjectIdentifier(expectation)]! + XCTFail("Spy not called", file: file, line: line) + } + } + + func nestedWaiter( + _ waiter: XCTWaiter, + wasInterruptedByTimedOutWaiter outerWaiter: XCTWaiter + ) { + XCTFail("wasInterruptedByTimedOutWaiter") + } + + func waiter( + _ waiter: XCTWaiter, + fulfillmentDidViolateOrderingConstraintsFor expectation: XCTestExpectation, + requiredExpectation: XCTestExpectation + ) { + XCTFail("fulfillmentDidViolateOrderingConstraintsFor") + } + + func waiter( + _ waiter: XCTWaiter, + didFulfillInvertedExpectation expectation: XCTestExpectation + ) { + XCTFail("didFulfillInvertedExpectation") + } +} + +extension Collection { + subscript(safe index: Index, file: StaticString = #file, line: UInt = #line) -> Element? { + guard indices.contains(index) else { + XCTFail("Index \(index) is out of bounds", file: file, line: line) + return nil + } + return self[index] + } +} diff --git a/Tests/Tests/ViewTypes/ButtonTests.swift b/Tests/Tests/ViewTypes/ButtonTests.swift new file mode 100644 index 000000000..d07dcd962 --- /dev/null +++ b/Tests/Tests/ViewTypes/ButtonTests.swift @@ -0,0 +1,49 @@ +#if !os(iOS) && !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ButtonTests: XCTestCase { + #if canImport(AppKit) + typealias PlatformButton = NSButton + #endif + + func testButton() { + XCTAssertViewIntrospection(of: PlatformButton.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + let spy3 = spies[3] + + VStack { + Button("Button 0", action: {}) + .buttonStyle(.bordered) + #if os(macOS) + .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + Button("Button 1", action: {}) + .buttonStyle(.borderless) + #if os(macOS) + .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + + Button("Button 2", action: {}) + .buttonStyle(.link) + #if os(macOS) + .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + + Button("Button 3", action: {}) + #if os(macOS) + .introspect(.button, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy3) + #endif + } + } extraAssertions: { + #if canImport(AppKit) + XCTAssert(Set($0.map(ObjectIdentifier.init)).count == 4) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ColorPickerTests.swift b/Tests/Tests/ViewTypes/ColorPickerTests.swift new file mode 100644 index 000000000..25375eb68 --- /dev/null +++ b/Tests/Tests/ViewTypes/ColorPickerTests.swift @@ -0,0 +1,61 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, macOS 11, *) +final class ColorPickerTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformColor = UIColor + typealias PlatformColorPicker = UIColorWell + #elseif canImport(AppKit) + typealias PlatformColor = NSColor + typealias PlatformColorPicker = NSColorWell + #endif + + func testColorPicker() throws { + guard #available(iOS 14, macOS 11, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformColorPicker.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + ColorPicker("", selection: .constant(PlatformColor.red.cgColor)) + #if os(iOS) || os(visionOS) + .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) + #endif + + ColorPicker("", selection: .constant(PlatformColor.green.cgColor)) + #if os(iOS) || os(visionOS) + .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) + #endif + + ColorPicker("", selection: .constant(PlatformColor.blue.cgColor)) + #if os(iOS) || os(visionOS) + .introspect(.colorPicker, on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.colorPicker, on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.selectedColor, .red) + XCTAssertEqual($0[safe: 1]?.selectedColor, .green) + XCTAssertEqual($0[safe: 2]?.selectedColor, .blue) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.color, .red) + XCTAssertEqual($0[safe: 1]?.color, .green) + XCTAssertEqual($0[safe: 2]?.color, .blue) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/DatePickerTests.swift b/Tests/Tests/ViewTypes/DatePickerTests.swift new file mode 100644 index 000000000..f105cd722 --- /dev/null +++ b/Tests/Tests/ViewTypes/DatePickerTests.swift @@ -0,0 +1,60 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class DatePickerTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformDatePicker = UIDatePicker + #elseif canImport(AppKit) + typealias PlatformDatePicker = NSDatePicker + #endif + + func testDatePicker() { + let date0 = Date(timeIntervalSince1970: 0) + let date1 = Date(timeIntervalSince1970: 5) + let date2 = Date(timeIntervalSince1970: 10) + + XCTAssertViewIntrospection(of: PlatformDatePicker.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + DatePicker("", selection: .constant(date0)) + #if os(iOS) || os(visionOS) + .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date1)) + #if os(iOS) || os(visionOS) + .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date2)) + #if os(iOS) || os(visionOS) + .introspect(.datePicker, on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.datePicker, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.date, date0) + XCTAssertEqual($0[safe: 1]?.date, date1) + XCTAssertEqual($0[safe: 2]?.date, date2) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.dateValue, date0) + XCTAssertEqual($0[safe: 1]?.dateValue, date1) + XCTAssertEqual($0[safe: 2]?.dateValue, date2) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/DatePickerWithCompactFieldStyleTests.swift b/Tests/Tests/ViewTypes/DatePickerWithCompactFieldStyleTests.swift new file mode 100644 index 000000000..6ac88b55b --- /dev/null +++ b/Tests/Tests/ViewTypes/DatePickerWithCompactFieldStyleTests.swift @@ -0,0 +1,68 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, macOS 10.15.4, *) +final class DatePickerWithCompactStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformDatePickerWithCompactStyle = UIDatePicker + #elseif canImport(AppKit) + typealias PlatformDatePickerWithCompactStyle = NSDatePicker + #endif + + func testDatePickerWithCompactStyle() throws { + guard #available(iOS 14, macOS 10.15.4, *) else { + throw XCTSkip() + } + + let date0 = Date(timeIntervalSince1970: 0) + let date1 = Date(timeIntervalSince1970: 5) + let date2 = Date(timeIntervalSince1970: 10) + + XCTAssertViewIntrospection(of: PlatformDatePickerWithCompactStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + DatePicker("", selection: .constant(date0)) + .datePickerStyle(.compact) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date1)) + .datePickerStyle(.compact) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date2)) + .datePickerStyle(.compact) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .compact), on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.datePicker(style: .compact), on: .macOS(.v10_15_4, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.date, date0) + XCTAssertEqual($0[safe: 1]?.date, date1) + XCTAssertEqual($0[safe: 2]?.date, date2) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.dateValue, date0) + XCTAssertEqual($0[safe: 1]?.dateValue, date1) + XCTAssertEqual($0[safe: 2]?.dateValue, date2) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/DatePickerWithFieldStyleTests.swift b/Tests/Tests/ViewTypes/DatePickerWithFieldStyleTests.swift new file mode 100644 index 000000000..630ceb472 --- /dev/null +++ b/Tests/Tests/ViewTypes/DatePickerWithFieldStyleTests.swift @@ -0,0 +1,51 @@ +#if !os(iOS) && !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class DatePickerWithFieldStyleTests: XCTestCase { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + typealias PlatformDatePickerWithFieldStyle = NSDatePicker + #endif + + func testDatePickerWithFieldStyle() { + let date0 = Date(timeIntervalSince1970: 0) + let date1 = Date(timeIntervalSince1970: 5) + let date2 = Date(timeIntervalSince1970: 10) + + XCTAssertViewIntrospection(of: PlatformDatePickerWithFieldStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + DatePicker("", selection: .constant(date0)) + .datePickerStyle(.field) + #if os(macOS) + .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date1)) + .datePickerStyle(.field) + #if os(macOS) + .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date2)) + .datePickerStyle(.field) + #if os(macOS) + .introspect(.datePicker(style: .field), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + XCTAssertEqual($0[safe: 0]?.dateValue, date0) + XCTAssertEqual($0[safe: 1]?.dateValue, date1) + XCTAssertEqual($0[safe: 2]?.dateValue, date2) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/DatePickerWithGraphicalStyleTests.swift b/Tests/Tests/ViewTypes/DatePickerWithGraphicalStyleTests.swift new file mode 100644 index 000000000..c4a4fbc30 --- /dev/null +++ b/Tests/Tests/ViewTypes/DatePickerWithGraphicalStyleTests.swift @@ -0,0 +1,68 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, *) +final class DatePickerWithGraphicalStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformDatePickerWithGraphicalStyle = UIDatePicker + #elseif canImport(AppKit) + typealias PlatformDatePickerWithGraphicalStyle = NSDatePicker + #endif + + func testDatePickerWithGraphicalStyle() throws { + guard #available(iOS 14, *) else { + throw XCTSkip() + } + + let date0 = Date(timeIntervalSince1970: 0) + let date1 = Date(timeIntervalSince1970: 3600 * 24 * 1) + let date2 = Date(timeIntervalSince1970: 3600 * 24 * 2) + + XCTAssertViewIntrospection(of: PlatformDatePickerWithGraphicalStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + DatePicker("", selection: .constant(date0)) + .datePickerStyle(.graphical) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date1)) + .datePickerStyle(.graphical) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date2)) + .datePickerStyle(.graphical) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .graphical), on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.datePicker(style: .graphical), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.date, date0) + XCTAssertEqual($0[safe: 1]?.date, date1) + XCTAssertEqual($0[safe: 2]?.date, date2) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.dateValue, date0) + XCTAssertEqual($0[safe: 1]?.dateValue, date1) + XCTAssertEqual($0[safe: 2]?.dateValue, date2) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/DatePickerWithStepperFieldStyleTests.swift b/Tests/Tests/ViewTypes/DatePickerWithStepperFieldStyleTests.swift new file mode 100644 index 000000000..b20055616 --- /dev/null +++ b/Tests/Tests/ViewTypes/DatePickerWithStepperFieldStyleTests.swift @@ -0,0 +1,51 @@ +#if !os(iOS) && !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class DatePickerWithStepperFieldStyleTests: XCTestCase { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + typealias PlatformDatePickerWithStepperFieldStyle = NSDatePicker + #endif + + func testDatePickerWithStepperFieldStyle() { + let date0 = Date(timeIntervalSince1970: 0) + let date1 = Date(timeIntervalSince1970: 5) + let date2 = Date(timeIntervalSince1970: 10) + + XCTAssertViewIntrospection(of: PlatformDatePickerWithStepperFieldStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + DatePicker("", selection: .constant(date0)) + .datePickerStyle(.stepperField) + #if os(macOS) + .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date1)) + .datePickerStyle(.stepperField) + #if os(macOS) + .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date2)) + .datePickerStyle(.stepperField) + #if os(macOS) + .introspect(.datePicker(style: .stepperField), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + XCTAssertEqual($0[safe: 0]?.dateValue, date0) + XCTAssertEqual($0[safe: 1]?.dateValue, date1) + XCTAssertEqual($0[safe: 2]?.dateValue, date2) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/DatePickerWithWheelStyleTests.swift b/Tests/Tests/ViewTypes/DatePickerWithWheelStyleTests.swift new file mode 100644 index 000000000..1f9df82a2 --- /dev/null +++ b/Tests/Tests/ViewTypes/DatePickerWithWheelStyleTests.swift @@ -0,0 +1,51 @@ +#if !os(tvOS) && !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class DatePickerWithWheelStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformDatePickerWithWheelStyle = UIDatePicker + #endif + + func testDatePickerWithWheelStyle() { + let date0 = Date(timeIntervalSince1970: 0) + let date1 = Date(timeIntervalSince1970: 5) + let date2 = Date(timeIntervalSince1970: 10) + + XCTAssertViewIntrospection(of: PlatformDatePickerWithWheelStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + DatePicker("", selection: .constant(date0)) + .datePickerStyle(.wheel) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date1)) + .datePickerStyle(.wheel) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #endif + .cornerRadius(8) + + DatePicker("", selection: .constant(date2)) + .datePickerStyle(.wheel) + #if os(iOS) || os(visionOS) + .introspect(.datePicker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.date, date0) + XCTAssertEqual($0[safe: 1]?.date, date1) + XCTAssertEqual($0[safe: 2]?.date, date2) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/FormTests.swift b/Tests/Tests/ViewTypes/FormTests.swift new file mode 100644 index 000000000..b6b5d8527 --- /dev/null +++ b/Tests/Tests/ViewTypes/FormTests.swift @@ -0,0 +1,40 @@ +#if !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class FormTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformForm = UIScrollView // covers both UITableView and UICollectionView + #elseif canImport(AppKit) + typealias PlatformForm = NSScrollView + #endif + + func testForm() throws { + XCTAssertViewIntrospection(of: PlatformForm.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + Form { + Text("Item 1") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.form, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy0($0) } + .introspect(.form, on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #endif + + Form { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.form, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor) { spy1($0) } + .introspect(.form, on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #endif + } + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/FormWithGroupedStyleTests.swift b/Tests/Tests/ViewTypes/FormWithGroupedStyleTests.swift new file mode 100644 index 000000000..90df92085 --- /dev/null +++ b/Tests/Tests/ViewTypes/FormWithGroupedStyleTests.swift @@ -0,0 +1,49 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 16, tvOS 16, macOS 13, *) +final class FormWithGroupedStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformFormWithGroupedStyle = UIScrollView // covers both UITableView and UICollectionView + #elseif canImport(AppKit) + typealias PlatformFormWithGroupedStyle = NSScrollView + #endif + + func testFormWithGroupedStyle() throws { + guard #available(iOS 16, tvOS 16, macOS 13, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformFormWithGroupedStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + Form { + Text("Item 1") + } + .formStyle(.grouped) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.form(style: .grouped), on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + .introspect(.form(style: .grouped), on: .tvOS(.v16, .v17)) { spy0($0) } + #elseif os(macOS) + .introspect(.form(style: .grouped), on: .macOS(.v13, .v14)) { spy0($0) } + #endif + + Form { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.form(style: .grouped), on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + .introspect(.form(style: .grouped), on: .tvOS(.v16, .v17), scope: .ancestor) { spy1($0) } + #elseif os(macOS) + .introspect(.form(style: .grouped), on: .macOS(.v13, .v14), scope: .ancestor) { spy1($0) } + #endif + } + .formStyle(.grouped) + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} diff --git a/Tests/Tests/ViewTypes/FullScreenCoverTests.swift b/Tests/Tests/ViewTypes/FullScreenCoverTests.swift new file mode 100644 index 000000000..cb83e292a --- /dev/null +++ b/Tests/Tests/ViewTypes/FullScreenCoverTests.swift @@ -0,0 +1,33 @@ +#if !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class FullScreenCoverTests: XCTestCase { + func testPresentationAsFullScreenCover() throws { + #if !os(visionOS) + throw XCTSkip("FIXME: this doesn't pass on anything other than visionOS, even though introspection works in the Showcase app") + #endif + + guard #available(iOS 14, tvOS 14, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in + let spy0 = spies[0] + + Text("Root") + .fullScreenCover(isPresented: .constant(true)) { + Text("Content") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect( + .fullScreenCover, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), + customize: spy0 + ) + #endif + } + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ListCellTests.swift b/Tests/Tests/ViewTypes/ListCellTests.swift new file mode 100644 index 000000000..d6882abf9 --- /dev/null +++ b/Tests/Tests/ViewTypes/ListCellTests.swift @@ -0,0 +1,46 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ListCellTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformListCell = UIView // covers both UITableViewCell and UICollectionViewCell + #elseif canImport(AppKit) + typealias PlatformListCell = NSTableCellView + #endif + + func testListCell() { + XCTAssertViewIntrospection(of: PlatformListCell.self) { spies in + let spy = spies[0] + + List { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.listCell, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy($0) } + .introspect(.listCell, on: .iOS(.v16, .v17), .visionOS(.v1)) { spy($0) } + #elseif os(macOS) + .introspect(.listCell, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy($0) } + #endif + } + } + } + + func testMaskedListCell() { + XCTAssertViewIntrospection(of: PlatformListCell.self) { spies in + let spy = spies[0] + + List { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.listCell, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy($0) } + .introspect(.listCell, on: .iOS(.v16, .v17), .visionOS(.v1)) { spy($0) } + #elseif os(macOS) + .introspect(.listCell, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy($0) } + #endif + .clipped() + .clipShape(RoundedRectangle(cornerRadius: 20.0)) + .cornerRadius(2.0) + } + } + } +} diff --git a/Tests/Tests/ViewTypes/ListTests.swift b/Tests/Tests/ViewTypes/ListTests.swift new file mode 100644 index 000000000..1b8da8f07 --- /dev/null +++ b/Tests/Tests/ViewTypes/ListTests.swift @@ -0,0 +1,103 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ListTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformList = UIScrollView // covers both UITableView and UICollectionView + #elseif canImport(AppKit) + typealias PlatformList = NSTableView + #endif + + func testList() { + XCTAssertViewIntrospection(of: PlatformList.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy0($0) } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #elseif os(macOS) + .introspect(.list, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy0($0) } + #endif + + List { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor) { spy1($0) } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #elseif os(macOS) + .introspect(.list, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } + #endif + } + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } + + #if !os(macOS) + func testNestedList() { + XCTAssertViewIntrospection(of: PlatformList.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + List { + Text("Item 1") + + List { + Text("Item 1") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy1($0) } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1)) { spy1($0) } + #endif + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy0($0) } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #endif + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } + #endif + + func testMaskedList() { + XCTAssertViewIntrospection(of: PlatformList.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy0($0) } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #elseif os(macOS) + .introspect(.list, on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy0($0) } + #endif + .clipped() + .clipShape(RoundedRectangle(cornerRadius: 20.0)) + .cornerRadius(2.0) + + List { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list, on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor) { spy1($0) } + .introspect(.list, on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #elseif os(macOS) + .introspect(.list, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } + #endif + } + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} diff --git a/Tests/Tests/ViewTypes/ListWithBorderedStyleTests.swift b/Tests/Tests/ViewTypes/ListWithBorderedStyleTests.swift new file mode 100644 index 000000000..357937c3c --- /dev/null +++ b/Tests/Tests/ViewTypes/ListWithBorderedStyleTests.swift @@ -0,0 +1,43 @@ +#if !os(iOS) && !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(macOS 12, *) +final class ListWithBorderedStyleTests: XCTestCase { + #if canImport(AppKit) + typealias PlatformListWithBorderedStyle = NSTableView + #endif + + func testListWithBorderedStyle() throws { + guard #available(macOS 12, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformListWithBorderedStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + .listStyle(.bordered) + #if os(macOS) + .introspect(.list(style: .bordered), on: .macOS(.v12, .v13, .v14)) { spy0($0) } + #endif + + List { + Text("Item 1") + #if os(macOS) + .introspect(.list(style: .bordered), on: .macOS(.v12, .v13, .v14), scope: .ancestor) { spy1($0) } + #endif + } + .listStyle(.bordered) + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ListWithGroupedStyleTests.swift b/Tests/Tests/ViewTypes/ListWithGroupedStyleTests.swift new file mode 100644 index 000000000..16793db88 --- /dev/null +++ b/Tests/Tests/ViewTypes/ListWithGroupedStyleTests.swift @@ -0,0 +1,40 @@ +#if !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ListWithGroupedStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformListWithGroupedStyle = UIScrollView // covers both UITableView and UICollectionView + #endif + + func testListWithGroupedStyle() { + XCTAssertViewIntrospection(of: PlatformListWithGroupedStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + .listStyle(.grouped) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list(style: .grouped), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy0($0) } + .introspect(.list(style: .grouped), on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #endif + + List { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list(style: .grouped), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor) { spy1($0) } + .introspect(.list(style: .grouped), on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #endif + } + .listStyle(.grouped) + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ListWithInsetGroupedStyleTests.swift b/Tests/Tests/ViewTypes/ListWithInsetGroupedStyleTests.swift new file mode 100644 index 000000000..23a539fa6 --- /dev/null +++ b/Tests/Tests/ViewTypes/ListWithInsetGroupedStyleTests.swift @@ -0,0 +1,45 @@ +#if !os(tvOS) && !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, *) +final class ListWithInsetGroupedStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformListWithInsetGroupedStyle = UIScrollView // covers both UITableView and UICollectionView + #endif + + func testListWithInsetGroupedStyle() throws { + guard #available(iOS 14, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformListWithInsetGroupedStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + .listStyle(.insetGrouped) + #if os(iOS) || os(visionOS) + .introspect(.list(style: .insetGrouped), on: .iOS(.v14, .v15)) { spy0($0) } + .introspect(.list(style: .insetGrouped), on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #endif + + List { + Text("Item 1") + #if os(iOS) || os(visionOS) + .introspect(.list(style: .insetGrouped), on: .iOS(.v14, .v15), scope: .ancestor) { spy1($0) } + .introspect(.list(style: .insetGrouped), on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #endif + } + .listStyle(.insetGrouped) + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ListWithInsetStyleTests.swift b/Tests/Tests/ViewTypes/ListWithInsetStyleTests.swift new file mode 100644 index 000000000..86e1746ab --- /dev/null +++ b/Tests/Tests/ViewTypes/ListWithInsetStyleTests.swift @@ -0,0 +1,51 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, macOS 11, *) +final class ListWithInsetStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformListWithInsetStyle = UIScrollView // covers both UITableView and UICollectionView + #elseif canImport(AppKit) + typealias PlatformListWithInsetStyle = NSTableView + #endif + + func testListWithInsetStyle() throws { + guard #available(iOS 14, macOS 11, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformListWithInsetStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + .listStyle(.inset) + #if os(iOS) || os(visionOS) + .introspect(.list(style: .inset), on: .iOS(.v14, .v15)) { spy0($0) } + .introspect(.list(style: .inset), on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #elseif os(macOS) + .introspect(.list(style: .inset), on: .macOS(.v11, .v12, .v13, .v14)) { spy0($0) } + #endif + + List { + Text("Item 1") + #if os(iOS) || os(visionOS) + .introspect(.list(style: .inset), on: .iOS(.v14, .v15), scope: .ancestor) { spy1($0) } + .introspect(.list(style: .inset), on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #elseif os(macOS) + .introspect(.list(style: .inset), on: .macOS(.v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } + #endif + } + .listStyle(.inset) + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ListWithPlainStyleTests.swift b/Tests/Tests/ViewTypes/ListWithPlainStyleTests.swift new file mode 100644 index 000000000..115a5a340 --- /dev/null +++ b/Tests/Tests/ViewTypes/ListWithPlainStyleTests.swift @@ -0,0 +1,44 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ListWithPlainStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformListWithPlainStyle = UIScrollView // covers both UITableView and UICollectionView + #elseif canImport(AppKit) + typealias PlatformListWithPlainStyle = NSTableView + #endif + + func testListWithPlainStyle() { + XCTAssertViewIntrospection(of: PlatformListWithPlainStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + .listStyle(.plain) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list(style: .plain), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17)) { spy0($0) } + .introspect(.list(style: .plain), on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #elseif os(macOS) + .introspect(.list(style: .plain), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy0($0) } + #endif + + List { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.list(style: .plain), on: .iOS(.v13, .v14, .v15), .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor) { spy1($0) } + .introspect(.list(style: .plain), on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #elseif os(macOS) + .introspect(.list(style: .plain), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } + #endif + } + .listStyle(.plain) + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} diff --git a/Tests/Tests/ViewTypes/ListWithSidebarStyleTests.swift b/Tests/Tests/ViewTypes/ListWithSidebarStyleTests.swift new file mode 100644 index 000000000..c45d456fb --- /dev/null +++ b/Tests/Tests/ViewTypes/ListWithSidebarStyleTests.swift @@ -0,0 +1,51 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, macOS 10.15, *) +final class ListWithSidebarStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformListWithSidebarStyle = UIScrollView // covers both UITableView and UICollectionView + #elseif canImport(AppKit) + typealias PlatformListWithSidebarStyle = NSTableView + #endif + + func testListWithSidebarStyle() throws { + guard #available(iOS 14, macOS 10.15, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformListWithSidebarStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + List { + Text("Item 1") + } + .listStyle(.sidebar) + #if os(iOS) || os(visionOS) + .introspect(.list(style: .sidebar), on: .iOS(.v14, .v15)) { spy0($0) } + .introspect(.list(style: .sidebar), on: .iOS(.v16, .v17), .visionOS(.v1)) { spy0($0) } + #elseif os(macOS) + .introspect(.list(style: .sidebar), on: .macOS(.v10_15, .v11, .v12, .v13, .v14)) { spy0($0) } + #endif + + List { + Text("Item 1") + #if os(iOS) || os(visionOS) + .introspect(.list(style: .sidebar), on: .iOS(.v14, .v15), scope: .ancestor) { spy1($0) } + .introspect(.list(style: .sidebar), on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor) { spy1($0) } + #elseif os(macOS) + .introspect(.list(style: .sidebar), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor) { spy1($0) } + #endif + } + .listStyle(.sidebar) + } + } extraAssertions: { + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/MapTests.swift b/Tests/Tests/ViewTypes/MapTests.swift new file mode 100644 index 000000000..2199b4cde --- /dev/null +++ b/Tests/Tests/ViewTypes/MapTests.swift @@ -0,0 +1,52 @@ +#if canImport(MapKit) +import MapKit +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, tvOS 14, macOS 11, *) +final class MapTests: XCTestCase { + typealias PlatformMap = MKMapView + + func testMap() throws { + guard #available(iOS 14, tvOS 14, macOS 11, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformMap.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + let region = Binding.constant(MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))) + + VStack { + Map(coordinateRegion: region) + .introspect( + .map, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1), + customize: spy0 + ) + + Map(coordinateRegion: region) + .introspect( + .map, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1), + customize: spy1 + ) + + Map(coordinateRegion: region) + .introspect( + .map, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1), + customize: spy2 + ) + } + } extraAssertions: { + XCTAssertNotIdentical($0[safe: 0], $0[safe: 1]) + XCTAssertNotIdentical($0[safe: 0], $0[safe: 2]) + XCTAssertNotIdentical($0[safe: 1], $0[safe: 2]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/NavigationSplitViewTests.swift b/Tests/Tests/ViewTypes/NavigationSplitViewTests.swift new file mode 100644 index 000000000..33c8a775d --- /dev/null +++ b/Tests/Tests/ViewTypes/NavigationSplitViewTests.swift @@ -0,0 +1,75 @@ +#if !LEGACY_MACOS_SDK +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 16, tvOS 16, macOS 13, *) +final class NavigationSplitViewTests: XCTestCase { + #if canImport(UIKit) && (os(iOS) || os(visionOS)) + typealias PlatformNavigationSplitView = UISplitViewController + #elseif canImport(UIKit) && os(tvOS) + typealias PlatformNavigationSplitView = UINavigationController + #elseif canImport(AppKit) + typealias PlatformNavigationSplitView = NSSplitView + #endif + + func testNavigationSplitView() throws { + guard #available(iOS 16, tvOS 16, macOS 13, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformNavigationSplitView.self) { spies in + let spy = spies[0] + + NavigationSplitView { + ZStack { + Color.red + Text("Root") + } + } detail: { + ZStack { + Color.blue + Text("Detail") + } + } + #if os(iOS) || os(visionOS) + .introspect(.navigationSplitView, on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy) + #elseif os(tvOS) + .introspect(.navigationSplitView, on: .tvOS(.v16, .v17), customize: spy) + #elseif os(macOS) + .introspect(.navigationSplitView, on: .macOS(.v13, .v14), customize: spy) + #endif + } + } + + func testNavigationSplitViewAsAncestor() throws { + guard #available(iOS 16, tvOS 16, macOS 13, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformNavigationSplitView.self) { spies in + let spy = spies[0] + + // 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. + NavigationSplitView(columnVisibility: .constant(.all)) { + ZStack { + Color.red + Text("Sidebar") + #if os(iOS) || os(visionOS) + .introspect(.navigationSplitView, on: .iOS(.v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy) + #elseif os(tvOS) + .introspect(.navigationSplitView, on: .tvOS(.v16, .v17), scope: .ancestor, customize: spy) + #elseif os(macOS) + .introspect(.navigationSplitView, on: .macOS(.v13, .v14), scope: .ancestor, customize: spy) + #endif + } + } detail: { + ZStack { + Color.blue + Text("Detail") + } + } + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/NavigationStackTests.swift b/Tests/Tests/ViewTypes/NavigationStackTests.swift new file mode 100644 index 000000000..702cbfc6e --- /dev/null +++ b/Tests/Tests/ViewTypes/NavigationStackTests.swift @@ -0,0 +1,52 @@ +#if !os(macOS) && !LEGACY_MACOS_SDK +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 16, tvOS 16, *) +final class NavigationStackTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformNavigationStack = UINavigationController + #endif + + func testNavigationStack() throws { + guard #available(iOS 16, tvOS 16, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformNavigationStack.self) { spies in + let spy = spies[0] + + NavigationStack { + ZStack { + Color.red + Text("Something") + } + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.navigationStack, on: .iOS(.v16, .v17), .tvOS(.v16, .v17), .visionOS(.v1), customize: spy) + #endif + } + } + + func testNavigationStackAsAncestor() throws { + guard #available(iOS 16, tvOS 16, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformNavigationStack.self) { spies in + let spy = spies[0] + + NavigationStack { + ZStack { + Color.red + Text("Something") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.navigationStack, on: .iOS(.v16, .v17), .tvOS(.v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy) + #endif + } + } + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/NavigationViewWithColumnsStyleTests.swift b/Tests/Tests/ViewTypes/NavigationViewWithColumnsStyleTests.swift new file mode 100644 index 000000000..a54edd7e7 --- /dev/null +++ b/Tests/Tests/ViewTypes/NavigationViewWithColumnsStyleTests.swift @@ -0,0 +1,61 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class NavigationViewWithColumnsStyleTests: XCTestCase { + #if canImport(UIKit) && (os(iOS) || os(visionOS)) + typealias PlatformNavigationViewWithColumnsStyle = UISplitViewController + #elseif canImport(UIKit) && os(tvOS) + typealias PlatformNavigationViewWithColumnsStyle = UINavigationController + #elseif canImport(AppKit) + typealias PlatformNavigationViewWithColumnsStyle = NSSplitView + #endif + + func testNavigationViewWithColumnsStyle() { + XCTAssertViewIntrospection(of: PlatformNavigationViewWithColumnsStyle.self) { spies in + let spy = spies[0] + + NavigationView { + ZStack { + Color.red + Text("Something") + } + } + .navigationViewStyle(DoubleColumnNavigationViewStyle()) + #if os(iOS) || os(visionOS) + .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy) + #elseif os(tvOS) + .introspect(.navigationView(style: .columns), on: .tvOS(.v13, .v14, .v15, .v16, .v17), customize: spy) + #elseif os(macOS) + .introspect(.navigationView(style: .columns), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy) + #endif + } + } + + func testNavigationViewWithColumnsStyleAsAncestor() { + XCTAssertViewIntrospection(of: PlatformNavigationViewWithColumnsStyle.self) { spies in + let spy = spies[0] + + NavigationView { + ZStack { + Color.red + Text("Something") + #if os(iOS) || os(visionOS) + .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy) + #elseif os(tvOS) + .introspect(.navigationView(style: .columns), on: .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor, customize: spy) + #elseif os(macOS) + .introspect(.navigationView(style: .columns), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor, customize: spy) + #endif + } + } + .navigationViewStyle(DoubleColumnNavigationViewStyle()) + #if os(iOS) + // 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. + .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { + $0.preferredDisplayMode = .oneOverSecondary + } + #endif + } + } +} diff --git a/Tests/Tests/ViewTypes/NavigationViewWithStackStyleTests.swift b/Tests/Tests/ViewTypes/NavigationViewWithStackStyleTests.swift new file mode 100644 index 000000000..eac7e45a2 --- /dev/null +++ b/Tests/Tests/ViewTypes/NavigationViewWithStackStyleTests.swift @@ -0,0 +1,45 @@ +#if !os(macOS) && !LEGACY_MACOS_SDK +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class NavigationViewWithStackStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformNavigationViewWithStackStyle = UINavigationController + #endif + + func testNavigationViewWithStackStyle() { + XCTAssertViewIntrospection(of: PlatformNavigationViewWithStackStyle.self) { spies in + let spy = spies[0] + + NavigationView { + ZStack { + Color.red + Text("Something") + } + } + .navigationViewStyle(.stack) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.navigationView(style: .stack), on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy) + #endif + } + } + + func testNavigationViewWithStackStyleAsAncestor() { + XCTAssertViewIntrospection(of: PlatformNavigationViewWithStackStyle.self) { spies in + let spy = spies[0] + + NavigationView { + ZStack { + Color.red + Text("Something") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.navigationView(style: .stack), on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy) + #endif + } + } + .navigationViewStyle(.stack) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/PageControlTests.swift b/Tests/Tests/ViewTypes/PageControlTests.swift new file mode 100644 index 000000000..261b3574b --- /dev/null +++ b/Tests/Tests/ViewTypes/PageControlTests.swift @@ -0,0 +1,31 @@ +#if !os(macOS) && !LEGACY_MACOS_SDK +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, tvOS 14, *) +final class PageControlTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformPageControl = UIPageControl + #endif + + func testPageControl() throws { + guard #available(iOS 14, tvOS 14, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformPageControl.self) { spies in + let spy = spies[0] + + TabView { + Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) + Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) + } + .tabViewStyle(.page(indexDisplayMode: .always)) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.pageControl, on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/PickerWithMenuStyleTests.swift b/Tests/Tests/ViewTypes/PickerWithMenuStyleTests.swift new file mode 100644 index 000000000..c0c629621 --- /dev/null +++ b/Tests/Tests/ViewTypes/PickerWithMenuStyleTests.swift @@ -0,0 +1,56 @@ +#if !os(iOS) && !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class PickerWithMenuStyleTests: XCTestCase { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + typealias PlatformPickerWithMenuStyle = NSPopUpButton + #endif + + func testPickerWithMenuStyle() { + XCTAssertViewIntrospection(of: PlatformPickerWithMenuStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + } + .pickerStyle(.menu) + #if os(macOS) + .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + Text("2").tag("2") + } + .pickerStyle(.menu) + #if os(macOS) + .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + Text("2").tag("2") + Text("3").tag("3") + } + .pickerStyle(.menu) + #if os(macOS) + .introspect(.picker(style: .menu), on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + XCTAssertEqual($0[safe: 0]?.numberOfItems, 1) + XCTAssertEqual($0[safe: 1]?.numberOfItems, 2) + XCTAssertEqual($0[safe: 2]?.numberOfItems, 3) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/PickerWithSegmentedStyleTests.swift b/Tests/Tests/ViewTypes/PickerWithSegmentedStyleTests.swift new file mode 100644 index 000000000..54123e4e7 --- /dev/null +++ b/Tests/Tests/ViewTypes/PickerWithSegmentedStyleTests.swift @@ -0,0 +1,66 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class PickerWithSegmentedStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformPickerWithSegmentedStyle = UISegmentedControl + #elseif canImport(AppKit) + typealias PlatformPickerWithSegmentedStyle = NSSegmentedControl + #endif + + func testPickerWithSegmentedStyle() { + XCTAssertViewIntrospection(of: PlatformPickerWithSegmentedStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + } + .pickerStyle(.segmented) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.picker(style: .segmented), on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + Text("2").tag("2") + } + .pickerStyle(.segmented) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.picker(style: .segmented), on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + Text("2").tag("2") + Text("3").tag("3") + } + .pickerStyle(.segmented) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.picker(style: .segmented), on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.picker(style: .segmented), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.numberOfSegments, 1) + XCTAssertEqual($0[safe: 1]?.numberOfSegments, 2) + XCTAssertEqual($0[safe: 2]?.numberOfSegments, 3) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.segmentCount, 1) + XCTAssertEqual($0[safe: 1]?.segmentCount, 2) + XCTAssertEqual($0[safe: 2]?.segmentCount, 3) + #endif + } + } +} diff --git a/Tests/Tests/ViewTypes/PickerWithWheelStyleTests.swift b/Tests/Tests/ViewTypes/PickerWithWheelStyleTests.swift new file mode 100644 index 000000000..8c05558e7 --- /dev/null +++ b/Tests/Tests/ViewTypes/PickerWithWheelStyleTests.swift @@ -0,0 +1,56 @@ +#if !os(tvOS) && !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class PickerWithWheelStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformPickerWithWheelStyle = UIPickerView + #endif + + func testPickerWithWheelStyle() { + XCTAssertViewIntrospection(of: PlatformPickerWithWheelStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + } + .pickerStyle(.wheel) + #if os(iOS) || os(visionOS) + .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #endif + .cornerRadius(8) + + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + Text("2").tag("2") + } + .pickerStyle(.wheel) + #if os(iOS) || os(visionOS) + .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #endif + .cornerRadius(8) + + Picker("Pick", selection: .constant("1")) { + Text("1").tag("1") + Text("2").tag("2") + Text("3").tag("3") + } + .pickerStyle(.wheel) + #if os(iOS) || os(visionOS) + .introspect(.picker(style: .wheel), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.numberOfRows(inComponent: 0), 1) + XCTAssertEqual($0[safe: 1]?.numberOfRows(inComponent: 0), 2) + XCTAssertEqual($0[safe: 2]?.numberOfRows(inComponent: 0), 3) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/PopoverTests.swift b/Tests/Tests/ViewTypes/PopoverTests.swift new file mode 100644 index 000000000..272fd6bcb --- /dev/null +++ b/Tests/Tests/ViewTypes/PopoverTests.swift @@ -0,0 +1,30 @@ +#if !os(tvOS) && !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class PopoverTests: XCTestCase { + func testPopover() throws { + if (UIDevice.current.userInterfaceIdiom == .pad) { + throw XCTSkip("FIXME: does not pass on iPad, even though it works in Showcase app") + } + #if os(visionOS) + throw XCTSkip("FIXME: does not pass on visionOS, even though it works in Showcase app") + #endif + + XCTAssertViewIntrospection(of: UIPopoverPresentationController.self) { spies in + let spy0 = spies[0] + + Text("Root") + .popover(isPresented: .constant(true)) { + Text("Popover") + .introspect( + .popover, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), + customize: spy0 + ) + } + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ProgressViewWithCircularStyleTests.swift b/Tests/Tests/ViewTypes/ProgressViewWithCircularStyleTests.swift new file mode 100644 index 000000000..4234c9137 --- /dev/null +++ b/Tests/Tests/ViewTypes/ProgressViewWithCircularStyleTests.swift @@ -0,0 +1,55 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ProgressViewWithCircularStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformProgressViewWithCircularStyle = UIActivityIndicatorView + #elseif canImport(AppKit) + typealias PlatformProgressViewWithCircularStyle = NSProgressIndicator + #endif + + func testProgressViewWithCircularStyle() throws { + guard #available(iOS 14, tvOS 14, macOS 11, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformProgressViewWithCircularStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + ProgressView(value: 0.25) + .progressViewStyle(.circular) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.progressView(style: .circular), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.progressView(style: .circular), on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) + #endif + + ProgressView(value: 0.5) + .progressViewStyle(.circular) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.progressView(style: .circular), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.progressView(style: .circular), on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) + #endif + + ProgressView(value: 0.75) + .progressViewStyle(.circular) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.progressView(style: .circular), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.progressView(style: .circular), on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + XCTAssertEqual($0[safe: 0]?.doubleValue, 0.25) + XCTAssertEqual($0[safe: 1]?.doubleValue, 0.5) + XCTAssertEqual($0[safe: 2]?.doubleValue, 0.75) + #endif + } + } +} diff --git a/Tests/Tests/ViewTypes/ProgressViewWithLinearStyleTests.swift b/Tests/Tests/ViewTypes/ProgressViewWithLinearStyleTests.swift new file mode 100644 index 000000000..9dac3147b --- /dev/null +++ b/Tests/Tests/ViewTypes/ProgressViewWithLinearStyleTests.swift @@ -0,0 +1,59 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ProgressViewWithLinearStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformProgressViewWithLinearStyle = UIProgressView + #elseif canImport(AppKit) + typealias PlatformProgressViewWithLinearStyle = NSProgressIndicator + #endif + + func testProgressViewWithLinearStyle() throws { + guard #available(iOS 14, tvOS 14, macOS 11, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformProgressViewWithLinearStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + ProgressView(value: 0.25) + .progressViewStyle(.linear) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.progressView(style: .linear), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.progressView(style: .linear), on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) + #endif + + ProgressView(value: 0.5) + .progressViewStyle(.linear) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.progressView(style: .linear), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.progressView(style: .linear), on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) + #endif + + ProgressView(value: 0.75) + .progressViewStyle(.linear) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.progressView(style: .linear), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.progressView(style: .linear), on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.progress, 0.25) + XCTAssertEqual($0[safe: 1]?.progress, 0.5) + XCTAssertEqual($0[safe: 2]?.progress, 0.75) + #elseif canImport(AppKit) && !targetEnvironment(macCatalyst) + XCTAssertEqual($0[safe: 0]?.doubleValue, 0.25) + XCTAssertEqual($0[safe: 1]?.doubleValue, 0.5) + XCTAssertEqual($0[safe: 2]?.doubleValue, 0.75) + #endif + } + } +} diff --git a/Tests/Tests/ViewTypes/ScrollViewTests.swift b/Tests/Tests/ViewTypes/ScrollViewTests.swift new file mode 100644 index 000000000..e5befb54a --- /dev/null +++ b/Tests/Tests/ViewTypes/ScrollViewTests.swift @@ -0,0 +1,132 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ScrollViewTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformScrollView = UIScrollView + #elseif canImport(AppKit) + typealias PlatformScrollView = NSScrollView + #endif + + func testScrollView() { + XCTAssertViewIntrospection(of: PlatformScrollView.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + ScrollView(showsIndicators: false) { + Text("Item 1") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + ScrollView(showsIndicators: true) { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy1) + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor, customize: spy1) + #endif + } + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.showsVerticalScrollIndicator, false) + XCTAssertEqual($0[safe: 1]?.showsVerticalScrollIndicator, true) + #elseif canImport(AppKit) + // FIXME: these assertions don't pass on macOS 12, not sure why... maybe callback is too premature in relation to view lifecycle? + if #available(macOS 13, *) { + XCTAssert($0[safe: 0]?.verticalScroller == nil) + XCTAssert($0[safe: 1]?.verticalScroller != nil) + } + #endif + + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } + + func testNestedScrollView() { + XCTAssertViewIntrospection(of: PlatformScrollView.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + ScrollView(showsIndicators: true) { + Text("Item 1") + + ScrollView(showsIndicators: false) { + Text("Item 1") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.showsVerticalScrollIndicator, true) + XCTAssertEqual($0[safe: 1]?.showsVerticalScrollIndicator, false) + #elseif canImport(AppKit) + // FIXME: these assertions don't pass on macOS 12, not sure why... maybe callback is too premature in relation to view lifecycle? + if #available(macOS 13, *) { + XCTAssert($0[safe: 0]?.verticalScroller != nil) + XCTAssert($0[safe: 1]?.verticalScroller == nil) + } + #endif + + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } + + func testMaskedScrollView() { + XCTAssertViewIntrospection(of: PlatformScrollView.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + + HStack { + ScrollView(showsIndicators: false) { + Text("Item 1") + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .clipped() + .clipShape(RoundedRectangle(cornerRadius: 20.0)) + .cornerRadius(2.0) + + ScrollView(showsIndicators: true) { + Text("Item 1") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy1) + #elseif os(macOS) + .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor, customize: spy1) + #endif + } + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.showsVerticalScrollIndicator, false) + XCTAssertEqual($0[safe: 1]?.showsVerticalScrollIndicator, true) + #elseif canImport(AppKit) + // FIXME: these assertions don't pass on macOS 12, not sure why... maybe callback is too premature in relation to view lifecycle? + if #available(macOS 13, *) { + XCTAssert($0[safe: 0]?.verticalScroller == nil) + XCTAssert($0[safe: 1]?.verticalScroller != nil) + } + #endif + + XCTAssert($0[safe: 0] !== $0[safe: 1]) + } + } +} diff --git a/Tests/Tests/ViewTypes/SearchFieldTests.swift b/Tests/Tests/ViewTypes/SearchFieldTests.swift new file mode 100644 index 000000000..5e615519e --- /dev/null +++ b/Tests/Tests/ViewTypes/SearchFieldTests.swift @@ -0,0 +1,100 @@ +#if !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 15, tvOS 15, *) +final class SearchFieldTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformSearchField = UISearchBar + #endif + + func testSearchFieldInNavigationStack() throws { + guard #available(iOS 15, tvOS 15, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformSearchField.self) { spies in + let spy = spies[0] + + NavigationView { + Text("Customized") + .searchable(text: .constant("")) + } + .navigationViewStyle(.stack) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.searchField, on: .iOS(.v15, .v16, .v17), .tvOS(.v15, .v16, .v17), .visionOS(.v1), customize: spy) + #endif + } + } + + func testSearchFieldInNavigationStackAsAncestor() throws { + guard #available(iOS 15, tvOS 15, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformSearchField.self) { spies in + let spy = spies[0] + + NavigationView { + Text("Customized") + .searchable(text: .constant("")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.searchField, on: .iOS(.v15, .v16, .v17), .tvOS(.v15, .v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy) + #endif + } + .navigationViewStyle(.stack) + } + } + + func testSearchFieldInNavigationSplitView() throws { + guard #available(iOS 15, tvOS 15, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformSearchField.self) { spies in + let spy = spies[0] + + NavigationView { + Text("Customized") + .searchable(text: .constant("")) + } + .navigationViewStyle(DoubleColumnNavigationViewStyle()) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.searchField, on: .iOS(.v15, .v16, .v17), .tvOS(.v15, .v16, .v17), .visionOS(.v1), customize: spy) + #endif + #if os(iOS) + // NB: this is necessary for introspection to work, because on iPad the search field is in the sidebar, which is initially hidden. + .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { + $0.preferredDisplayMode = .oneOverSecondary + } + #endif + } + } + + func testSearchFieldInNavigationSplitViewAsAncestor() throws { + guard #available(iOS 15, tvOS 15, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformSearchField.self) { spies in + let spy = spies[0] + + NavigationView { + Text("Customized") + .searchable(text: .constant("")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.searchField, on: .iOS(.v15, .v16, .v17), .tvOS(.v15, .v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy) + #endif + } + .navigationViewStyle(DoubleColumnNavigationViewStyle()) + #if os(iOS) + // NB: this is necessary for introspection to work, because on iPad the search field is in the sidebar, which is initially hidden. + .introspect(.navigationView(style: .columns), on: .iOS(.v13, .v14, .v15, .v16, .v17)) { + $0.preferredDisplayMode = .oneOverSecondary + } + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/SecureFieldTests.swift b/Tests/Tests/ViewTypes/SecureFieldTests.swift new file mode 100644 index 000000000..042c47de0 --- /dev/null +++ b/Tests/Tests/ViewTypes/SecureFieldTests.swift @@ -0,0 +1,95 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class SecureFieldTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformSecureField = UITextField + #elseif canImport(AppKit) + typealias PlatformSecureField = NSTextField + #endif + + func testSecureField() { + XCTAssertViewIntrospection(of: PlatformSecureField.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + SecureField("", text: .constant("Secure Field 0")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.secureField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.secureField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + SecureField("", text: .constant("Secure Field 1")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.secureField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.secureField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + SecureField("", text: .constant("Secure Field 2")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.secureField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.secureField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.text, "Secure Field 0") + XCTAssertEqual($0[safe: 1]?.text, "Secure Field 1") + XCTAssertEqual($0[safe: 2]?.text, "Secure Field 2") + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.stringValue, "Secure Field 0") + XCTAssertEqual($0[safe: 1]?.stringValue, "Secure Field 1") + XCTAssertEqual($0[safe: 2]?.stringValue, "Secure Field 2") + #endif + } + } + + func testSecureFieldsEmbeddedInList() { + XCTAssertViewIntrospection(of: PlatformSecureField.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + List { + SecureField("", text: .constant("Secure Field 0")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.secureField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.secureField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + SecureField("", text: .constant("Secure Field 1")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.secureField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.secureField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + + SecureField("", text: .constant("Secure Field 2")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.secureField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.secureField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.text, "Secure Field 0") + XCTAssertEqual($0[safe: 1]?.text, "Secure Field 1") + XCTAssertEqual($0[safe: 2]?.text, "Secure Field 2") + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.stringValue, "Secure Field 0") + XCTAssertEqual($0[safe: 1]?.stringValue, "Secure Field 1") + XCTAssertEqual($0[safe: 2]?.stringValue, "Secure Field 2") + #endif + } + } +} diff --git a/Tests/Tests/ViewTypes/SheetTests.swift b/Tests/Tests/ViewTypes/SheetTests.swift new file mode 100644 index 000000000..dd744bd71 --- /dev/null +++ b/Tests/Tests/ViewTypes/SheetTests.swift @@ -0,0 +1,79 @@ +#if !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class SheetTests: XCTestCase { + #if os(iOS) + func testSheet() throws { + XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in + let spy0 = spies[0] + + Text("Root") + .sheet(isPresented: .constant(true)) { + Text("Sheet") + .introspect( + .sheet, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), + customize: spy0 + ) + } + } + } + + func testSheetAsSheetPresentationController() throws { + guard #available(iOS 15, tvOS 15, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: UISheetPresentationController.self) { spies in + let spy0 = spies[0] + + Text("Root") + .sheet(isPresented: .constant(true)) { + Text("Sheet") + .introspect( + .sheet, + on: .iOS(.v15, .v16, .v17), + customize: spy0 + ) + } + } + } + #elseif os(tvOS) + func testSheet() throws { + throw XCTSkip("FIXME: this test doesn't pass for some reason, even though introspection works in the Showcase app") + + XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in + let spy0 = spies[0] + + Text("Root") + .sheet(isPresented: .constant(true)) { + Text("Content") + .introspect( + .sheet, + on: .tvOS(.v13, .v14, .v15, .v16, .v17), + customize: spy0 + ) + } + } + } + #elseif os(visionOS) + func testSheet() throws { + XCTAssertViewIntrospection(of: UIPresentationController.self) { spies in + let spy0 = spies[0] + + Text("Root") + .sheet(isPresented: .constant(true)) { + Text("Sheet") + .introspect( + .sheet, + on: .visionOS(.v1), + customize: spy0 + ) + } + } + } + #endif +} +#endif diff --git a/Tests/Tests/ViewTypes/SignInWithAppleButtonTests.swift b/Tests/Tests/ViewTypes/SignInWithAppleButtonTests.swift new file mode 100644 index 000000000..22b440cfc --- /dev/null +++ b/Tests/Tests/ViewTypes/SignInWithAppleButtonTests.swift @@ -0,0 +1,50 @@ +#if canImport(AuthenticationServices) +import AuthenticationServices +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, tvOS 14, macOS 11, *) +final class SignInWithAppleButtonTests: XCTestCase { + typealias PlatformSignInWithAppleButton = ASAuthorizationAppleIDButton + + func testSignInWithAppleButton() throws { + guard #available(iOS 14, tvOS 14, macOS 11, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformSignInWithAppleButton.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + SignInWithAppleButton(.continue, onRequest: { _ in }, onCompletion: { _ in }) + .introspect( + .signInWithAppleButton, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1), + customize: spy0 + ) + + SignInWithAppleButton(.signIn, onRequest: { _ in }, onCompletion: { _ in }) + .introspect( + .signInWithAppleButton, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1), + customize: spy1 + ) + + SignInWithAppleButton(.signUp, onRequest: { _ in }, onCompletion: { _ in }) + .introspect( + .signInWithAppleButton, + on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .macOS(.v11, .v12, .v13, .v14), .visionOS(.v1), + customize: spy2 + ) + } + } extraAssertions: { + XCTAssertNotIdentical($0[safe: 0], $0[safe: 1]) + XCTAssertNotIdentical($0[safe: 0], $0[safe: 2]) + XCTAssertNotIdentical($0[safe: 1], $0[safe: 2]) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/SliderTests.swift b/Tests/Tests/ViewTypes/SliderTests.swift new file mode 100644 index 000000000..db3200bbe --- /dev/null +++ b/Tests/Tests/ViewTypes/SliderTests.swift @@ -0,0 +1,56 @@ +#if !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class SliderTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformSlider = UISlider + #elseif canImport(AppKit) + typealias PlatformSlider = NSSlider + #endif + + func testSlider() { + XCTAssertViewIntrospection(of: PlatformSlider.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Slider(value: .constant(0.2), in: 0...1) + #if os(iOS) + .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17), customize: spy0) + #elseif os(macOS) + .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + Slider(value: .constant(0.5), in: 0...1) + #if os(iOS) + .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17), customize: spy1) + #elseif os(macOS) + .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + Slider(value: .constant(0.8), in: 0...1) + #if os(iOS) + .introspect(.slider, on: .iOS(.v13, .v14, .v15, .v16, .v17), customize: spy2) + #elseif os(macOS) + .introspect(.slider, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.value, 0.2) + XCTAssertEqual($0[safe: 1]?.value, 0.5) + XCTAssertEqual($0[safe: 2]?.value, 0.8) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.floatValue, 0.2) + XCTAssertEqual($0[safe: 1]?.floatValue, 0.5) + XCTAssertEqual($0[safe: 2]?.floatValue, 0.8) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/StepperTests.swift b/Tests/Tests/ViewTypes/StepperTests.swift new file mode 100644 index 000000000..e130f1de6 --- /dev/null +++ b/Tests/Tests/ViewTypes/StepperTests.swift @@ -0,0 +1,48 @@ +#if !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class StepperTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformStepper = UIStepper + #elseif canImport(AppKit) + typealias PlatformStepper = NSStepper + #endif + + func testStepper() { + XCTAssertViewIntrospection(of: PlatformStepper.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Stepper("", value: .constant(0), in: 0...10) + #if os(iOS) + .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17), customize: spy0) + #elseif os(macOS) + .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + Stepper("", value: .constant(0), in: 0...10) + #if os(iOS) + .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17), customize: spy1) + #elseif os(macOS) + .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + Stepper("", value: .constant(0), in: 0...10) + #if os(iOS) + .introspect(.stepper, on: .iOS(.v13, .v14, .v15, .v16, .v17), customize: spy2) + #elseif os(macOS) + .introspect(.stepper, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + XCTAssert(Set($0.map(ObjectIdentifier.init)).count == 3) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/TabViewTests.swift b/Tests/Tests/ViewTypes/TabViewTests.swift new file mode 100644 index 000000000..1538f12db --- /dev/null +++ b/Tests/Tests/ViewTypes/TabViewTests.swift @@ -0,0 +1,49 @@ +#if !os(visionOS) && !LEGACY_MACOS_SDK +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class TabViewTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformTabView = UITabBarController + #elseif canImport(AppKit) + typealias PlatformTabView = NSTabView + #endif + + func testTabView() { + XCTAssertViewIntrospection(of: PlatformTabView.self) { spies in + let spy = spies[0] + + TabView { + ZStack { + Color.red + Text("Something") + } + } + #if os(iOS) || os(tvOS) + .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), customize: spy) + #elseif os(macOS) + .introspect(.tabView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy) + #endif + } + } + + func testTabViewAsAncestor() { + XCTAssertViewIntrospection(of: PlatformTabView.self) { spies in + let spy = spies[0] + + TabView { + ZStack { + Color.red + Text("Something") + #if os(iOS) || os(tvOS) + .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), scope: .ancestor, customize: spy) + #elseif os(macOS) + .introspect(.tabView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), scope: .ancestor, customize: spy) + #endif + } + } + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/TabViewWithPageStyleTests.swift b/Tests/Tests/ViewTypes/TabViewWithPageStyleTests.swift new file mode 100644 index 000000000..93d0e160e --- /dev/null +++ b/Tests/Tests/ViewTypes/TabViewWithPageStyleTests.swift @@ -0,0 +1,50 @@ +#if !os(macOS) && !LEGACY_MACOS_SDK +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, tvOS 14, *) +final class TabViewWithPageStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformTabViewWithPageStyle = UICollectionView + #endif + + func testTabViewWithPageStyle() throws { + guard #available(iOS 14, tvOS 14, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformTabViewWithPageStyle.self) { spies in + let spy = spies[0] + + TabView { + Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) + Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) + } + .tabViewStyle(.page) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.tabView(style: .page), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy) + #endif + } + } + + func testTabViewWithPageStyleAsAncestor() throws { + guard #available(iOS 14, tvOS 14, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformTabViewWithPageStyle.self) { spies in + let spy = spies[0] + + TabView { + Text("Page 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.tabView(style: .page), on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), scope: .ancestor, customize: spy) + #endif + Text("Page 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue) + } + .tabViewStyle(.page) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/TableTests.swift b/Tests/Tests/ViewTypes/TableTests.swift new file mode 100644 index 000000000..b30967c8e --- /dev/null +++ b/Tests/Tests/ViewTypes/TableTests.swift @@ -0,0 +1,152 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 16, macOS 12, *) +final class TableTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformTable = UICollectionView + #elseif canImport(AppKit) + typealias PlatformTable = NSTableView + #endif + + func testTable() throws { + guard #available(iOS 16, macOS 12, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformTable.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + TipTable() + #if os(iOS) || os(visionOS) + .introspect(.table, on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy0) + #endif + + TipTable() + #if os(iOS) || os(visionOS) + .introspect(.table, on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy1) + #endif + + TipTable() + #if os(iOS) || os(visionOS) + .introspect(.table, on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy2) + #endif + } + } + } + + func testTableWithInsetStyle() throws { + guard #available(iOS 16, macOS 12, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformTable.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + TipTable() + .tableStyle(.inset) + #if os(iOS) || os(visionOS) + .introspect(.table, on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy0) + #endif + + TipTable() + .tableStyle(.inset) + #if os(iOS) || os(visionOS) + .introspect(.table, on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy1) + #endif + + TipTable() + .tableStyle(.inset) + #if os(iOS) || os(visionOS) + .introspect(.table, on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy2) + #endif + } + } + } + + #if os(macOS) + func testTableWithBorderedStyle() throws { + guard #available(macOS 12, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformTable.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + TipTable() + .tableStyle(.bordered) + #if os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy0) + #endif + + TipTable() + .tableStyle(.bordered) + #if os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy1) + #endif + + TipTable() + .tableStyle(.bordered) + #if os(macOS) + .introspect(.table, on: .macOS(.v12, .v13, .v14), customize: spy2) + #endif + } + } + } + #endif +} + +@available(iOS 16, macOS 12, *) +extension TableTests { + struct TipTable: View { + struct Purchase: Identifiable { + let price: Decimal + let id = UUID() + } + + var body: some View { + Table(of: Purchase.self) { + TableColumn("Base price") { purchase in + Text(purchase.price, format: .currency(code: "USD")) + } + TableColumn("With 15% tip") { purchase in + Text(purchase.price * 1.15, format: .currency(code: "USD")) + } + TableColumn("With 20% tip") { purchase in + Text(purchase.price * 1.2, format: .currency(code: "USD")) + } + TableColumn("With 25% tip") { purchase in + Text(purchase.price * 1.25, format: .currency(code: "USD")) + } + } rows: { + TableRow(Purchase(price: 20)) + TableRow(Purchase(price: 50)) + TableRow(Purchase(price: 75)) + } + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/TextEditorTests.swift b/Tests/Tests/ViewTypes/TextEditorTests.swift new file mode 100644 index 000000000..6eca638df --- /dev/null +++ b/Tests/Tests/ViewTypes/TextEditorTests.swift @@ -0,0 +1,61 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, macOS 11, *) +final class TextEditorTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformTextEditor = UITextView + #elseif canImport(AppKit) + typealias PlatformTextEditor = NSTextView + #endif + + func testTextEditor() throws { + guard #available(iOS 14, macOS 11, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformTextEditor.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + TextEditor(text: .constant("Text Field 0")) + #if os(iOS) || os(visionOS) + .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + TextEditor(text: .constant("Text Field 1")) + #if os(iOS) || os(visionOS) + .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + TextEditor(text: .constant("Text Field 2")) + #if os(iOS) || os(visionOS) + .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.textEditor, on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.text, "Text Field 0") + XCTAssertEqual($0[safe: 1]?.text, "Text Field 1") + XCTAssertEqual($0[safe: 2]?.text, "Text Field 2") + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.string, "Text Field 0") + XCTAssertEqual($0[safe: 1]?.string, "Text Field 1") + XCTAssertEqual($0[safe: 2]?.string, "Text Field 2") + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/TextFieldTests.swift b/Tests/Tests/ViewTypes/TextFieldTests.swift new file mode 100644 index 000000000..0e65c99a8 --- /dev/null +++ b/Tests/Tests/ViewTypes/TextFieldTests.swift @@ -0,0 +1,95 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class TextFieldTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformTextField = UITextField + #elseif canImport(AppKit) + typealias PlatformTextField = NSTextField + #endif + + func testTextField() { + XCTAssertViewIntrospection(of: PlatformTextField.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + TextField("", text: .constant("Text Field 0")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + TextField("", text: .constant("Text Field 1")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + TextField("", text: .constant("Text Field 2")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.text, "Text Field 0") + XCTAssertEqual($0[safe: 1]?.text, "Text Field 1") + XCTAssertEqual($0[safe: 2]?.text, "Text Field 2") + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.stringValue, "Text Field 0") + XCTAssertEqual($0[safe: 1]?.stringValue, "Text Field 1") + XCTAssertEqual($0[safe: 2]?.stringValue, "Text Field 2") + #endif + } + } + + func testTextFieldsEmbeddedInList() { + XCTAssertViewIntrospection(of: PlatformTextField.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + List { + TextField("", text: .constant("Text Field 0")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + TextField("", text: .constant("Text Field 1")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + + TextField("", text: .constant("Text Field 2")) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.textField, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.text, "Text Field 0") + XCTAssertEqual($0[safe: 1]?.text, "Text Field 1") + XCTAssertEqual($0[safe: 2]?.text, "Text Field 2") + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.stringValue, "Text Field 0") + XCTAssertEqual($0[safe: 1]?.stringValue, "Text Field 1") + XCTAssertEqual($0[safe: 2]?.stringValue, "Text Field 2") + #endif + } + } +} diff --git a/Tests/Tests/ViewTypes/TextFieldWithVerticalAxisTests.swift b/Tests/Tests/ViewTypes/TextFieldWithVerticalAxisTests.swift new file mode 100644 index 000000000..8289b86d9 --- /dev/null +++ b/Tests/Tests/ViewTypes/TextFieldWithVerticalAxisTests.swift @@ -0,0 +1,69 @@ +#if !LEGACY_MACOS_SDK +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 16, tvOS 16, macOS 13, *) +final class TextFieldWithVerticalAxisTests: XCTestCase { + #if canImport(UIKit) && (os(iOS) || os(visionOS)) + typealias PlatformTextField = UITextView + #elseif canImport(UIKit) && os(tvOS) + typealias PlatformTextField = UITextField + #elseif canImport(AppKit) + typealias PlatformTextField = NSTextField + #endif + + func testTextFieldWithVerticalAxis() throws { + guard #available(iOS 16, tvOS 16, macOS 13, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformTextField.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + TextField("", text: .constant("Text Field 1"), axis: .vertical) + #if os(iOS) || os(visionOS) + .introspect(.textField(axis: .vertical), on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(tvOS) + .introspect(.textField(axis: .vertical), on: .tvOS(.v16, .v17), customize: spy0) + #elseif os(macOS) + .introspect(.textField(axis: .vertical), on: .macOS(.v13, .v14), customize: spy0) + #endif + .cornerRadius(8) + + TextField("", text: .constant("Text Field 2"), axis: .vertical) + #if os(iOS) || os(visionOS) + .introspect(.textField(axis: .vertical), on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(tvOS) + .introspect(.textField(axis: .vertical), on: .tvOS(.v16, .v17), customize: spy1) + #elseif os(macOS) + .introspect(.textField(axis: .vertical), on: .macOS(.v13, .v14), customize: spy1) + #endif + .cornerRadius(8) + + TextField("", text: .constant("Text Field 3"), axis: .vertical) + #if os(iOS) || os(visionOS) + .introspect(.textField(axis: .vertical), on: .iOS(.v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(tvOS) + .introspect(.textField(axis: .vertical), on: .tvOS(.v16, .v17), customize: spy2) + #elseif os(macOS) + .introspect(.textField(axis: .vertical), on: .macOS(.v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.text, "Text Field 1") + XCTAssertEqual($0[safe: 1]?.text, "Text Field 2") + XCTAssertEqual($0[safe: 2]?.text, "Text Field 3") + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.stringValue, "Text Field 1") + XCTAssertEqual($0[safe: 1]?.stringValue, "Text Field 2") + XCTAssertEqual($0[safe: 2]?.stringValue, "Text Field 3") + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ToggleTests.swift b/Tests/Tests/ViewTypes/ToggleTests.swift new file mode 100644 index 000000000..544fcc520 --- /dev/null +++ b/Tests/Tests/ViewTypes/ToggleTests.swift @@ -0,0 +1,54 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ToggleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformToggle = UISwitch + #elseif canImport(AppKit) + typealias PlatformToggle = NSButton + #endif + + func testToggle() { + XCTAssertViewIntrospection(of: PlatformToggle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Toggle("", isOn: .constant(true)) + #if os(iOS) || os(visionOS) + .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + Toggle("", isOn: .constant(false)) + #if os(iOS) || os(visionOS) + .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + + Toggle("", isOn: .constant(true)) + #if os(iOS) || os(visionOS) + .introspect(.toggle, on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.toggle, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.isOn, true) + XCTAssertEqual($0[safe: 1]?.isOn, false) + XCTAssertEqual($0[safe: 2]?.isOn, true) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.state, .on) + XCTAssertEqual($0[safe: 1]?.state, .off) + XCTAssertEqual($0[safe: 2]?.state, .on) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ToggleWithButtonStyleTests.swift b/Tests/Tests/ViewTypes/ToggleWithButtonStyleTests.swift new file mode 100644 index 000000000..b91d78579 --- /dev/null +++ b/Tests/Tests/ViewTypes/ToggleWithButtonStyleTests.swift @@ -0,0 +1,50 @@ +#if !os(iOS) && !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(macOS 12, *) +final class ToggleWithButtonStyleTests: XCTestCase { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + typealias PlatformToggleWithButtonStyle = NSButton + #endif + + func testToggleWithButtonStyle() throws { + guard #available(macOS 12, *) else { + throw XCTSkip() + } + + XCTAssertViewIntrospection(of: PlatformToggleWithButtonStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Toggle("", isOn: .constant(true)) + .toggleStyle(.button) + #if os(macOS) + .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14), customize: spy0) + #endif + + Toggle("", isOn: .constant(false)) + .toggleStyle(.button) + #if os(macOS) + .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14), customize: spy1) + #endif + + Toggle("", isOn: .constant(true)) + .toggleStyle(.button) + #if os(macOS) + .introspect(.toggle(style: .button), on: .macOS(.v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + XCTAssertEqual($0[safe: 0]?.state, .on) + XCTAssertEqual($0[safe: 1]?.state, .off) + XCTAssertEqual($0[safe: 2]?.state, .on) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ToggleWithCheckboxStyleTests.swift b/Tests/Tests/ViewTypes/ToggleWithCheckboxStyleTests.swift new file mode 100644 index 000000000..0497518d8 --- /dev/null +++ b/Tests/Tests/ViewTypes/ToggleWithCheckboxStyleTests.swift @@ -0,0 +1,45 @@ +#if !os(iOS) && !os(tvOS) && !os(visionOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ToggleWithCheckboxStyleTests: XCTestCase { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + typealias PlatformToggleWithCheckboxStyle = NSButton + #endif + + func testToggleWithCheckboxStyle() throws { + XCTAssertViewIntrospection(of: PlatformToggleWithCheckboxStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Toggle("", isOn: .constant(true)) + .toggleStyle(.checkbox) + #if os(macOS) + .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + Toggle("", isOn: .constant(false)) + .toggleStyle(.checkbox) + #if os(macOS) + .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + + Toggle("", isOn: .constant(true)) + .toggleStyle(.checkbox) + #if os(macOS) + .introspect(.toggle(style: .checkbox), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + XCTAssertEqual($0[safe: 0]?.state, .on) + XCTAssertEqual($0[safe: 1]?.state, .off) + XCTAssertEqual($0[safe: 2]?.state, .on) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ToggleWithSwitchStyleTests.swift b/Tests/Tests/ViewTypes/ToggleWithSwitchStyleTests.swift new file mode 100644 index 000000000..8836e850f --- /dev/null +++ b/Tests/Tests/ViewTypes/ToggleWithSwitchStyleTests.swift @@ -0,0 +1,57 @@ +#if !os(tvOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ToggleWithSwitchStyleTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformToggleWithSwitchStyle = UISwitch + #elseif canImport(AppKit) + typealias PlatformToggleWithSwitchStyle = NSSwitch + #endif + + func testToggleWithSwitchStyle() { + XCTAssertViewIntrospection(of: PlatformToggleWithSwitchStyle.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Toggle("", isOn: .constant(true)) + .toggleStyle(.switch) + #if os(iOS) || os(visionOS) + .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + Toggle("", isOn: .constant(false)) + .toggleStyle(.switch) + #if os(iOS) || os(visionOS) + .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + + Toggle("", isOn: .constant(true)) + .toggleStyle(.switch) + #if os(iOS) || os(visionOS) + .introspect(.toggle(style: .switch), on: .iOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.toggle(style: .switch), on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + #if canImport(UIKit) + XCTAssertEqual($0[safe: 0]?.isOn, true) + XCTAssertEqual($0[safe: 1]?.isOn, false) + XCTAssertEqual($0[safe: 2]?.isOn, true) + #elseif canImport(AppKit) + XCTAssertEqual($0[safe: 0]?.state, .on) + XCTAssertEqual($0[safe: 1]?.state, .off) + XCTAssertEqual($0[safe: 2]?.state, .on) + #endif + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/VideoPlayerTests.swift b/Tests/Tests/ViewTypes/VideoPlayerTests.swift new file mode 100644 index 000000000..9658001e7 --- /dev/null +++ b/Tests/Tests/ViewTypes/VideoPlayerTests.swift @@ -0,0 +1,58 @@ +#if canImport(AVKit) +import AVKit +import SwiftUI +import SwiftUIIntrospect +import XCTest + +@available(iOS 14, tvOS 14, macOS 11, *) +final class VideoPlayerTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformVideoPlayer = AVPlayerViewController + #elseif canImport(AppKit) + typealias PlatformVideoPlayer = AVPlayerView + #endif + + func testVideoPlayer() throws { + guard #available(iOS 14, tvOS 14, macOS 11, *) else { + throw XCTSkip() + } + + let videoURL0 = URL(string: "https://bit.ly/swswift#1")! + let videoURL1 = URL(string: "https://bit.ly/swswift#2")! + let videoURL2 = URL(string: "https://bit.ly/swswift#3")! + + XCTAssertViewIntrospection(of: PlatformVideoPlayer.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + VideoPlayer(player: AVPlayer(url: videoURL0)) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.videoPlayer, on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.videoPlayer, on: .macOS(.v11, .v12, .v13, .v14), customize: spy0) + #endif + + VideoPlayer(player: AVPlayer(url: videoURL1)) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.videoPlayer, on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.videoPlayer, on: .macOS(.v11, .v12, .v13, .v14), customize: spy1) + #endif + + VideoPlayer(player: AVPlayer(url: videoURL2)) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.videoPlayer, on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.videoPlayer, on: .macOS(.v11, .v12, .v13, .v14), customize: spy2) + #endif + } + } extraAssertions: { + XCTAssertEqual(($0[safe: 0]?.player?.currentItem?.asset as? AVURLAsset)?.url, videoURL0) + XCTAssertEqual(($0[safe: 1]?.player?.currentItem?.asset as? AVURLAsset)?.url, videoURL1) + XCTAssertEqual(($0[safe: 2]?.player?.currentItem?.asset as? AVURLAsset)?.url, videoURL2) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ViewControllerTests.swift b/Tests/Tests/ViewTypes/ViewControllerTests.swift new file mode 100644 index 000000000..a0a7993b3 --- /dev/null +++ b/Tests/Tests/ViewTypes/ViewControllerTests.swift @@ -0,0 +1,48 @@ +#if !os(macOS) +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ViewControllerTests: XCTestCase { + func testViewController() { + XCTAssertViewIntrospection(of: PlatformViewController.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + TabView { + NavigationView { + Text("Root").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red) + .introspect( + .viewController, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), + customize: spy2 + ) + } + .navigationViewStyle(.stack) + .tabItem { + Image(systemName: "1.circle") + Text("Tab 1") + } + .introspect( + .viewController, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), + customize: spy1 + ) + } + .introspect( + .viewController, + on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), + customize: spy0 + ) + } extraAssertions: { + #if !os(visionOS) + XCTAssert($0[safe: 0] is UITabBarController) + #endif + XCTAssert($0[safe: 1] is UINavigationController) + XCTAssert(String(describing: $0[safe: 2]).contains("UIHostingController")) + XCTAssert($0[safe: 1] === $0[safe: 2]?.parent) + } + } +} +#endif diff --git a/Tests/Tests/ViewTypes/ViewTests.swift b/Tests/Tests/ViewTypes/ViewTests.swift new file mode 100644 index 000000000..1dcf065ea --- /dev/null +++ b/Tests/Tests/ViewTypes/ViewTests.swift @@ -0,0 +1,39 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class ViewTests: XCTestCase { + func testView() { + XCTAssertViewIntrospection(of: PlatformView.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack(spacing: 10) { + Image(systemName: "scribble").resizable().frame(height: 30) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + Text("Text").frame(height: 40) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + } + .padding(10) + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.view, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.view, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } extraAssertions: { + XCTAssertEqual($0[safe: 0]?.frame.height, 30) + XCTAssertEqual($0[safe: 1]?.frame.height, 40) + XCTAssertEqual($0[safe: 2]?.frame.height, 100) + } + } +} diff --git a/Tests/Tests/ViewTypes/WindowTests.swift b/Tests/Tests/ViewTypes/WindowTests.swift new file mode 100644 index 000000000..a6f43ab18 --- /dev/null +++ b/Tests/Tests/ViewTypes/WindowTests.swift @@ -0,0 +1,44 @@ +import SwiftUI +import SwiftUIIntrospect +import XCTest + +final class WindowTests: XCTestCase { + #if canImport(UIKit) + typealias PlatformWindow = UIWindow + #elseif canImport(AppKit) + typealias PlatformWindow = NSWindow + #endif + + func testWindow() { + XCTAssertViewIntrospection(of: PlatformWindow.self) { spies in + let spy0 = spies[0] + let spy1 = spies[1] + let spy2 = spies[2] + + VStack { + Image(systemName: "scribble") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy0) + #elseif os(macOS) + .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy0) + #endif + + Text("Text") + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy1) + #elseif os(macOS) + .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy1) + #endif + } + #if os(iOS) || os(tvOS) || os(visionOS) + .introspect(.window, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17), .visionOS(.v1), customize: spy2) + #elseif os(macOS) + .introspect(.window, on: .macOS(.v10_15, .v11, .v12, .v13, .v14), customize: spy2) + #endif + } extraAssertions: { + XCTAssertIdentical($0[safe: 0], $0[safe: 1]) + XCTAssertIdentical($0[safe: 0], $0[safe: 2]) + XCTAssertIdentical($0[safe: 1], $0[safe: 2]) + } + } +} diff --git a/Tests/Tests/WeakTests.swift b/Tests/Tests/WeakTests.swift new file mode 100644 index 000000000..a626b29d3 --- /dev/null +++ b/Tests/Tests/WeakTests.swift @@ -0,0 +1,56 @@ +@_spi(Advanced) import SwiftUIIntrospect +import XCTest + +final class WeakTests: XCTestCase { + final class Foo {} + + var strongFoo: Foo? = Foo() + + func testInit_nil() { + @Weak var weakFoo: Foo? + XCTAssertNil(weakFoo) + } + + func testInit_nonNil() { + @Weak var weakFoo: Foo? = strongFoo + XCTAssertIdentical(weakFoo, strongFoo) + } + + func testAssignment_nilToNil() { + @Weak var weakFoo: Foo? + weakFoo = nil + XCTAssertNil(weakFoo) + } + + func testAssignment_nilToNonNil() { + @Weak var weakFoo: Foo? + let otherFoo = Foo() + weakFoo = otherFoo + XCTAssertIdentical(weakFoo, otherFoo) + } + + func testAssignment_nonNilToNil() { + @Weak var weakFoo: Foo? = strongFoo + weakFoo = nil + XCTAssertNil(weakFoo) + } + + func testAssignment_nonNilToNonNil() { + @Weak var weakFoo: Foo? = strongFoo + let otherFoo = Foo() + weakFoo = otherFoo + XCTAssertIdentical(weakFoo, otherFoo) + } + + func testIndirectAssignment_nonNilToNil() { + @Weak var weakFoo: Foo? = strongFoo + strongFoo = nil + XCTAssertNil(weakFoo) + } + + func testIndirectAssignment_nonNilToNonNil() { + @Weak var weakFoo: Foo? = strongFoo + strongFoo = Foo() + XCTAssertNil(weakFoo) + } +} diff --git a/Tests/TestsHostApp/TestsHostApp.swift b/Tests/TestsHostApp/TestsHostApp.swift new file mode 100644 index 000000000..050f4177e --- /dev/null +++ b/Tests/TestsHostApp/TestsHostApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct App: SwiftUI.App { + var body: some Scene { + WindowGroup { + EmptyView() + } + } +} diff --git a/Tests/UITests/StatusBarStyleUITests.swift b/Tests/UITests/StatusBarStyleUITests.swift new file mode 100644 index 000000000..0c116bd43 --- /dev/null +++ b/Tests/UITests/StatusBarStyleUITests.swift @@ -0,0 +1,33 @@ +import SnapshotTesting +import XCTest + +final class StatusBarStyleUITests: UITestCase { + override var testCase: TestCase { + .statusBarStyle + } + + func test() throws { + guard #available(iOS 14, *) else { + throw XCTSkip("TODO: run on iOS 13 and check in screenshots in order to be able to remove this skip") + } + guard #unavailable(iOS 17) else { + throw XCTSkip("SimulatorStatusMagic stopped working in iOS 17, so we can no longer consistently compare status bar screenshots") + } + + app.buttons["Navigate To Detail"].tap() + app.buttons["Navigate To Detail"].tap() + app.buttons["Navigate Back"].tap() + + let iOSDevice = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone" + let iOSVersion = ProcessInfo().operatingSystemVersion + func screenshotName(_ number: Int) -> String { + "\(iOSDevice)-ios-\(iOSVersion.majorVersion)-screenshot-\(number)" + } + + assertSnapshot( + matching: app.windows.firstMatch.screenshot().image, + as: .image(perceptualPrecision: 0.95), + named: screenshotName(1) + ) + } +} diff --git a/Tests/UITests/UITestCase.swift b/Tests/UITests/UITestCase.swift new file mode 100644 index 000000000..d99d199b1 --- /dev/null +++ b/Tests/UITests/UITestCase.swift @@ -0,0 +1,21 @@ +import SimulatorStatusMagic +import XCTest + +class UITestCase: XCTestCase { + var testCase: TestCase { + preconditionFailure("Please override this property") + } + + let app = XCUIApplication() + + override func invokeTest() { + SDStatusBarManager.sharedInstance().enableOverrides() + + continueAfterFailure = false + + app.launchEnvironment["testCase"] = testCase.rawValue + app.launch() + + super.invokeTest() + } +} diff --git a/Tests/UITests/UITests.xctestplan b/Tests/UITests/UITests.xctestplan new file mode 100644 index 000000000..403bcc61a --- /dev/null +++ b/Tests/UITests/UITests.xctestplan @@ -0,0 +1,24 @@ +{ + "configurations" : [ + { + "id" : "DD0EEECD-4762-4A68-91A8-F7B5A2209B45", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:Tests.xcodeproj", + "identifier" : "D58D832A2A66BDD500A203BE", + "name" : "UITests" + } + } + ], + "version" : 1 +} diff --git a/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-14-screenshot-1.png b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-14-screenshot-1.png new file mode 100644 index 000000000..f008882b2 Binary files /dev/null and b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-14-screenshot-1.png differ diff --git a/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-15-screenshot-1.png b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-15-screenshot-1.png new file mode 100644 index 000000000..6f0e828cb Binary files /dev/null and b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-15-screenshot-1.png differ diff --git a/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-16-screenshot-1.png b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-16-screenshot-1.png new file mode 100644 index 000000000..9778bb3ff Binary files /dev/null and b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.ipad-ios-16-screenshot-1.png differ diff --git a/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-14-screenshot-1.png b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-14-screenshot-1.png new file mode 100644 index 000000000..74dfd54e4 Binary files /dev/null and b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-14-screenshot-1.png differ diff --git a/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-15-screenshot-1.png b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-15-screenshot-1.png new file mode 100644 index 000000000..d8c9541b9 Binary files /dev/null and b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-15-screenshot-1.png differ diff --git a/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-16-screenshot-1.png b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-16-screenshot-1.png new file mode 100644 index 000000000..a5129697e Binary files /dev/null and b/Tests/UITests/__Snapshots__/StatusBarStyleUITests/test.iphone-ios-16-screenshot-1.png differ diff --git a/Tests/UITestsHostApp/Assets.xcassets/AccentColor.colorset/Contents.json b/Tests/UITestsHostApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/Tests/UITestsHostApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/UITestsHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/UITestsHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/Tests/UITestsHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/UITestsHostApp/Assets.xcassets/Contents.json b/Tests/UITestsHostApp/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Tests/UITestsHostApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/UITestsHostApp/Info.plist b/Tests/UITestsHostApp/Info.plist new file mode 100644 index 000000000..364d0f3ee --- /dev/null +++ b/Tests/UITestsHostApp/Info.plist @@ -0,0 +1,8 @@ + + + + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/Tests/UITestsHostApp/Preview Content/Preview Assets.xcassets/Contents.json b/Tests/UITestsHostApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Tests/UITestsHostApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/UITestsHostApp/StatusBarStyle/HostingController.swift b/Tests/UITestsHostApp/StatusBarStyle/HostingController.swift new file mode 100644 index 000000000..1b13e8b13 --- /dev/null +++ b/Tests/UITestsHostApp/StatusBarStyle/HostingController.swift @@ -0,0 +1,32 @@ +// +// HostingController.swift +// Blago +// +// Created by Dmytro Chumakov on 04.05.2023. +// + +import SwiftUI + +final class HostingController: UIHostingController where ContentView: View { + + var statusBarStyle: UIStatusBarStyle = .darkContent + var isInteractivePopGestureEnabled = true + + override var preferredStatusBarStyle: UIStatusBarStyle { + statusBarStyle + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.interactivePopGestureRecognizer?.isEnabled = isInteractivePopGestureEnabled + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + guard #available(iOS 16, *) else { + navigationController?.setNavigationBarHidden(true, animated: false) + return + } + } +} diff --git a/Tests/UITestsHostApp/StatusBarStyle/NavigationView.swift b/Tests/UITestsHostApp/StatusBarStyle/NavigationView.swift new file mode 100644 index 000000000..85e353547 --- /dev/null +++ b/Tests/UITestsHostApp/StatusBarStyle/NavigationView.swift @@ -0,0 +1,44 @@ + +// +// NavigationController.swift +// Blago +// +// Created by Dmytro Chumakov on 11.05.2023. +// + +import UIKit + +// MARK: - NavigationController + +final class NavigationController: UINavigationController { + + static var shared: UINavigationController? + + // MARK: Lifecycle + + override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + setNavigationBarHidden(true, animated: false) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + topViewController?.preferredStatusBarStyle ?? .default + } + + override func viewDidLoad() { + super.viewDidLoad() + interactivePopGestureRecognizer?.delegate = self + } +} + +// MARK: UIGestureRecognizerDelegate + +extension NavigationController: UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool { + viewControllers.count > 1 + } +} diff --git a/Tests/UITestsHostApp/StatusBarStyle/RootView.swift b/Tests/UITestsHostApp/StatusBarStyle/RootView.swift new file mode 100644 index 000000000..478b1ec99 --- /dev/null +++ b/Tests/UITestsHostApp/StatusBarStyle/RootView.swift @@ -0,0 +1,52 @@ +// +// RootView.swift +// Showcase +// +// Created by Orackle on 14.07.2023. +// + +import SwiftUI +import SwiftUIIntrospect + +struct RootView: View { + + var body: some View { + VStack { + Button("Navigate To Detail", action: navigateToDetail) + } + } + + private func navigateToDetail() { + let controller = HostingController(rootView: DetailView()) + controller.statusBarStyle = .lightContent + NavigationController.shared?.pushViewController(controller, animated: true) + } +} + +struct DetailView: View { + @Environment(\.presentationMode) var dismiss + + var body: some View { + ZStack { + Color.red.edgesIgnoringSafeArea(.all) + + VStack { + Button("Navigate To Detail", action: navigateToDetail) + Button("Navigate Back", action: goBack) + } + } + .introspect(.viewController, on: .iOS(.v13, .v14, .v15, .v16)) { viewController in + /// some customizations there + } + } + + private func goBack() { + dismiss.wrappedValue.dismiss() + } + + private func navigateToDetail() { + let controller = HostingController(rootView: DetailView()) + controller.statusBarStyle = .lightContent + NavigationController.shared?.pushViewController(controller, animated: true) + } +} diff --git a/Tests/UITestsHostApp/TestCases.swift b/Tests/UITestsHostApp/TestCases.swift new file mode 100644 index 000000000..64285580a --- /dev/null +++ b/Tests/UITestsHostApp/TestCases.swift @@ -0,0 +1,3 @@ +public enum TestCase: String { + case statusBarStyle = "Status Bar Style" +} diff --git a/Tests/UITestsHostApp/UITestsHostApp.swift b/Tests/UITestsHostApp/UITestsHostApp.swift new file mode 100644 index 000000000..0069c252a --- /dev/null +++ b/Tests/UITestsHostApp/UITestsHostApp.swift @@ -0,0 +1,28 @@ +import SwiftUI + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + guard + let testCaseRawValue = ProcessInfo.processInfo.environment["testCase"], + let testCase = TestCase(rawValue: testCaseRawValue) + else { + preconditionFailure("entryViewController not set") + } + + window?.rootViewController = { + switch testCase { + case .statusBarStyle: + let navController = NavigationController(rootViewController: HostingController(rootView: RootView())) + NavigationController.shared = navController + return navController + } + }() + window?.makeKeyAndVisible() + return true + } +} diff --git a/docs/LEGACY.md b/docs/LEGACY.md new file mode 100644 index 000000000..37cf99734 --- /dev/null +++ b/docs/LEGACY.md @@ -0,0 +1,193 @@ +> **Note** +> +> [`Introspect`](../Package.swift#L13) is a legacy module being replaced by [`SwiftUIIntrospect`](../Package@swift-5.7.swift#L19) later this year. +> +> Please refer to this repo's [README](../README.md) to learn more. + +Introspect for SwiftUI +====================== + +[![GithubCI_Status]][GithubCI_URL] [![Siteline_Badge]](https://siteline.com) [![Quintschaf_Badge]](https://quintschaf.com) + +> Introspect allows you to get the underlying UIKit or AppKit element of a SwiftUI view. + +For instance, with Introspect you can access `UITableView` to modify separators, or `UINavigationController` to customize the tab bar. + +How it works +------------ + +Introspect works by adding a custom `IntrospectionView` to the view hierarchy, then looking into the UIKit hierarchy to find the relevant view. + +![](./docs/diagram.png) + +For instance, when introspecting a `TextField`, it will: + + - Add `IntrospectionView` as an overlay of `TextField` + - Get the view host of the introspection view (which is alongside the view host of the `UITextField`) + - Get the previous sibling containing `UITextField` + +**Please note that this introspection method might break in future SwiftUI releases.** Future implementations might not use the same hierarchy, or might not use UIKit elements that are being looked for. Though the library is unlikely to crash, the `.introspect()` method will not be called in those cases. + +### Usage in production + +`Introspect` is meant to be used in production. It does not use any private API. It only inspects the view hierarchy using publicly available methods. The library takes a defensive approach to inspecting the view hierarchy: there is no hard assumption that elements are laid out a certain way, there is no force-cast to UIKit classes, and the `introspect()` methods are simply ignored if UIKit views cannot be found. + + +Install +------- + +### SwiftPM + +``` +https://github.com/siteline/SwiftUI-Introspect.git +``` + +### Cocoapods + +``` +pod 'Introspect' +``` + +Introspection +------------- + +### Implemented + +SwiftUI | UIKit | AppKit | Introspect +--- | --- | --- | --- +NavigationView (StackNavigationViewStyle) | UINavigationController | _N/A_ | `.introspectNavigationController()` +NavigationView (DoubleColumnNavigationViewStyle) | UISplitViewController | _N/A_ | `.introspectSplitViewController()` +NavigationView (DoubleColumnNavigationViewStyle) | _N/A_ | NSSplitView | `.introspectSplitView()` +_Any embedded view_ | UIViewController | _N/A_ | `.introspectViewController()` +ScrollView | UIScrollView | NSScrollView | `.introspectScrollView()` +List (iOS15 and below) | UITableView | NSTableView | `.introspectTableView()` +View in List (iOS15 and below) | UITableViewCell | NSTableCellView | `introspectTableViewCell()` +List (iOS 16) | UICollectionView | _N/A_ | `.introspectCollectionView()` +View in List (iOS 16) | UICollectionViewCell | _N/A_ | `.introspectCollectionViewCell()` +TabView | UITabBarController | NSTabView | `.introspectTabBarController()` (iOS)
`.introspectTabView()` (macOS) +TextField | UITextField | NSTextField | `.introspectTextField()` +Toggle | UISwitch | NSButton | `.introspectSwitch()` (iOS)
`.introspectButton()` (macOS) +Slider | UISlider | NSSlider | `.introspectSlider()` +Stepper | UIStepper | NSStepper | `.introspectStepper()` +DatePicker | UIDatePicker | NSDatePicker | `.introspectDatePicker()` +Picker (SegmentedPickerStyle) | UISegmentedControl | NSSegmentedControl | `.introspectSegmentedControl()` +Button | _N/A_ | NSButton | `.introspectButton()` +ColorPicker | UIColorWell | NSColorWell | `.introspectColorWell()` +TextEditor | UITextView | NSTextView | `.introspectTextView()` + + +**Missing an element?** Please [create an issue](https://github.com/timbersoftware/SwiftUI-Introspect/issues). As a temporary solution, you can [implement your own selector](#implement-your-own-selector). + +### Cannot implement + +SwiftUI | Affected Frameworks | Why +--- | --- | --- +Text | UIKit, AppKit | Not a UILabel / NSLabel +Image | UIKit, AppKit | Not a UIImageView / NSImageView +Button | UIKit | Not a UIButton + +Examples +-------- + +### List + +```swift +List { + Text("Item 1") + Text("Item 2") +} +.introspectTableView { tableView in + tableView.separatorStyle = .none +} +.introspectTableViewCell { cell in + let backgroundView = UIView() + backgroundView.backgroundColor = .clear + cell.selectedBackgroundView = backgroundView +} +``` + +### ScrollView + +```swift +ScrollView { + Text("Item 2") +} +.introspectScrollView { scrollView in + scrollView.refreshControl = UIRefreshControl() +} +``` + +### NavigationView + +```swift +NavigationView { + Text("Item 2") + .introspectNavigationController { navigationController in + navigationController.navigationBar.backgroundColor = .red + } +} +``` + +### TextField + +```swift +TextField("Text Field", text: $textFieldValue) + .introspectTextField { textField in + textField.layer.backgroundColor = UIColor.red.cgColor + } +``` + +Implement your own selector +--------------------------- + +**Missing an element?** Please [create an issue](https://github.com/timbersoftware/SwiftUI-Introspect/issues). + +In case Introspect doesn't support the SwiftUI element that you're looking for, you can implement your own selector. For example, to look for a `UITextField`: + +```swift +extension View { + public func introspectTextField(customize: @escaping (UITextField) -> ()) -> some View { + return inject(UIKitIntrospectionView( + selector: { introspectionView in + guard let viewHost = Introspect.findViewHost(from: introspectionView) else { + return nil + } + return Introspect.previousSibling(containing: UITextField.self, from: viewHost) + }, + customize: customize + )) + } +} +``` + +You can use any of the following [methods](https://github.com/timbersoftware/SwiftUI-Introspect/blob/master/Introspect/Introspect.swift#L3-L71) to inspect the hierarchy: + + - `Introspect.findChild(ofType:in:)` + - `Introspect.findChildUsingFrame(ofType:in:from:)` + - `Introspect.previousSibling(containing:from:)` + - `Introspect.nextSibling(containing:from:)` + - `Introspect.findAncestor(ofType:from:)` + - `Introspect.findHostingView(from:)` + - `Introspect.findViewHost(from:)` + +Releasing +--------- + +1. Update changelog with new version +2. PR as 'Bump to X.Y.Z' and merge it +3. Tag new version: + + ```sh + $ git tag X.Y.Z + $ git push origin --tags + ``` + + + +[GithubCI_Status]: https://github.com/siteline/swiftui-introspect/actions/workflows/ci.yml/badge.svg?branch=master + +[GithubCI_URL]: https://github.com/siteline/SwiftUI-Introspect/actions/workflows/ci.yml + +[Siteline_Badge]: https://badgen.net/badge/Built%20by/Siteline/blue?icon=https://uploads-ssl.webflow.com/5f4513afbbfc64c4777fcccf/5f525b122370d681879e170e_siteline-icon.svg + +[Quintschaf_Badge]: https://badgen.net/badge/Maintained%20by/Quintschaf/cyan?icon=https://quintschaf.com/assets/logo.svg diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 07d836dd5..33e29c813 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,33 +1,114 @@ -default_platform(:ios) skip_docs -platform :ios do - lane :test do |options| - puts 'Running on GitHub CI.' if options[:ci] == 'github' - multi_scan( - devices: ["iPhone 8", "iPad (8th generation)"], - scheme: "Introspect iOS", - skip_build: options[:ci] == 'github', - try_count: 3, - quit_simulators: false - ) +devices = { + "ios" => { + 13 => ["iPhone 11 (13.7)", "iPad Pro (9.7-inch) (13.7)"], + 14 => ["iPhone 12 (14.5)", "iPad Pro (9.7-inch) (14.5)"], + 15 => ["iPhone SE (3rd generation) (15.5)", "iPad Air (5th generation) (15.5)",], + 16 => ["iPhone 14 (16.4)", "iPad Pro (11-inch) (4th generation) (16.4)"], + 17 => ["iPhone 14 (17.0)", "iPad Pro (11-inch) (4th generation) (17.0)"], + }, + "tvos" => { + 13 => ["Apple TV (13.4)"], + 14 => ["Apple TV (14.5)"], + 15 => ["Apple TV (15.4)"], + 16 => ["Apple TV (16.4)"], + 17 => ["Apple TV (17.0)"], + }, + "visionos" => { + 1 => ["Apple Vision Pro (1.0)"], + }, +} + +lane :build do |options| + platform = options[:platform].to_s + version = options[:version].to_i + scheme = options[:scheme].to_s - multi_scan( - devices: ["Apple TV"], - scheme: "Introspect tvOS", - skip_build: options[:ci] == 'github', - try_count: 3, - quit_simulators: false + unless scheme == "Showcase" + raise "Unsupported scheme: #{scheme}" + next + end + + if platform == "macos" + for destination in ["platform=macOS", "platform=macOS,variant=Mac Catalyst"] + build_app( + scheme: scheme, + destination: destination, + skip_archive: true, + skip_codesigning: true, + skip_package_ipa: true, + skip_profile_detection: true, + ) + end + else + run_tests( + configuration: "Debug", + build_for_testing: true, + scheme: scheme, + devices: devices[platform][version], + prelaunch_simulator: false, + ensure_devices_found: true, + force_quit_simulator: true, + disable_concurrent_testing: true, ) end end -platform :mac do - lane :test do |options| - puts 'Running on GitHub CI.' if options[:ci] == 'github' - scan( - scheme: "Introspect macOS", - skip_build: options[:ci] == 'github' +lane :test do |options| + configuration = (options[:configuration] || "Debug").to_s + platform = options[:platform].to_s + version = options[:version].to_i + scheme = options[:scheme].to_s + + if platform == "macos" + case scheme + when "Introspect" + spm( + command: "test", + configuration: configuration.downcase, + ) + when "SwiftUIIntrospectTests" + for destination in ["platform=macOS", "platform=macOS,variant=Mac Catalyst"] + run_tests( + configuration: configuration, + build_for_testing: (destination.include? "Catalyst"), # build-only on Mac Catalyst since tests crash because app isn't launched for some reason. error: Thread 1: "NSApplication has not been created yet. Consider using -[UINSApplicationDelegate runBlockWhenSharedDelegateBecomesAvailable:] to avoid premature instantiation." + scheme: scheme, + destination: [destination], + catalyst_platform: "macos", + disable_slide_to_type: false, + prelaunch_simulator: false, + ensure_devices_found: true, + force_quit_simulator: false, + disable_concurrent_testing: true, + xcargs: version == 11 ? 'SWIFT_ACTIVE_COMPILATION_CONDITIONS="$(inherited) LEGACY_MACOS_SDK"' : nil, + ) + end + else + raise "Unsupported scheme: #{scheme}" + end + else + is_legacy_sdk = (platform == "ios" && version == 13) || (platform == "tvos" && version == 13) + scheme = case scheme + when "Introspect" + "Introspect" + when "SwiftUIIntrospectTests" + is_legacy_sdk ? "LegacySwiftUIIntrospectTests" : "SwiftUIIntrospectTests" + when "SwiftUIIntrospectUITests" + "SwiftUIIntrospectUITests" + else + raise "Unsupported scheme: #{scheme}" + end + run_tests( + configuration: configuration, + scheme: scheme, + devices: devices[platform][version], + prelaunch_simulator: true, + ensure_devices_found: true, + force_quit_simulator: true, + disable_concurrent_testing: true, + result_bundle: true, + output_directory: Dir.pwd + "/test_output", ) end end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile deleted file mode 100644 index 139f3a778..000000000 --- a/fastlane/Pluginfile +++ /dev/null @@ -1,5 +0,0 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! - -gem 'fastlane-plugin-test_center' diff --git a/renovate.json b/renovate.json deleted file mode 100644 index f45d8f110..000000000 --- a/renovate.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": [ - "config:base" - ] -} diff --git a/tea.yml b/tea.yml new file mode 100644 index 000000000..419f146e3 --- /dev/null +++ b/tea.yml @@ -0,0 +1,3 @@ +dependencies: + fastlane.tools: '*' # latest + tuist.io/xcbeautify: '*' # latest