diff --git a/.circleci/config.yml b/.circleci/config.yml index 41813f2..70e407f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ # Shared Xcode version between jobs -xcode-version: &xcode-version "11.2.1" +xcode-version: &xcode-version "11.3.1" # 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. @@ -40,8 +40,20 @@ jobs: name: Create reports directory command: mkdir output - run: - name: Test - command: bundle exec fastlane test + 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/CHANGELOG.md b/CHANGELOG.md index 5f721ac..d24d4c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Changelog ## master + - Added `macOS` and `tvOS` support. + ## [0.0.6] - Added `.introspectSegmentedControl()`. diff --git a/Introspect.podspec b/Introspect.podspec index 1a3d5a0..b816d5b 100644 --- a/Introspect.podspec +++ b/Introspect.podspec @@ -2,14 +2,18 @@ Pod::Spec.new do |spec| spec.name = 'Introspect' spec.version = '0.0.6' spec.license = { type: 'MIT' } - spec.homepage = 'https://github.com/timbersoftware/SwiftUI-Introspect.git' - spec.authors = { 'Lois Di Qual' => 'lois@timber.so' } + spec.homepage = 'https://github.com/siteline/SwiftUI-Introspect.git' + spec.authors = { 'Lois Di Qual' => 'lois@siteline.com' } spec.summary = 'Introspect the underlying UIKit element of a SwiftUI view.' spec.source = { - git: 'https://github.com/timbersoftware/SwiftUI-Introspect.git', + git: 'https://github.com/siteline/SwiftUI-Introspect.git', tag: spec.version } - spec.source_files = 'Introspect/**/*.swift' - spec.platform = :ios, '13.0' + + spec.source_files = 'Introspect/*.swift' + spec.swift_version = '5.1' + 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 index 0ded53f..d72f6b3 100644 --- a/Introspect.xcodeproj/project.pbxproj +++ b/Introspect.xcodeproj/project.pbxproj @@ -10,15 +10,34 @@ 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 */; }; - C068702F238DF01200DAFD3D /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C068702E238DF01200DAFD3D /* TestUtils.swift */; }; - C0687031238DF3C900DAFD3D /* IntrospectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0687030238DF3C900DAFD3D /* IntrospectTests.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 */; }; - C0C6D694238E007100DA6285 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0C6D693238E007100DA6285 /* Assets.xcassets */; }; - C0C6D697238E007100DA6285 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0C6D696238E007100DA6285 /* Preview Assets.xcassets */; }; - C0C6D69A238E007100DA6285 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0C6D698238E007100DA6285 /* LaunchScreen.storyboard */; }; 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 */ @@ -30,6 +49,20 @@ 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 */ @@ -39,19 +72,24 @@ 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 /* IntrospectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntrospectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; - C068702E238DF01200DAFD3D /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; - C0687030238DF3C900DAFD3D /* IntrospectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntrospectTests.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 = ""; }; - C0C6D693238E007100DA6285 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - C0C6D696238E007100DA6285 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - C0C6D699238E007100DA6285 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; 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 */ @@ -70,6 +108,21 @@ ); 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; @@ -79,6 +132,21 @@ ); 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 */ @@ -107,8 +175,12 @@ isa = PBXGroup; children = ( C0687012238DE85D00DAFD3D /* Introspect.framework */, - C068701B238DE85D00DAFD3D /* IntrospectTests.xctest */, + 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 = ""; @@ -116,9 +188,12 @@ C0687014238DE85D00DAFD3D /* Introspect */ = { isa = PBXGroup; children = ( - C0687015238DE85D00DAFD3D /* Introspect.h */, - C0687016238DE85D00DAFD3D /* Info.plist */, + C0ED341A23F3A258005FA859 /* AppKitIntrospectionView.swift */, C068702C238DE8FD00DAFD3D /* Introspect.swift */, + C0ED341623F3A149005FA859 /* UIKitIntrospectionViewController.swift */, + C0ED341423F3A13C005FA859 /* UIKitIntrospectionView.swift */, + C0ED341C23F3A58B005FA859 /* ViewExtensions.swift */, + C0ED346523F4880B005FA859 /* Supporting Files */, ); path = Introspect; sourceTree = ""; @@ -126,9 +201,9 @@ C068701F238DE85D00DAFD3D /* IntrospectTests */ = { isa = PBXGroup; children = ( + C0ED342223F3AC14005FA859 /* AppKitTests.swift */, + C0687030238DF3C900DAFD3D /* UIKitTests.swift */, C0687022238DE85D00DAFD3D /* Info.plist */, - C0687030238DF3C900DAFD3D /* IntrospectTests.swift */, - C068702E238DF01200DAFD3D /* TestUtils.swift */, ); path = IntrospectTests; sourceTree = ""; @@ -139,22 +214,11 @@ C0C6D68D238E006B00DA6285 /* AppDelegate.swift */, C0C6D68F238E006B00DA6285 /* SceneDelegate.swift */, C0C6D691238E006B00DA6285 /* ContentView.swift */, - C0C6D693238E007100DA6285 /* Assets.xcassets */, - C0C6D698238E007100DA6285 /* LaunchScreen.storyboard */, C0C6D69B238E007100DA6285 /* Info.plist */, - C0C6D695238E007100DA6285 /* Preview Content */, ); path = IntrospectExamples; sourceTree = ""; }; - C0C6D695238E007100DA6285 /* Preview Content */ = { - isa = PBXGroup; - children = ( - C0C6D696238E007100DA6285 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; C0C6D69F238E00D300DA6285 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -163,6 +227,15 @@ 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 */ @@ -174,12 +247,28 @@ ); 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 */ = { + C0687011238DE85D00DAFD3D /* Introspect iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = C0687026238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect" */; + buildConfigurationList = C0687026238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS" */; buildPhases = ( C068700D238DE85D00DAFD3D /* Headers */, C068700E238DE85D00DAFD3D /* Sources */, @@ -190,14 +279,14 @@ ); dependencies = ( ); - name = Introspect; + name = "Introspect iOS"; productName = Introspect; productReference = C0687012238DE85D00DAFD3D /* Introspect.framework */; productType = "com.apple.product-type.framework"; }; - C068701A238DE85D00DAFD3D /* IntrospectTests */ = { + C068701A238DE85D00DAFD3D /* Introspect iOS Tests */ = { isa = PBXNativeTarget; - buildConfigurationList = C0687029238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "IntrospectTests" */; + buildConfigurationList = C0687029238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS Tests" */; buildPhases = ( C0687017238DE85D00DAFD3D /* Sources */, C0687018238DE85D00DAFD3D /* Frameworks */, @@ -208,9 +297,45 @@ dependencies = ( C068701E238DE85D00DAFD3D /* PBXTargetDependency */, ); - name = IntrospectTests; + name = "Introspect iOS Tests"; productName = IntrospectTests; - productReference = C068701B238DE85D00DAFD3D /* IntrospectTests.xctest */; + 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 */ = { @@ -232,13 +357,49 @@ 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 = 1120; + LastSwiftUpdateCheck = 1130; LastUpgradeCheck = 1120; ORGANIZATIONNAME = "Lois Di Qual"; TargetAttributes = { @@ -249,9 +410,21 @@ 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" */; @@ -267,8 +440,12 @@ projectDirPath = ""; projectRoot = ""; targets = ( - C0687011238DE85D00DAFD3D /* Introspect */, - C068701A238DE85D00DAFD3D /* IntrospectTests */, + C0687011238DE85D00DAFD3D /* Introspect iOS */, + C068701A238DE85D00DAFD3D /* Introspect iOS Tests */, + C0ED340823F39E93005FA859 /* Introspect macOS */, + C0ED343423F3AC43005FA859 /* Introspect macOS Tests */, + C0796E1E23F3CCD2002BF033 /* Introspect tvOS */, + C0796E2E23F3CDA4002BF033 /* Introspect tvOS Tests */, C0C6D68A238E006B00DA6285 /* IntrospectExamples */, ); }; @@ -289,13 +466,38 @@ ); 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 = ( - C0C6D69A238E007100DA6285 /* LaunchScreen.storyboard in Resources */, - C0C6D697238E007100DA6285 /* Preview Assets.xcassets in Resources */, - C0C6D694238E007100DA6285 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C0ED340723F39E93005FA859 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C0ED343323F3AC43005FA859 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -348,6 +550,10 @@ 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; @@ -356,8 +562,29 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C0687031238DF3C900DAFD3D /* IntrospectTests.swift in Sources */, - C068702F238DF01200DAFD3D /* TestUtils.swift in Sources */, + 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; }; @@ -371,26 +598,46 @@ ); 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 */; + target = C0687011238DE85D00DAFD3D /* Introspect iOS */; targetProxy = C068701D238DE85D00DAFD3D /* PBXContainerItemProxy */; }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - C0C6D698238E007100DA6285 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - C0C6D699238E007100DA6285 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; + C0796E3623F3CDA4002BF033 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C0796E1E23F3CCD2002BF033 /* Introspect tvOS */; + targetProxy = C0796E3523F3CDA4002BF033 /* PBXContainerItemProxy */; }; -/* End PBXVariantGroup section */ + C0ED343C23F3AC43005FA859 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C0ED340823F39E93005FA859 /* Introspect macOS */; + targetProxy = C0ED343B23F3AC43005FA859 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ C0687024238DE85D00DAFD3D /* Debug */ = { @@ -445,12 +692,14 @@ 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 = ""; }; @@ -502,11 +751,13 @@ 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 = ""; @@ -517,9 +768,9 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 5CYVM7UEHW; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -530,9 +781,11 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.loisdiqual.Introspect; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; + PRODUCT_NAME = Introspect; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -543,9 +796,9 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 5CYVM7UEHW; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -556,9 +809,11 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.loisdiqual.Introspect; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.Introspect; + PRODUCT_NAME = Introspect; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -602,20 +857,108 @@ }; 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", + ); + 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", + ); + 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; - DEVELOPMENT_ASSET_PATHS = "\"IntrospectExamples/Preview Content\""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = IntrospectExamples/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.loisdiqual.IntrospectExamples; + MARKETING_VERSION = 0.0.6; + PRODUCT_BUNDLE_IDENTIFIER = com.siteline.IntrospectExamples; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -628,20 +971,116 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_ASSET_PATHS = "\"IntrospectExamples/Preview Content\""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = IntrospectExamples/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.loisdiqual.IntrospectExamples; + 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; + 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; + 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 */ @@ -654,7 +1093,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C0687026238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect" */ = { + C0687026238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( C0687027238DE85D00DAFD3D /* Debug */, @@ -663,7 +1102,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C0687029238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "IntrospectTests" */ = { + C0687029238DE85D00DAFD3D /* Build configuration list for PBXNativeTarget "Introspect iOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( C068702A238DE85D00DAFD3D /* Debug */, @@ -672,6 +1111,24 @@ 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 = ( @@ -681,6 +1138,24 @@ 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/xcshareddata/xcschemes/Introspect.xcscheme b/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect iOS.xcscheme similarity index 92% rename from Introspect.xcodeproj/xcshareddata/xcschemes/Introspect.xcscheme rename to Introspect.xcodeproj/xcshareddata/xcschemes/Introspect iOS.xcscheme index 74a5946..15086f8 100644 --- a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect.xcscheme +++ b/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect iOS.xcscheme @@ -16,7 +16,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "C0687011238DE85D00DAFD3D" BuildableName = "Introspect.framework" - BlueprintName = "Introspect" + BlueprintName = "Introspect iOS" ReferencedContainer = "container:Introspect.xcodeproj"> @@ -33,8 +33,8 @@ @@ -62,7 +62,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "C0687011238DE85D00DAFD3D" BuildableName = "Introspect.framework" - BlueprintName = "Introspect" + BlueprintName = "Introspect iOS" ReferencedContainer = "container:Introspect.xcodeproj"> diff --git a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect macOS.xcscheme b/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect macOS.xcscheme new file mode 100644 index 0000000..8a729ef --- /dev/null +++ b/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect macOS.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect tvOS.xcscheme b/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect tvOS.xcscheme new file mode 100644 index 0000000..baa2bfc --- /dev/null +++ b/Introspect.xcodeproj/xcshareddata/xcschemes/Introspect tvOS.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Introspect.xcodeproj/xcuserdata/ldiqual.xcuserdatad/xcschemes/xcschememanagement.plist b/Introspect.xcodeproj/xcuserdata/ldiqual.xcuserdatad/xcschemes/xcschememanagement.plist index c2ccccd..8fbd6a6 100644 --- a/Introspect.xcodeproj/xcuserdata/ldiqual.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Introspect.xcodeproj/xcuserdata/ldiqual.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,16 +4,26 @@ SchemeUserState - Introspect.xcscheme_^#shared#^_ + Introspect iOS.xcscheme_^#shared#^_ orderHint 0 - IntrospectExamples.xcscheme_^#shared#^_ + Introspect macOS.xcscheme_^#shared#^_ orderHint 3 + Introspect tvOS.xcscheme_^#shared#^_ + + orderHint + 4 + + IntrospectExamples.xcscheme_^#shared#^_ + + orderHint + 5 + SuppressBuildableAutocreation @@ -27,6 +37,26 @@ primary + C0796E1E23F3CCD2002BF033 + + primary + + + C0796E2E23F3CDA4002BF033 + + primary + + + C0ED340823F39E93005FA859 + + primary + + + C0ED343423F3AC43005FA859 + + primary + + diff --git a/Introspect/AppKitIntrospectionView.swift b/Introspect/AppKitIntrospectionView.swift new file mode 100644 index 0000000..8697301 --- /dev/null +++ b/Introspect/AppKitIntrospectionView.swift @@ -0,0 +1,66 @@ +#if canImport(AppKit) +import SwiftUI +import AppKit + +/// Introspection NSView that is inserted alongside the target view. +public class IntrospectionNSView: NSView { + + required init() { + super.init(frame: .zero) + isHidden = true + } + + public override func hitTest(_ point: NSPoint) -> NSView? { + return nil + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +/// 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. +public struct AppKitIntrospectionView: NSViewRepresentable { + + /// Method that introspects the view hierarchy to find the target view. + /// First argument is the introspection view itself, which is contained in a view host alongside the target view. + let selector: (IntrospectionNSView) -> TargetViewType? + + /// User-provided customization method for the target view. + let customize: (TargetViewType) -> Void + + public init( + selector: @escaping (IntrospectionNSView) -> TargetViewType?, + customize: @escaping (TargetViewType) -> Void + ) { + self.selector = selector + self.customize = customize + } + + public func makeNSView(context: NSViewRepresentableContext) -> IntrospectionNSView { + let view = IntrospectionNSView() + view.setAccessibilityLabel("IntrospectionNSView<\(TargetViewType.self)>") + return view + } + + /// When `updateNSView` is called after creating the Introspection view, it is not yet in the AppKit hierarchy. + /// At this point, `introspectionView.superview.superview` is nil and we can't access the target AppKit 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 `updateNSView` + /// gets called when the introspection view gets removed from the hierarchy. + public func updateNSView( + _ nsView: IntrospectionNSView, + context: NSViewRepresentableContext + ) { + DispatchQueue.main.async { + guard let targetView = self.selector(nsView) else { + return + } + self.customize(targetView) + } + } +} +#endif + diff --git a/Introspect/Info.plist b/Introspect/Info.plist index 9bcb244..94b4f07 100644 --- a/Introspect/Info.plist +++ b/Introspect/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 0.0.6 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/Introspect/Introspect.swift b/Introspect/Introspect.swift index 3039bed..80da378 100644 --- a/Introspect/Introspect.swift +++ b/Introspect/Introspect.swift @@ -1,14 +1,28 @@ import SwiftUI +#if os(macOS) +public typealias PlatformView = NSView +#endif +#if os(iOS) || os(tvOS) +public typealias PlatformView = UIView +#endif + +#if os(macOS) +public typealias PlatformViewController = NSViewController +#endif +#if os(iOS) || os(tvOS) +public typealias PlatformViewController = UIViewController +#endif + /// Utility methods to inspect the UIKit view hierarchy. public enum Introspect { /// Finds a subview of the specified type. /// This method will recursively look for this view. /// Returns nil if it can't find a view of the specified type. - public static func findChild( + public static func findChild( ofType type: AnyViewType.Type, - in root: UIView + in root: PlatformView ) -> AnyViewType? { for subview in root.subviews { if let typed = subview as? AnyViewType { @@ -23,9 +37,9 @@ public enum Introspect { /// Finds a child view controller of the specified type. /// This method will recursively look for this child. /// Returns nil if it can't find a view of the specified type. - public static func findChild( + public static func findChild( ofType type: AnyViewControllerType.Type, - in root: UIViewController + in root: PlatformViewController ) -> AnyViewControllerType? { for child in root.children { if let typed = child as? AnyViewControllerType { @@ -40,9 +54,9 @@ public enum Introspect { /// Finds a previous sibling that contains a view of the specified type. /// This method inspects siblings recursively. /// Returns nil if no sibling contains the specified type. - public static func previousSibling( + public static func previousSibling( containing type: AnyViewType.Type, - from entry: UIView + from entry: PlatformView ) -> AnyViewType? { guard let superview = entry.superview, @@ -64,9 +78,10 @@ public enum Introspect { /// Finds a previous sibling that contains a view controller of the specified type. /// This method inspects siblings recursively. /// Returns nil if no sibling contains the specified type. - public static func previousSibling( + @available(macOS, unavailable) + public static func previousSibling( containing type: AnyViewControllerType.Type, - from entry: UIViewController + from entry: PlatformViewController ) -> AnyViewControllerType? { guard let parent = entry.parent, @@ -88,9 +103,9 @@ public enum Introspect { /// Finds a previous sibling that is a view controller of the specified type. /// This method does not inspect siblings recursively. /// Returns nil if no sibling is of the specified type. - public static func previousSibling( + public static func previousSibling( ofType type: AnyViewControllerType.Type, - from entry: UIViewController + from entry: PlatformViewController ) -> AnyViewControllerType? { guard let parent = entry.parent, @@ -112,9 +127,9 @@ public enum Introspect { /// Finds a next sibling that contains a view of the specified type. /// This method inspects siblings recursively. /// Returns nil if no sibling contains the specified type. - public static func nextSibling( + public static func nextSibling( containing type: AnyViewType.Type, - from entry: UIView + from entry: PlatformView ) -> AnyViewType? { guard let superview = entry.superview, @@ -134,7 +149,7 @@ public enum Introspect { /// Finds an ancestor of the specified type. /// If it reaches the top of the view without finding the specified view type, it returns nil. - public static func findAncestor(ofType type: AnyViewType.Type, from entry: UIView) -> AnyViewType? { + public static func findAncestor(ofType type: AnyViewType.Type, from entry: PlatformView) -> AnyViewType? { var superview = entry.superview while let s = superview { if let typed = s as? AnyViewType { @@ -149,10 +164,10 @@ public enum Introspect { /// Hosting views generally contain subviews for one specific SwiftUI element. /// For instance, if there are multiple text fields in a VStack, the hosting view will contain those text fields (and their host views, see below). /// Returns nil if it couldn't find a hosting view. This should never happen when called with an IntrospectionView. - public static func findHostingView(from entry: UIView) -> UIView? { + public static func findHostingView(from entry: PlatformView) -> PlatformView? { var superview = entry.superview while let s = superview { - if NSStringFromClass(type(of: s)).contains("UIHostingView") { + if NSStringFromClass(type(of: s)).contains("HostingView") { return s } superview = s.superview @@ -163,7 +178,7 @@ public enum Introspect { /// Finds the view host of a specific view. /// SwiftUI wraps each UIView within a ViewHost, then within a HostingView. /// Returns nil if it couldn't find a view host. This should never happen when called with an IntrospectionView. - public static func findViewHost(from entry: UIView) -> UIView? { + public static func findViewHost(from entry: PlatformView) -> PlatformView? { var superview = entry.superview while let s = superview { if NSStringFromClass(type(of: s)).contains("ViewHost") { @@ -175,6 +190,22 @@ public enum Introspect { } } +enum TargetViewSelector { + public static func sibling(from entry: PlatformView) -> TargetView? { + guard let viewHost = Introspect.findViewHost(from: entry) else { + return nil + } + return Introspect.previousSibling(containing: TargetView.self, from: viewHost) + } + + public static func ancestorOrSibling(from entry: PlatformView) -> TargetView? { + if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) { + return tableView + } + return sibling(from: entry) + } +} + /// Allows to safely access an array element by index /// Usage: array[safe: 2] private extension Array { @@ -186,245 +217,3 @@ private extension Array { return self[index] } } - -/// Introspection UIView that is inserted alongside the target view. -public class IntrospectionUIView: UIView { - - required init() { - super.init(frame: .zero) - isHidden = true - isUserInteractionEnabled = false - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -/// 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. -public struct IntrospectionView: UIViewRepresentable { - - /// Method that introspects the view hierarchy to find the target view. - /// First argument is the introspection view itself, which is contained in a view host alongside the target view. - let selector: (IntrospectionUIView) -> TargetViewType? - - /// User-provided customization method for the target view. - let customize: (TargetViewType) -> Void - - public init( - selector: @escaping (UIView) -> TargetViewType?, - customize: @escaping (TargetViewType) -> Void - ) { - self.selector = selector - self.customize = customize - } - - public func makeUIView(context: UIViewRepresentableContext) -> IntrospectionUIView { - let view = IntrospectionUIView() - view.accessibilityLabel = "IntrospectionUIView<\(TargetViewType.self)>" - 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. - public func updateUIView( - _ uiView: IntrospectionUIView, - context: UIViewRepresentableContext - ) { - DispatchQueue.main.asyncAfter(deadline: .now()) { - guard let targetView = self.selector(uiView) else { - return - } - self.customize(targetView) - } - } -} - -/// Introspection UIViewController that is inserted alongside the target view controller. -public class IntrospectionUIViewController: UIViewController { - required init() { - super.init(nibName: nil, bundle: nil) - view = IntrospectionUIView() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -/// This is the same logic as IntrospectionView but for view controllers. Please see details above. -public struct IntrospectionViewController: UIViewControllerRepresentable { - - let selector: (IntrospectionUIViewController) -> TargetViewControllerType? - let customize: (TargetViewControllerType) -> Void - - public init( - selector: @escaping (UIViewController) -> TargetViewControllerType?, - customize: @escaping (TargetViewControllerType) -> Void - ) { - self.selector = selector - self.customize = customize - } - - public func makeUIViewController( - context: UIViewControllerRepresentableContext - ) -> IntrospectionUIViewController { - let viewController = IntrospectionUIViewController() - viewController.accessibilityLabel = "IntrospectionUIViewController<\(TargetViewControllerType.self)>" - viewController.view.accessibilityLabel = "IntrospectionUIView<\(TargetViewControllerType.self)>" - return viewController - } - - public func updateUIViewController( - _ uiViewController: IntrospectionUIViewController, - context: UIViewControllerRepresentableContext - ) { - DispatchQueue.main.asyncAfter(deadline: .now()) { - guard let targetView = self.selector(uiViewController) else { - return - } - self.customize(targetView) - } - } -} - - -extension View { - - public func inject(_ view: SomeView) -> some View where SomeView: View { - return overlay(view.frame(width: 0, height: 0)) - } - - /// Finds a `UITableView` from a `SwiftUI.List`, or `SwiftUI.List` child. - public func introspectTableView(customize: @escaping (UITableView) -> ()) -> some View { - return inject(IntrospectionView( - selector: { introspectionView in - - // Search in ancestors - if let tableView = Introspect.findAncestor(ofType: UITableView.self, from: introspectionView) { - return tableView - } - - guard let viewHost = Introspect.findViewHost(from: introspectionView) else { - return nil - } - - // Search in siblings - return Introspect.previousSibling(containing: UITableView.self, from: viewHost) - }, - customize: customize - )) - } - - /// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child. - public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View { - return inject(IntrospectionView( - selector: { introspectionView in - - // Search in ancestors - if let tableView = Introspect.findAncestor(ofType: UIScrollView.self, from: introspectionView) { - return tableView - } - - guard let viewHost = Introspect.findViewHost(from: introspectionView) else { - return nil - } - - // Search in siblings - return Introspect.previousSibling(containing: UIScrollView.self, from: viewHost) - }, - customize: customize - )) - } - - /// Finds a `UINavigationController` from any view embedded in a `SwiftUI.NavigationView`. - public func introspectNavigationController(customize: @escaping (UINavigationController) -> ()) -> some View { - return inject(IntrospectionViewController( - selector: { introspectionViewController in - - // Search in ancestors - if let navigationController = introspectionViewController.navigationController { - return navigationController - } - - // Search in siblings - return Introspect.previousSibling(containing: UINavigationController.self, from: introspectionViewController) - }, - customize: customize - )) - } - - /// Finds the containing `UIViewController` of a SwiftUI view. - public func introspectViewController(customize: @escaping (UIViewController) -> ()) -> some View { - return inject(IntrospectionViewController( - selector: { $0.parent }, - customize: customize - )) - } - - /// Finds a `UITabBarController` from any SwiftUI view embedded in a `SwiftUI.TabView` - public func introspectTabBarController(customize: @escaping (UITabBarController) -> ()) -> some View { - return inject(IntrospectionViewController( - selector: { introspectionViewController in - - // Search in ancestors - if let navigationController = introspectionViewController.tabBarController { - return navigationController - } - - // Search in siblings - return Introspect.previousSibling(ofType: UITabBarController.self, from: introspectionViewController) - }, - customize: customize - )) - } - - /// Finds a `TargetView` from a `SwiftUI.View` - public func introspect(customize: @escaping (TargetView) -> ()) -> some View { - return inject(IntrospectionView( - selector: { introspectionView in - guard let viewHost = Introspect.findViewHost(from: introspectionView) else { - return nil - } - return Introspect.previousSibling(containing: TargetView.self, from: viewHost) - }, - customize: customize - )) - } - - /// Finds a `UITextField` from a `SwiftUI.TextField` - public func introspectTextField(customize: @escaping (UITextField) -> ()) -> some View { - return introspect(customize: customize) - } - - /// Finds a `UISwitch` from a `SwiftUI.Toggle` - public func introspectSwitch(customize: @escaping (UISwitch) -> ()) -> some View { - return introspect(customize: customize) - } - - /// Finds a `UISlider` from a `SwiftUI.Slider` - public func introspectSlider(customize: @escaping (UISlider) -> ()) -> some View { - return introspect(customize: customize) - } - - /// Finds a `UIStepper` from a `SwiftUI.Stepper` - public func introspectStepper(customize: @escaping (UIStepper) -> ()) -> some View { - return introspect(customize: customize) - } - - /// Finds a `UIDatePicker` from a `SwiftUI.DatePicker` - public func introspectDatePicker(customize: @escaping (UIDatePicker) -> ()) -> some View { - return introspect(customize: customize) - } - - /// Finds a `UISegmentedControl` from a `SwiftUI.Picker` with style `SegmentedPickerStyle` - public func introspectSegmentedControl(customize: @escaping (UISegmentedControl) -> ()) -> some View { - return introspect(customize: customize) - } -} diff --git a/Introspect/UIKitIntrospectionView.swift b/Introspect/UIKitIntrospectionView.swift new file mode 100644 index 0000000..e633866 --- /dev/null +++ b/Introspect/UIKitIntrospectionView.swift @@ -0,0 +1,62 @@ +#if canImport(UIKit) +import UIKit +import SwiftUI + +/// Introspection UIView that is inserted alongside the target view. +public class IntrospectionUIView: UIView { + + required init() { + super.init(frame: .zero) + isHidden = true + isUserInteractionEnabled = false + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +/// 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. +public struct UIKitIntrospectionView: UIViewRepresentable { + + /// Method that introspects the view hierarchy to find the target view. + /// First argument is the introspection view itself, which is contained in a view host alongside the target view. + let selector: (IntrospectionUIView) -> TargetViewType? + + /// User-provided customization method for the target view. + let customize: (TargetViewType) -> Void + + public init( + selector: @escaping (IntrospectionUIView) -> TargetViewType?, + customize: @escaping (TargetViewType) -> Void + ) { + self.selector = selector + self.customize = customize + } + + public func makeUIView(context: UIViewRepresentableContext) -> IntrospectionUIView { + let view = IntrospectionUIView() + view.accessibilityLabel = "IntrospectionUIView<\(TargetViewType.self)>" + 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. + public func updateUIView( + _ uiView: IntrospectionUIView, + context: UIViewRepresentableContext + ) { + DispatchQueue.main.async { + guard let targetView = self.selector(uiView) else { + return + } + self.customize(targetView) + } + } +} +#endif diff --git a/Introspect/UIKitIntrospectionViewController.swift b/Introspect/UIKitIntrospectionViewController.swift new file mode 100644 index 0000000..59ed12b --- /dev/null +++ b/Introspect/UIKitIntrospectionViewController.swift @@ -0,0 +1,53 @@ +#if canImport(UIKit) +import SwiftUI +import UIKit + +/// Introspection UIViewController that is inserted alongside the target view controller. +public class IntrospectionUIViewController: UIViewController { + required init() { + super.init(nibName: nil, bundle: nil) + view = IntrospectionUIView() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +/// This is the same logic as IntrospectionView but for view controllers. Please see details above. +public struct UIKitIntrospectionViewController: UIViewControllerRepresentable { + + let selector: (IntrospectionUIViewController) -> TargetViewControllerType? + let customize: (TargetViewControllerType) -> Void + + public init( + selector: @escaping (UIViewController) -> TargetViewControllerType?, + customize: @escaping (TargetViewControllerType) -> Void + ) { + self.selector = selector + self.customize = customize + } + + public func makeUIViewController( + context: UIViewControllerRepresentableContext + ) -> IntrospectionUIViewController { + let viewController = IntrospectionUIViewController() + viewController.accessibilityLabel = "IntrospectionUIViewController<\(TargetViewControllerType.self)>" + viewController.view.accessibilityLabel = "IntrospectionUIView<\(TargetViewControllerType.self)>" + return viewController + } + + public func updateUIViewController( + _ uiViewController: IntrospectionUIViewController, + context: UIViewControllerRepresentableContext + ) { + DispatchQueue.main.async { + guard let targetView = self.selector(uiViewController) else { + return + } + self.customize(targetView) + } + } +} +#endif diff --git a/Introspect/ViewExtensions.swift b/Introspect/ViewExtensions.swift new file mode 100644 index 0000000..409effd --- /dev/null +++ b/Introspect/ViewExtensions.swift @@ -0,0 +1,166 @@ +import SwiftUI + +#if canImport(AppKit) +import AppKit +#elseif canImport(UIKit) +import UIKit +#endif + +extension View { + public func inject(_ view: SomeView) -> some View where SomeView: View { + return overlay(view.frame(width: 0, height: 0)) + } +} + +#if canImport(UIKit) +extension View { + + /// Finds a `TargetView` from a `SwiftUI.View` + public func introspect( + selector: @escaping (IntrospectionUIView) -> TargetView?, + customize: @escaping (TargetView) -> () + ) -> some View { + return inject(UIKitIntrospectionView( + selector: selector, + customize: customize + )) + } + + /// Finds a `UINavigationController` from any view embedded in a `SwiftUI.NavigationView`. + public func introspectNavigationController(customize: @escaping (UINavigationController) -> ()) -> some View { + return inject(UIKitIntrospectionViewController( + selector: { introspectionViewController in + + // Search in ancestors + if let navigationController = introspectionViewController.navigationController { + return navigationController + } + + // Search in siblings + return Introspect.previousSibling(containing: UINavigationController.self, from: introspectionViewController) + }, + customize: customize + )) + } + + /// Finds the containing `UIViewController` of a SwiftUI view. + public func introspectViewController(customize: @escaping (UIViewController) -> ()) -> some View { + return inject(UIKitIntrospectionViewController( + selector: { $0.parent }, + customize: customize + )) + } + + /// Finds a `UITabBarController` from any SwiftUI view embedded in a `SwiftUI.TabView` + public func introspectTabBarController(customize: @escaping (UITabBarController) -> ()) -> some View { + return inject(UIKitIntrospectionViewController( + selector: { introspectionViewController in + + // Search in ancestors + if let navigationController = introspectionViewController.tabBarController { + return navigationController + } + + // Search in siblings + return Introspect.previousSibling(ofType: UITabBarController.self, from: introspectionViewController) + }, + customize: customize + )) + } + + /// Finds a `UITableView` from a `SwiftUI.List`, or `SwiftUI.List` child. + public func introspectTableView(customize: @escaping (UITableView) -> ()) -> some View { + return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize) + } + + /// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child. + public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View { + return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize) + } + + /// Finds a `UITextField` from a `SwiftUI.TextField` + public func introspectTextField(customize: @escaping (UITextField) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `UISwitch` from a `SwiftUI.Toggle` + @available(tvOS, unavailable) + public func introspectSwitch(customize: @escaping (UISwitch) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `UISlider` from a `SwiftUI.Slider` + @available(tvOS, unavailable) + public func introspectSlider(customize: @escaping (UISlider) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `UIStepper` from a `SwiftUI.Stepper` + @available(tvOS, unavailable) + public func introspectStepper(customize: @escaping (UIStepper) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `UIDatePicker` from a `SwiftUI.DatePicker` + @available(tvOS, unavailable) + public func introspectDatePicker(customize: @escaping (UIDatePicker) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `UISegmentedControl` from a `SwiftUI.Picker` with style `SegmentedPickerStyle` + public func introspectSegmentedControl(customize: @escaping (UISegmentedControl) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } +} +#endif + +#if canImport(AppKit) +extension View { + + /// Finds a `TargetView` from a `SwiftUI.View` + public func introspect( + selector: @escaping (IntrospectionNSView) -> TargetView?, + customize: @escaping (TargetView) -> () + ) -> some View { + return inject(AppKitIntrospectionView( + selector: selector, + customize: customize + )) + } + + /// Finds a `NSTableView` from a `SwiftUI.List`, or `SwiftUI.List` child. + public func introspectTableView(customize: @escaping (NSTableView) -> ()) -> some View { + return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize) + } + + /// Finds a `NSScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child. + public func introspectScrollView(customize: @escaping (NSScrollView) -> ()) -> some View { + return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize) + } + + /// Finds a `NSTextField` from a `SwiftUI.TextField` + public func introspectTextField(customize: @escaping (NSTextField) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `NSSlider` from a `SwiftUI.Slider` + public func introspectSlider(customize: @escaping (NSSlider) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `NSStepper` from a `SwiftUI.Stepper` + public func introspectStepper(customize: @escaping (NSStepper) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `NSDatePicker` from a `SwiftUI.DatePicker` + public func introspectDatePicker(customize: @escaping (NSDatePicker) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } + + /// Finds a `NSSegmentedControl` from a `SwiftUI.Picker` with style `SegmentedPickerStyle` + public func introspectSegmentedControl(customize: @escaping (NSSegmentedControl) -> ()) -> some View { + return introspect(selector: TargetViewSelector.sibling, customize: customize) + } +} +#endif diff --git a/IntrospectExamples/Assets.xcassets/AppIcon.appiconset/Contents.json b/IntrospectExamples/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d8db8d6..0000000 --- a/IntrospectExamples/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - }, - { - "idiom" : "ios-marketing", - "size" : "1024x1024", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/IntrospectExamples/Assets.xcassets/Contents.json b/IntrospectExamples/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164..0000000 --- a/IntrospectExamples/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/IntrospectExamples/Base.lproj/LaunchScreen.storyboard b/IntrospectExamples/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e932..0000000 --- a/IntrospectExamples/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/IntrospectExamples/Info.plist b/IntrospectExamples/Info.plist index 9742bf0..13a77e1 100644 --- a/IntrospectExamples/Info.plist +++ b/IntrospectExamples/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 0.0.6 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/IntrospectExamples/Preview Content/Preview Assets.xcassets/Contents.json b/IntrospectExamples/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index da4a164..0000000 --- a/IntrospectExamples/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/IntrospectTests/AppKitTests.swift b/IntrospectTests/AppKitTests.swift new file mode 100644 index 0000000..2413d66 --- /dev/null +++ b/IntrospectTests/AppKitTests.swift @@ -0,0 +1,202 @@ +#if canImport(AppKit) + +import XCTest +import SwiftUI +@testable import Introspect + +enum TestUtils { + static func present(view: ViewType) { + + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false) + window.center() + window.setFrameAutosaveName("Main Window") + window.contentView = NSHostingView(rootView: view) + window.makeKeyAndOrderFront(nil) + } +} + +private struct ListTestView: View { + + let spy1: () -> Void + let spy2: () -> Void + + var body: some View { + List { + Text("Item 1") + Text("Item 2") + .introspectTableView { tableView in + self.spy2() + } + } + .introspectTableView { tableView in + self.spy1() + } + } +} + +private struct ScrollTestView: View { + + let spy1: () -> Void + let spy2: () -> Void + + var body: some View { + HStack { + ScrollView { + Text("Item 1") + } + .introspectScrollView { scrollView in + self.spy1() + } + ScrollView { + Text("Item 1") + .introspectScrollView { scrollView in + self.spy2() + } + } + } + } +} + +private struct TextFieldTestView: View { + let spy: () -> Void + @State private var textFieldValue = "" + var body: some View { + TextField("Text Field", text: $textFieldValue) + .introspectTextField { textField in + self.spy() + } + } +} + +private struct SliderTestView: View { + let spy: () -> Void + @State private var sliderValue = 0.0 + var body: some View { + Slider(value: $sliderValue, in: 0...100) + .introspectSlider { slider in + self.spy() + } + } +} + +private struct StepperTestView: View { + let spy: () -> Void + var body: some View { + Stepper(onIncrement: {}, onDecrement: {}) { + Text("Stepper") + } + .introspectStepper { stepper in + self.spy() + } + } +} + +private struct DatePickerTestView: View { + let spy: () -> Void + @State private var datePickerValue = Date() + var body: some View { + DatePicker(selection: $datePickerValue) { + Text("DatePicker") + } + .introspectDatePicker { datePicker in + self.spy() + } + } +} + +private struct SegmentedControlTestView: View { + @State private var pickerValue = 0 + let spy: () -> Void + var body: some View { + Picker(selection: $pickerValue, label: Text("Segmented control")) { + Text("Option 1").tag(0) + Text("Option 2").tag(1) + Text("Option 3").tag(2) + } + .pickerStyle(SegmentedPickerStyle()) + .introspectSegmentedControl { segmentedControl in + self.spy() + } + } +} + +class AppKitTests: XCTestCase { + + func testList() { + + let expectation1 = XCTestExpectation() + let expectation2 = XCTestExpectation() + let view = ListTestView( + spy1: { expectation1.fulfill() }, + spy2: { expectation2.fulfill() } + ) + TestUtils.present(view: view) + wait(for: [expectation1, expectation2], timeout: 1) + } + + func testScrollView() { + + let expectation1 = XCTestExpectation() + let expectation2 = XCTestExpectation() + let view = ScrollTestView( + spy1: { expectation1.fulfill() }, + spy2: { expectation2.fulfill() } + ) + TestUtils.present(view: view) + wait(for: [expectation1, expectation2], timeout: 1) + } + + func testTextField() { + + let expectation = XCTestExpectation() + let view = TextFieldTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: 1) + } + + func testSlider() { + + let expectation = XCTestExpectation() + let view = SliderTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: 1) + } + + func testStepper() { + + let expectation = XCTestExpectation() + let view = StepperTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: 1) + } + + func testDatePicker() { + + let expectation = XCTestExpectation() + let view = DatePickerTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: 1) + } + + func testSegmentedControl() { + + let expectation = XCTestExpectation() + let view = SegmentedControlTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: 1) + } +} +#endif diff --git a/IntrospectTests/Info.plist b/IntrospectTests/Info.plist index 64d65ca..05bd31d 100644 --- a/IntrospectTests/Info.plist +++ b/IntrospectTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 0.0.6 CFBundleVersion 1 diff --git a/IntrospectTests/TestUtils.swift b/IntrospectTests/TestUtils.swift deleted file mode 100644 index caae2da..0000000 --- a/IntrospectTests/TestUtils.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import UIKit -import SwiftUI - -enum TestUtils { - static func present(view: ViewType) { - - let hostingController = UIHostingController(rootView: view) - - let application = UIApplication.shared - application.windows.forEach { window in - if let presentedViewController = window.rootViewController?.presentedViewController { - presentedViewController.dismiss(animated: false, completion: nil) - } - window.isHidden = true - } - - let window = UIWindow(frame: UIScreen.main.bounds) - window.layer.speed = 10 - - hostingController.beginAppearanceTransition(true, animated: false) - window.rootViewController = hostingController - window.makeKeyAndVisible() - hostingController.endAppearanceTransition() - } -} diff --git a/IntrospectTests/IntrospectTests.swift b/IntrospectTests/UIKitTests.swift similarity index 88% rename from IntrospectTests/IntrospectTests.swift rename to IntrospectTests/UIKitTests.swift index 0beaf13..a9052f9 100644 --- a/IntrospectTests/IntrospectTests.swift +++ b/IntrospectTests/UIKitTests.swift @@ -1,8 +1,32 @@ +#if canImport(UIKit) import XCTest import SwiftUI @testable import Introspect +enum TestUtils { + static func present(view: ViewType) { + + let hostingController = UIHostingController(rootView: view) + + let application = UIApplication.shared + application.windows.forEach { window in + if let presentedViewController = window.rootViewController?.presentedViewController { + presentedViewController.dismiss(animated: false, completion: nil) + } + window.isHidden = true + } + + let window = UIWindow(frame: UIScreen.main.bounds) + window.layer.speed = 10 + + hostingController.beginAppearanceTransition(true, animated: false) + window.rootViewController = hostingController + window.makeKeyAndVisible() + hostingController.endAppearanceTransition() + } +} + private struct NavigationTestView: View { let spy: () -> Void var body: some View { @@ -126,6 +150,7 @@ private struct TextFieldTestView: View { } } +@available(tvOS, unavailable) private struct ToggleTestView: View { let spy: () -> Void @State private var toggleValue = false @@ -137,6 +162,7 @@ private struct ToggleTestView: View { } } +@available(tvOS, unavailable) private struct SliderTestView: View { let spy: () -> Void @State private var sliderValue = 0.0 @@ -148,6 +174,7 @@ private struct SliderTestView: View { } } +@available(tvOS, unavailable) private struct StepperTestView: View { let spy: () -> Void var body: some View { @@ -160,6 +187,7 @@ private struct StepperTestView: View { } } +@available(tvOS, unavailable) private struct DatePickerTestView: View { let spy: () -> Void @State private var datePickerValue = Date() @@ -189,7 +217,7 @@ private struct SegmentedControlTestView: View { } } -class IntrospectTests: XCTestCase { +class UIKitTests: XCTestCase { func testNavigation() { let expectation = XCTestExpectation() @@ -210,16 +238,6 @@ class IntrospectTests: XCTestCase { wait(for: [expectation], timeout: 1) } - func testRootNavigation() { - - let expectation = XCTestExpectation() - let view = NavigationRootTestView(spy: { - expectation.fulfill() - }) - TestUtils.present(view: view) - wait(for: [expectation], timeout: 1) - } - func testTabView() { let expectation = XCTestExpectation() @@ -274,6 +292,27 @@ class IntrospectTests: XCTestCase { wait(for: [expectation], timeout: 1) } + func testSegmentedControl() { + + let expectation = XCTestExpectation() + let view = SegmentedControlTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: 1) + } + + #if os(iOS) + func testRootNavigation() { + + let expectation = XCTestExpectation() + let view = NavigationRootTestView(spy: { + expectation.fulfill() + }) + TestUtils.present(view: view) + wait(for: [expectation], timeout: 1) + } + func testToggle() { let expectation = XCTestExpectation() @@ -313,14 +352,6 @@ class IntrospectTests: XCTestCase { TestUtils.present(view: view) wait(for: [expectation], timeout: 1) } - - func testSegmentedControl() { - - let expectation = XCTestExpectation() - let view = SegmentedControlTestView(spy: { - expectation.fulfill() - }) - TestUtils.present(view: view) - wait(for: [expectation], timeout: 1) - } + #endif } +#endif diff --git a/Package.swift b/Package.swift index 9f20c94..c9f72ed 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "Introspect", platforms: [ - .iOS(.v13) + .macOS(.v10_15), .iOS(.v13), .tvOS(.v13) ], products: [ .library( @@ -19,6 +19,11 @@ let package = Package( name: "Introspect", dependencies: [], path: "Introspect" + ), + .testTarget( + name: "IntrospectTests", + dependencies: ["Introspect"], + path: "IntrospectTests" ) ] ) \ No newline at end of file diff --git a/README.md b/README.md index 10f974b..bf38db5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Introspect for SwiftUI [![CircleCI](https://circleci.com/gh/siteline/SwiftUI-Introspect.svg?style=svg&circle-token=6f995f204d4d417d31f79e7257f6e1ecf430ae07)](https://circleci.com/gh/siteline/SwiftUI-Introspect) -Introspect allows you to get the underlying UIKit element of a SwiftUI view. +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. @@ -47,19 +47,19 @@ Introspection ### Implemented -SwiftUI | UIKit | Introspect | Target ---- | --- | --- | --- -List | UITableView | `.introspectTableView()` | List, or List child -ScrollView | UIScrollView | `.introspectScrollView()` | ScrollView, or ScrollView child -NavigationView | UINavigationController | `.introspectNavigationController()` | NavigationView, or NavigationView child -_Any embedded view_ | UIViewController | `.introspectViewController()` | View embedded in a view controller -TabView | UITabBarController | `.introspectTabBarController()` | TabView, or TabView child -TextField | UITextField | `.introspectTextField()` | TextField -Toggle | UISwitch | `.introspectSwitch()` | Toggle -Slider | UISlider | `.introspectSlider()` | Slider -Stepper | UIStepper | `.introspectStepper()` | Stepper -DatePicker | UIDatePicker | `.introspectDatePicker()` | DatePicker -Picker (SegmentedPickerStyle) | UISegmentedControl | `.introspectSegmentedControl()` | Picker +SwiftUI | UIKit | AppKit | Introspect +--- | --- | --- | --- | --- +List | UITableView | NSTableView | `.introspectTableView()` +ScrollView | UIScrollView | NSScrollView | `.introspectScrollView()` +NavigationView | UINavigationController | _N/A_ | `.introspectNavigationController()` +_Any embedded view_ | UIViewController | _N/A_ | `.introspectViewController()` +TabView | UITabBarController | _N/A_ | `.introspectTabBarController()` +TextField | UITextField | NSTextField | `.introspectTextField()` +Toggle | UISwitch | N/A | `.introspectSwitch()` +Slider | UISlider | NSSlider | `.introspectSlider()` +Stepper | UIStepper | NSStepper | `.introspectStepper()` +DatePicker | UIDatePicker | NSDatePicker | `.introspectDatePicker()` +Picker (SegmentedPickerStyle) | UISegmentedControl | NSSegmentedControl | `.introspectSegmentedControl()` **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). @@ -148,3 +148,28 @@ You can use any of the following [methods](https://github.com/timbersoftware/Swi - `Introspect.findAncestor(ofType:from:)` - `Introspect.findHostingView(from:)` - `Introspect.findViewHost(from:)` + +Releasing +--------- + + - Increment version number: + +``` +$ bundle exec fastlane run increment_version_number bump_type:minor # major|minor|patch +``` + + - Update changelog with new version + - Bump version in `Introspect.podspec` + - Commit and push changes + - Tag new version: + +``` +$ git tag -a -m "" +$ git push origin --tags +``` + + - Push to cocoapods trunk: + +``` +$ bundle exec pod trunk push . +``` diff --git a/fastlane/Fastfile b/fastlane/Fastfile index e8158a5..de53afa 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,9 +1,24 @@ default_platform(:ios) skip_docs -lane :test do - scan( - devices: ["iPhone 8"], - scheme: "Introspect" - ) +platform :ios do + lane :test do + scan( + devices: ["iPhone 8"], + scheme: "Introspect iOS" + ) + + scan( + devices: ["Apple TV"], + scheme: "Introspect tvOS" + ) + end +end + +platform :mac do + lane :test do + scan( + scheme: "Introspect macOS" + ) + end end