diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..9761268
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,27 @@
+name: Test
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - '*'
+ workflow_dispatch:
+
+jobs:
+ example:
+ runs-on: ${{ matrix.runsOn }}
+ env:
+ DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}/Contents/Developer"
+ strategy:
+ matrix:
+ include:
+ - xcode: "Xcode_12.5.1.app"
+ runsOn: macOS-11
+ - xcode: "Xcode_13.2.1.app"
+ runsOn: macOS-12
+ steps:
+ - uses: actions/checkout@v2
+ - name: Run tests
+ run: xcodebuild -project ./Example/Example.xcodeproj -scheme Example test -destination platform='iOS Simulator',name='iPhone 11' -quiet -enableCodeCoverage YES -derivedDataPath "./output"
\ No newline at end of file
diff --git a/.swiftformat b/.swiftformat
new file mode 100644
index 0000000..dc1c807
--- /dev/null
+++ b/.swiftformat
@@ -0,0 +1,9 @@
+--extensionacl on-declarations
+--redundanttype explicit
+--swiftversion 5.5
+--maxwidth 120
+--header "{file}\nPickBetter\n\nCopyright © {year} MFB Technologies, Inc. All rights reserved. All rights reserved.\n\nThis source code is licensed under the MIT license found in the\nLICENSE file in the root directory of this source tree."
+--allman false
+--exclude **/*/output
+--wraparguments before-first
+--wrapcollections before-first
\ No newline at end of file
diff --git a/.swiftlint.yml b/.swiftlint.yml
new file mode 100644
index 0000000..a432951
--- /dev/null
+++ b/.swiftlint.yml
@@ -0,0 +1,26 @@
+disabled_rules:
+ - multiple_closures_with_trailing_closure # by SwiftUI
+ - trailing_comma # conflicts with SwiftFormat
+ - opening_brace # conflicts with SwiftFormat
+excluded: # paths to ignore during linting. Takes precedence over `included`.
+ - Carthage
+ - Pods
+ - ./**/.build
+ - ./**/output
+ - ./**/test_output
+ - ./**/Previews
+identifier_name:
+ allowed_symbols: "_"
+ excluded: # excluded via string array
+ - id
+ - to
+ - vm
+ - vc
+ - _min
+ - _max
+ - or
+ - by
+type_name:
+ allowed_symbols: "_"
+ excluded:
+ - ID
diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Example/Example-macOS/Example_macOS.entitlements b/Example/Example-macOS/Example_macOS.entitlements
new file mode 100644
index 0000000..f2ef3ae
--- /dev/null
+++ b/Example/Example-macOS/Example_macOS.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.files.user-selected.read-only
+
+
+
diff --git a/Example/Example-macOS/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example-macOS/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Example/Example-macOS/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example-tvOS/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example-tvOS/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Example/Example-tvOS/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..4ef6cc4
--- /dev/null
+++ b/Example/Example.xcodeproj/project.pbxproj
@@ -0,0 +1,827 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 55;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 4994CA7027EE23C100F54306 /* SingleOptionalValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA6F27EE23C100F54306 /* SingleOptionalValueTests.swift */; };
+ 4994CA7E27EE2ADA00F54306 /* ExampleUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA7D27EE2ADA00F54306 /* ExampleUITestCase.swift */; };
+ 4994CA8027EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA7F27EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift */; };
+ 4994CA8927EE72B300F54306 /* MutliValueUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA8727EE729C00F54306 /* MutliValueUITests.swift */; };
+ 49A241AE2800E7BA003DD15F /* XCUIElement+Trigger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A241AD2800E7BA003DD15F /* XCUIElement+Trigger.swift */; };
+ 49A241BD2800EBBD003DD15F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49A241BC2800EBBD003DD15F /* Preview Assets.xcassets */; };
+ 49A241EC2800EBF1003DD15F /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598B027B230AF00C1F2CF /* ExampleApp.swift */; };
+ 49A242052800EF34003DD15F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49A242042800EF34003DD15F /* Preview Assets.xcassets */; };
+ 49A242362800EF75003DD15F /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598B027B230AF00C1F2CF /* ExampleApp.swift */; };
+ 49B89D1127DBC66B004BB78F /* XCUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B89D1027DBC66B004BB78F /* XCUITestCase.swift */; };
+ 49BF84E52825867C006A9084 /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 49BF84E42825867C006A9084 /* Shared */; };
+ 49BF84E728258689006A9084 /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 49BF84E628258689006A9084 /* Shared */; };
+ 49BF84E92825CF49006A9084 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49BF84E82825CF49006A9084 /* Assets.xcassets */; };
+ 49BF84EA2825CF49006A9084 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49BF84E82825CF49006A9084 /* Assets.xcassets */; };
+ 49BF84EB2825CF49006A9084 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49BF84E82825CF49006A9084 /* Assets.xcassets */; };
+ 49BF84ED2825D01C006A9084 /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 49BF84EC2825D01C006A9084 /* Shared */; };
+ 49E598B127B230AF00C1F2CF /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598B027B230AF00C1F2CF /* ExampleApp.swift */; };
+ 49E598B827B230AF00C1F2CF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49E598B727B230AF00C1F2CF /* Preview Assets.xcassets */; };
+ 49E598CC27B230AF00C1F2CF /* SingleValueUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 49E598C827B230AF00C1F2CF /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 49E598A527B230AF00C1F2CF /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 49E598AC27B230AF00C1F2CF;
+ remoteInfo = Example;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 490F6D7B2800E4E200DAF85E /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; };
+ 4994CA6F27EE23C100F54306 /* SingleOptionalValueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleOptionalValueTests.swift; sourceTree = ""; };
+ 4994CA7D27EE2ADA00F54306 /* ExampleUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITestCase.swift; sourceTree = ""; };
+ 4994CA7F27EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElementQuery+ExactlyOneMatching.swift"; sourceTree = ""; };
+ 4994CA8727EE729C00F54306 /* MutliValueUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutliValueUITests.swift; sourceTree = ""; };
+ 49A241AD2800E7BA003DD15F /* XCUIElement+Trigger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Trigger.swift"; sourceTree = ""; };
+ 49A241B32800EBBC003DD15F /* Example-macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 49A241BC2800EBBD003DD15F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ 49A241BE2800EBBD003DD15F /* Example_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example_macOS.entitlements; sourceTree = ""; };
+ 49A241FB2800EF33003DD15F /* Example-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 49A242042800EF34003DD15F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ 49B89D1027DBC66B004BB78F /* XCUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUITestCase.swift; sourceTree = ""; };
+ 49BF84E228258594006A9084 /* Shared */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Shared; sourceTree = ""; };
+ 49BF84E328258666006A9084 /* Shared */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Shared; sourceTree = ""; };
+ 49BF84E82825CF49006A9084 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 49E598AD27B230AF00C1F2CF /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 49E598B027B230AF00C1F2CF /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; };
+ 49E598B727B230AF00C1F2CF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ 49E598C727B230AF00C1F2CF /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleValueUITests.swift; sourceTree = ""; };
+ 49E598DB27B2312500C1F2CF /* swiftui-pick-better */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "swiftui-pick-better"; path = ..; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 49A241B02800EBBC003DD15F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49BF84E728258689006A9084 /* Shared in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49A241F82800EF33003DD15F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49BF84ED2825D01C006A9084 /* Shared in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49E598AA27B230AF00C1F2CF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49BF84E52825867C006A9084 /* Shared in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49E598C427B230AF00C1F2CF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 49A241B42800EBBC003DD15F /* Example-macOS */ = {
+ isa = PBXGroup;
+ children = (
+ 49A241BE2800EBBD003DD15F /* Example_macOS.entitlements */,
+ 49A241BB2800EBBD003DD15F /* Preview Content */,
+ );
+ path = "Example-macOS";
+ sourceTree = "";
+ };
+ 49A241BB2800EBBD003DD15F /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ 49A241BC2800EBBD003DD15F /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+ 49A241FC2800EF33003DD15F /* Example-tvOS */ = {
+ isa = PBXGroup;
+ children = (
+ 49A242032800EF34003DD15F /* Preview Content */,
+ );
+ path = "Example-tvOS";
+ sourceTree = "";
+ };
+ 49A242032800EF34003DD15F /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ 49A242042800EF34003DD15F /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+ 49E598A427B230AF00C1F2CF = {
+ isa = PBXGroup;
+ children = (
+ 49BF84E228258594006A9084 /* Shared */,
+ 49E598DA27B2312400C1F2CF /* Packages */,
+ 49E598AF27B230AF00C1F2CF /* Example */,
+ 49E598CA27B230AF00C1F2CF /* ExampleUITests */,
+ 49A241B42800EBBC003DD15F /* Example-macOS */,
+ 49A241FC2800EF33003DD15F /* Example-tvOS */,
+ 49E598AE27B230AF00C1F2CF /* Products */,
+ 49E598DC27B2314400C1F2CF /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 49E598AE27B230AF00C1F2CF /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 49E598AD27B230AF00C1F2CF /* Example.app */,
+ 49E598C727B230AF00C1F2CF /* ExampleUITests.xctest */,
+ 49A241B32800EBBC003DD15F /* Example-macOS.app */,
+ 49A241FB2800EF33003DD15F /* Example-tvOS.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 49E598AF27B230AF00C1F2CF /* Example */ = {
+ isa = PBXGroup;
+ children = (
+ 49BF84E82825CF49006A9084 /* Assets.xcassets */,
+ 490F6D7B2800E4E200DAF85E /* Example.entitlements */,
+ 49E598B027B230AF00C1F2CF /* ExampleApp.swift */,
+ 49E598B627B230AF00C1F2CF /* Preview Content */,
+ );
+ path = Example;
+ sourceTree = "";
+ };
+ 49E598B627B230AF00C1F2CF /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ 49E598B727B230AF00C1F2CF /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+ 49E598CA27B230AF00C1F2CF /* ExampleUITests */ = {
+ isa = PBXGroup;
+ children = (
+ 4994CA8727EE729C00F54306 /* MutliValueUITests.swift */,
+ 49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */,
+ 4994CA6F27EE23C100F54306 /* SingleOptionalValueTests.swift */,
+ 49B89D1027DBC66B004BB78F /* XCUITestCase.swift */,
+ 4994CA7D27EE2ADA00F54306 /* ExampleUITestCase.swift */,
+ 4994CA7F27EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift */,
+ 49A241AD2800E7BA003DD15F /* XCUIElement+Trigger.swift */,
+ );
+ path = ExampleUITests;
+ sourceTree = "";
+ };
+ 49E598DA27B2312400C1F2CF /* Packages */ = {
+ isa = PBXGroup;
+ children = (
+ 49E598DB27B2312500C1F2CF /* swiftui-pick-better */,
+ 49BF84E328258666006A9084 /* Shared */,
+ );
+ name = Packages;
+ sourceTree = "";
+ };
+ 49E598DC27B2314400C1F2CF /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 49A241B22800EBBC003DD15F /* Example-macOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 49A241D52800EBBD003DD15F /* Build configuration list for PBXNativeTarget "Example-macOS" */;
+ buildPhases = (
+ 49A241AF2800EBBC003DD15F /* Sources */,
+ 49A241B02800EBBC003DD15F /* Frameworks */,
+ 49A241B12800EBBC003DD15F /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Example-macOS";
+ packageProductDependencies = (
+ 49BF84E628258689006A9084 /* Shared */,
+ );
+ productName = "Example-macOS";
+ productReference = 49A241B32800EBBC003DD15F /* Example-macOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 49A241FA2800EF33003DD15F /* Example-tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 49A2421C2800EF34003DD15F /* Build configuration list for PBXNativeTarget "Example-tvOS" */;
+ buildPhases = (
+ 49A241F72800EF33003DD15F /* Sources */,
+ 49A241F82800EF33003DD15F /* Frameworks */,
+ 49A241F92800EF33003DD15F /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Example-tvOS";
+ packageProductDependencies = (
+ 49BF84EC2825D01C006A9084 /* Shared */,
+ );
+ productName = "Example-tvOS";
+ productReference = 49A241FB2800EF33003DD15F /* Example-tvOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 49E598AC27B230AF00C1F2CF /* Example */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 49E598D127B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "Example" */;
+ buildPhases = (
+ 49E598A927B230AF00C1F2CF /* Sources */,
+ 49E598AA27B230AF00C1F2CF /* Frameworks */,
+ 49E598AB27B230AF00C1F2CF /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Example;
+ packageProductDependencies = (
+ 49BF84E42825867C006A9084 /* Shared */,
+ );
+ productName = Example;
+ productReference = 49E598AD27B230AF00C1F2CF /* Example.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 49E598C627B230AF00C1F2CF /* ExampleUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 49E598D727B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "ExampleUITests" */;
+ buildPhases = (
+ 49E598C327B230AF00C1F2CF /* Sources */,
+ 49E598C427B230AF00C1F2CF /* Frameworks */,
+ 49E598C527B230AF00C1F2CF /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 49E598C927B230AF00C1F2CF /* PBXTargetDependency */,
+ );
+ name = ExampleUITests;
+ productName = ExampleUITests;
+ productReference = 49E598C727B230AF00C1F2CF /* ExampleUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 49E598A527B230AF00C1F2CF /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1330;
+ LastUpgradeCheck = 1330;
+ TargetAttributes = {
+ 49A241B22800EBBC003DD15F = {
+ CreatedOnToolsVersion = 13.3;
+ };
+ 49A241FA2800EF33003DD15F = {
+ CreatedOnToolsVersion = 13.3;
+ };
+ 49E598AC27B230AF00C1F2CF = {
+ CreatedOnToolsVersion = 13.2.1;
+ };
+ 49E598C627B230AF00C1F2CF = {
+ CreatedOnToolsVersion = 13.2.1;
+ TestTargetID = 49E598AC27B230AF00C1F2CF;
+ };
+ };
+ };
+ buildConfigurationList = 49E598A827B230AF00C1F2CF /* Build configuration list for PBXProject "Example" */;
+ compatibilityVersion = "Xcode 13.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 49E598A427B230AF00C1F2CF;
+ productRefGroup = 49E598AE27B230AF00C1F2CF /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 49E598AC27B230AF00C1F2CF /* Example */,
+ 49E598C627B230AF00C1F2CF /* ExampleUITests */,
+ 49A241B22800EBBC003DD15F /* Example-macOS */,
+ 49A241FA2800EF33003DD15F /* Example-tvOS */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 49A241B12800EBBC003DD15F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49BF84EA2825CF49006A9084 /* Assets.xcassets in Resources */,
+ 49A241BD2800EBBD003DD15F /* Preview Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49A241F92800EF33003DD15F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49BF84EB2825CF49006A9084 /* Assets.xcassets in Resources */,
+ 49A242052800EF34003DD15F /* Preview Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49E598AB27B230AF00C1F2CF /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49BF84E92825CF49006A9084 /* Assets.xcassets in Resources */,
+ 49E598B827B230AF00C1F2CF /* Preview Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49E598C527B230AF00C1F2CF /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 49A241AF2800EBBC003DD15F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49A241EC2800EBF1003DD15F /* ExampleApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49A241F72800EF33003DD15F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49A242362800EF75003DD15F /* ExampleApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49E598A927B230AF00C1F2CF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49E598B127B230AF00C1F2CF /* ExampleApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 49E598C327B230AF00C1F2CF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 4994CA7E27EE2ADA00F54306 /* ExampleUITestCase.swift in Sources */,
+ 49E598CC27B230AF00C1F2CF /* SingleValueUITests.swift in Sources */,
+ 49B89D1127DBC66B004BB78F /* XCUITestCase.swift in Sources */,
+ 49A241AE2800E7BA003DD15F /* XCUIElement+Trigger.swift in Sources */,
+ 4994CA7027EE23C100F54306 /* SingleOptionalValueTests.swift in Sources */,
+ 4994CA8027EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift in Sources */,
+ 4994CA8927EE72B300F54306 /* MutliValueUITests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 49E598C927B230AF00C1F2CF /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 49E598AC27B230AF00C1F2CF /* Example */;
+ targetProxy = 49E598C827B230AF00C1F2CF /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 49A241D62800EBBD003DD15F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = "Example-macOS/Example_macOS.entitlements";
+ CODE_SIGN_IDENTITY = "-";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"Example-macOS/Preview Content\"";
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-macOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 49A241D72800EBBD003DD15F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = "Example-macOS/Example_macOS.entitlements";
+ CODE_SIGN_IDENTITY = "-";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"Example-macOS/Preview Content\"";
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-macOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 49A2421D2800EF34003DD15F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"Example-tvOS/Preview Content\"";
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 14.0;
+ };
+ name = Debug;
+ };
+ 49A2421E2800EF34003DD15F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"Example-tvOS/Preview Content\"";
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 14.0;
+ };
+ name = Release;
+ };
+ 49E598CF27B230AF00C1F2CF /* 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++17";
+ 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_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 = 14.0;
+ 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";
+ };
+ name = Debug;
+ };
+ 49E598D027B230AF00C1F2CF /* 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++17";
+ 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_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 = 14.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 49E598D227B230AF00C1F2CF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ 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 = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ MARKETING_VERSION = 1.0;
+ PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.Example";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TVOS_DEPLOYMENT_TARGET = 14.0;
+ WATCHOS_DEPLOYMENT_TARGET = 7.0;
+ };
+ name = Debug;
+ };
+ 49E598D327B230AF00C1F2CF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ 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 = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ MARKETING_VERSION = 1.0;
+ PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.Example";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TVOS_DEPLOYMENT_TARGET = 14.0;
+ WATCHOS_DEPLOYMENT_TARGET = 7.0;
+ };
+ name = Release;
+ };
+ 49E598D827B230AF00C1F2CF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.ExampleUITests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = Example;
+ };
+ name = Debug;
+ };
+ 49E598D927B230AF00C1F2CF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = D79J55YKW6;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.ExampleUITests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = Example;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 49A241D52800EBBD003DD15F /* Build configuration list for PBXNativeTarget "Example-macOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 49A241D62800EBBD003DD15F /* Debug */,
+ 49A241D72800EBBD003DD15F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 49A2421C2800EF34003DD15F /* Build configuration list for PBXNativeTarget "Example-tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 49A2421D2800EF34003DD15F /* Debug */,
+ 49A2421E2800EF34003DD15F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 49E598A827B230AF00C1F2CF /* Build configuration list for PBXProject "Example" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 49E598CF27B230AF00C1F2CF /* Debug */,
+ 49E598D027B230AF00C1F2CF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 49E598D127B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "Example" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 49E598D227B230AF00C1F2CF /* Debug */,
+ 49E598D327B230AF00C1F2CF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 49E598D727B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 49E598D827B230AF00C1F2CF /* Debug */,
+ 49E598D927B230AF00C1F2CF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 49BF84E42825867C006A9084 /* Shared */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = Shared;
+ };
+ 49BF84E628258689006A9084 /* Shared */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = Shared;
+ };
+ 49BF84EC2825D01C006A9084 /* Shared */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = Shared;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 49E598A527B230AF00C1F2CF /* Project object */;
+}
diff --git a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme
new file mode 100644
index 0000000..2f94a1d
--- /dev/null
+++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme
new file mode 100644
index 0000000..927218c
--- /dev/null
+++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..e12e779
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "platform" : "universal",
+ "reference" : "systemPinkColor"
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/Contents.json b/Example/Example/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Example/Example/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Example.entitlements b/Example/Example/Example.entitlements
new file mode 100644
index 0000000..ee95ab7
--- /dev/null
+++ b/Example/Example/Example.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+
+
diff --git a/Example/Example/ExampleApp.swift b/Example/Example/ExampleApp.swift
new file mode 100644
index 0000000..3aae123
--- /dev/null
+++ b/Example/Example/ExampleApp.swift
@@ -0,0 +1,19 @@
+// ExampleApp.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import Shared
+import SwiftUI
+
+@main
+struct ExampleApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/ExampleUITests/ExampleUITestCase.swift b/Example/ExampleUITests/ExampleUITestCase.swift
new file mode 100644
index 0000000..0ff1549
--- /dev/null
+++ b/Example/ExampleUITests/ExampleUITestCase.swift
@@ -0,0 +1,118 @@
+// ExampleUITestCase.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import XCTest
+#if os(iOS)
+ import Example
+#elseif os(macOS)
+ import Example_macOS
+#endif
+
+class ExampleUITestCase: XCUITestCase {
+ var _sectionNavItem: (() throws -> XCUIElement)? {
+ nil
+ }
+
+ func sectionNavItem() throws -> XCUIElement {
+ try XCTUnwrap(_sectionNavItem)()
+ }
+
+ var _picker: (() throws -> XCUIElement)? {
+ nil
+ }
+
+ func picker() throws -> XCUIElement {
+ try XCTUnwrap(_picker)()
+ }
+
+ override func setUpWithError() throws {
+ try super.setUpWithError()
+
+ #if os(iOS)
+ // iPad has a bug where view is blank when starting in portrait
+ // Let's rotate it landscape and back to work around that bug.
+ XCUIDevice.shared.orientation = .portrait
+ XCUIDevice.shared.orientation = .landscapeRight
+ XCUIDevice.shared.orientation = .portrait
+ #endif
+ }
+
+ func gridStyleToggle() throws -> XCUIElement {
+ try allElements().matching(identifier: "gridStyleToggle").firstMatch
+ }
+
+ func isGridStyleSelected() throws -> Bool {
+ try gridStyleToggle().label == "Grid Style"
+ }
+
+ func setGridStyle() throws {
+ if try !isGridStyleSelected() {
+ try gridStyleToggle().trigger()
+ }
+ XCTAssert(try isGridStyleSelected())
+ }
+
+ func setListStyle() throws {
+ if try isGridStyleSelected() {
+ try gridStyleToggle().trigger()
+ }
+ XCTAssert(try !isGridStyleSelected())
+ }
+
+ func singleValueNavItem() throws -> XCUIElement {
+ try allElements().matching(identifier: "singleValueSectionNavItem").firstMatch
+ }
+
+ func singleOptionalValueNavItem() throws -> XCUIElement {
+ try allElements().matching(identifier: "singleOptionalValueSectionNavItem").firstMatch
+ }
+
+ func multiValueNavItem() throws -> XCUIElement {
+ try allElements().matching(identifier: "multiValueSectionNavItem").firstMatch
+ }
+
+ func singleValuePicker() throws -> XCUIElement {
+ try allElements().matching(identifier: "singleValuePicker").exactlyOneMatch()
+ }
+
+ func singleOptionalValuePicker() throws -> XCUIElement {
+ try allElements().matching(identifier: "singleOptionalValuePicker").exactlyOneMatch()
+ }
+
+ func multiValuePicker() throws -> XCUIElement {
+ try allElements().matching(identifier: "multiValuePicker").exactlyOneMatch()
+ }
+
+ func cellZero() throws -> XCUIElement {
+ try allElements().children(matching: .cell)
+ .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 0", "Cell - 0, Selected"]))
+ }
+
+ func buttonZero() throws -> XCUIElement {
+ try allElements().children(matching: .button)
+ .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 0", "Cell - 0, Selected"]))
+ }
+
+ func cellOne() throws -> XCUIElement {
+ try allElements().children(matching: .cell)
+ .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 1", "Cell - 1, Selected"]))
+ }
+
+ func buttonOne() throws -> XCUIElement {
+ try allElements().children(matching: .button)
+ .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 1", "Cell - 1, Selected"]))
+ }
+
+ func button(_ buttonNumber: Int) throws -> XCUIElement {
+ try allElements().children(matching: .button)
+ .element(matching: elementCompoundOrPredicate(labeled: [
+ "Cell - \(buttonNumber)",
+ "Cell - \(buttonNumber), Selected",
+ ]))
+ }
+}
diff --git a/Example/ExampleUITests/MutliValueUITests.swift b/Example/ExampleUITests/MutliValueUITests.swift
new file mode 100644
index 0000000..480fdda
--- /dev/null
+++ b/Example/ExampleUITests/MutliValueUITests.swift
@@ -0,0 +1,72 @@
+// MutliValueUITests.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import XCTest
+#if os(iOS)
+ import Example
+#elseif os(macOS)
+ import Example_macOS
+#endif
+
+final class MultiValueUITests: ExampleUITestCase {
+ override var _sectionNavItem: (() throws -> XCUIElement)? {
+ multiValueNavItem
+ }
+
+ override var _picker: (() throws -> XCUIElement)? {
+ multiValuePicker
+ }
+
+ func sharedTestSteps() throws {
+ _ = try sectionNavItem().waitForExistence(timeout: 1)
+ XCTAssert(try sectionNavItem().exists, "Navigation link/tab for 'Multi' value picker must exist")
+ try sectionNavItem().trigger()
+
+ _ = try picker().waitForExistence(timeout: 1)
+ XCTAssert(try picker().exists, "Multi value picker must exist")
+ _ = try buttonZero().waitForExistence(timeout: 1)
+ _ = try buttonOne().waitForExistence(timeout: 1)
+
+ XCTAssert(try buttonZero().exists, "Button Zero must exist")
+ XCTAssert(try buttonOne().exists, "Button One must exist")
+ XCTAssert(
+ try !buttonZero().isSelected && (try !buttonOne().isSelected),
+ "Initial state should have no item selected"
+ )
+
+ try buttonZero().trigger()
+ XCTAssert(try buttonZero().isSelected, "'0' should be selected after being tapped")
+ XCTAssert(try !buttonOne().isSelected, "'1' should not be selected before being tapped.")
+
+ try buttonOne().trigger()
+ XCTAssert(try buttonZero().isSelected, "'0' should still be selected without being tapped again to deselect")
+ XCTAssert(try buttonOne().isSelected, "'1' should be selected after being tapped.")
+
+ try buttonOne().trigger()
+ XCTAssert(try buttonZero().isSelected, "'0' should still be selected without being tapped again to deselect")
+ XCTAssert(try !buttonOne().isSelected, "'1' should no longer be selected after being tapped again to deselect")
+
+ try buttonZero().trigger()
+ XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after being tapped again to deselect")
+ XCTAssert(try !buttonOne().isSelected, "'1' is still not selected after being deselected and '0' is deselected")
+ }
+
+ func testGridStyleDeselectAndSelectNew() throws {
+ _ = try gridStyleToggle().waitForExistence(timeout: 1)
+ try setGridStyle()
+
+ try sharedTestSteps()
+ }
+
+ func testListStyleDeselectAndSelectNew() throws {
+ _ = try gridStyleToggle().waitForExistence(timeout: 1)
+ try setListStyle()
+
+ try sharedTestSteps()
+ }
+}
diff --git a/Example/ExampleUITests/SingleOptionalValueTests.swift b/Example/ExampleUITests/SingleOptionalValueTests.swift
new file mode 100644
index 0000000..9418347
--- /dev/null
+++ b/Example/ExampleUITests/SingleOptionalValueTests.swift
@@ -0,0 +1,70 @@
+// SingleOptionalValueTests.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import XCTest
+#if os(iOS)
+ import Example
+#elseif os(macOS)
+ import Example_macOS
+#endif
+
+final class SingleOptionalValueUITests: ExampleUITestCase {
+ override var _sectionNavItem: (() throws -> XCUIElement)? {
+ singleOptionalValueNavItem
+ }
+
+ override var _picker: (() throws -> XCUIElement)? {
+ singleOptionalValuePicker
+ }
+
+ func sharedTestSteps() throws {
+ _ = try gridStyleToggle().waitForExistence(timeout: 1)
+ try setGridStyle()
+
+ _ = try sectionNavItem().waitForExistence(timeout: 1)
+ XCTAssert(try sectionNavItem().exists, "Navigation link/tab for 'Single' value picker must exist")
+ try sectionNavItem().trigger()
+
+ _ = try picker().waitForExistence(timeout: 1)
+ XCTAssert(try picker().exists, "Single value picker must exist")
+ _ = try buttonZero().waitForExistence(timeout: 1)
+ _ = try buttonOne().waitForExistence(timeout: 1)
+ XCTAssert(try buttonZero().exists, "Button Zero must exist")
+ XCTAssert(try buttonOne().exists, "Button One must exist")
+ XCTAssert(
+ try !buttonZero().isSelected && (try !buttonOne().isSelected),
+ "Initial state should have no item selected"
+ )
+
+ try buttonZero().trigger()
+ XCTAssert(try buttonZero().isSelected, "'0' should be selected after being tapped")
+ XCTAssert(try !buttonOne().isSelected, "'1' should not be selected after '0' is tapped.")
+
+ try buttonZero().trigger()
+ XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after being tapped while selected")
+ XCTAssert(try !buttonOne().isSelected, "'1' should not be selected before being tapped.")
+
+ try buttonOne().trigger()
+ XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after tapping a new item.")
+ XCTAssert(try buttonOne().isSelected, "'1' should be selected after it is tapped.")
+ }
+
+ func testGridStyleDeselectAndSelectNew() throws {
+ _ = try gridStyleToggle().waitForExistence(timeout: 1)
+ try setGridStyle()
+
+ try sharedTestSteps()
+ }
+
+ func testListStyleDeselectAndSelectNew() throws {
+ _ = try gridStyleToggle().waitForExistence(timeout: 1)
+ try setListStyle()
+
+ try sharedTestSteps()
+ }
+}
diff --git a/Example/ExampleUITests/SingleValueUITests.swift b/Example/ExampleUITests/SingleValueUITests.swift
new file mode 100644
index 0000000..459c8fd
--- /dev/null
+++ b/Example/ExampleUITests/SingleValueUITests.swift
@@ -0,0 +1,95 @@
+// SingleValueUITests.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import XCTest
+#if os(iOS)
+ import Example
+#elseif os(macOS)
+ import Example_macOS
+#endif
+
+final class SingleValueUITests: ExampleUITestCase {
+ override var _sectionNavItem: (() throws -> XCUIElement)? {
+ singleValueNavItem
+ }
+
+ override var _picker: (() throws -> XCUIElement)? {
+ singleValuePicker
+ }
+
+ func sharedTestSteps() throws {
+ _ = try sectionNavItem().waitForExistence(timeout: 1)
+ XCTAssert(try sectionNavItem().exists, "Navigation link/tab for 'Single' value picker must exist")
+ #if os(tvOS)
+ guard try findVertically(try sectionNavItem(), method: .upOnly) else {
+ XCTFail("Failed to find navigation picker")
+ return
+ }
+ #endif
+ try sectionNavItem().trigger()
+
+ _ = try picker().waitForExistence(timeout: 1)
+ XCTAssert(try picker().exists, "Single value picker must exist")
+ _ = try buttonZero().waitForExistence(timeout: 1)
+ _ = try buttonOne().waitForExistence(timeout: 1)
+ XCTAssert(try buttonZero().exists, "Button Zero must exist")
+ XCTAssert(try buttonOne().exists, "Button One must exist")
+ XCTAssert(try buttonZero().isSelected, "Initial state should have the first item selected which is '0'")
+
+ #if os(tvOS)
+ guard try findVertically(try button(3), method: .downOnly) else {
+ XCTFail("Failed to find '3' to select it.")
+ return
+ }
+ guard try findHorizontally(try buttonZero(), method: .leftOnly) else {
+ XCTFail("Failed to find '0'")
+ return
+ }
+ #endif
+ try buttonZero().trigger()
+ XCTAssert(try buttonZero().isSelected, "The 'Single' value picker does not allow de-selecting.")
+ XCTAssert(try !buttonOne().isSelected, "'1' should not be selected before being tapped.")
+
+ #if os(tvOS)
+ guard try findHorizontally(try buttonOne(), method: .rightOnly) else {
+ XCTFail("Failed to find '1' to select it.")
+ return
+ }
+ #endif
+ try buttonOne().trigger()
+ XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after tapping a new item.")
+ XCTAssert(try buttonOne().isSelected, "'1' should be selected after it is tapped.")
+ XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after tapping a new item.")
+ }
+
+ func testGridStyleDeselectAndSelectNew() throws {
+ _ = try gridStyleToggle().waitForExistence(timeout: 1)
+ #if os(tvOS)
+ guard try findVertically(try gridStyleToggle(), method: .downOnly) else {
+ XCTFail("Failed to find grid toggle")
+ return
+ }
+ #endif
+ try setGridStyle()
+
+ try sharedTestSteps()
+ }
+
+ func testListStyleDeselectAndSelectNew() throws {
+ _ = try gridStyleToggle().waitForExistence(timeout: 1)
+ #if os(tvOS)
+ guard try findVertically(try gridStyleToggle(), method: .downOnly) else {
+ XCTFail("Failed to find grid toggle")
+ return
+ }
+ #endif
+ try setListStyle()
+
+ try sharedTestSteps()
+ }
+}
diff --git a/Example/ExampleUITests/XCUIElement+Trigger.swift b/Example/ExampleUITests/XCUIElement+Trigger.swift
new file mode 100644
index 0000000..748debb
--- /dev/null
+++ b/Example/ExampleUITests/XCUIElement+Trigger.swift
@@ -0,0 +1,32 @@
+// XCUIElement+Trigger.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import Foundation
+import XCTest
+
+extension XCUIElement {
+ func trigger() {
+ #if os(iOS) || os(watchOS)
+ tap()
+ #elseif os(macOS)
+ click()
+ #elseif os(tvOS)
+ XCUIRemote.shared.press(.select)
+ #endif
+ }
+
+ func details() throws -> String {
+ guard identifier.isEmpty else {
+ return identifier
+ }
+ guard label.isEmpty else {
+ return label
+ }
+ return debugDescription
+ }
+}
diff --git a/Example/ExampleUITests/XCUIElementQuery+ExactlyOneMatching.swift b/Example/ExampleUITests/XCUIElementQuery+ExactlyOneMatching.swift
new file mode 100644
index 0000000..65aa1cf
--- /dev/null
+++ b/Example/ExampleUITests/XCUIElementQuery+ExactlyOneMatching.swift
@@ -0,0 +1,16 @@
+// XCUIElementQuery+ExactlyOneMatching.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import XCTest
+
+extension XCUIElementQuery {
+ public func exactlyOneMatch() throws -> XCUIElement {
+ XCTAssertEqual(count, 1, "Requiring only one element match the query resulting in `self`")
+ return firstMatch
+ }
+}
diff --git a/Example/ExampleUITests/XCUITestCase.swift b/Example/ExampleUITests/XCUITestCase.swift
new file mode 100644
index 0000000..938fc1a
--- /dev/null
+++ b/Example/ExampleUITests/XCUITestCase.swift
@@ -0,0 +1,210 @@
+// XCUITestCase.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import XCTest
+
+class XCUITestCase: XCTestCase {
+ private var _app: XCUIApplication?
+
+ func app() throws -> XCUIApplication {
+ try XCTUnwrap(_app)
+ }
+
+ func allElements() throws -> XCUIElementQuery {
+ try app().descendants(matching: .any)
+ }
+
+ func elementPredicate(labeled label: String) -> NSPredicate {
+ NSComparisonPredicate(
+ leftExpression: NSExpression(forKeyPath: \XCUIElement.label),
+ rightExpression: NSExpression(forConstantValue: label),
+ modifier: .direct,
+ type: .equalTo,
+ options: []
+ )
+ }
+
+ func elementCompoundOrPredicate(labeled labels: Set) -> NSPredicate {
+ NSCompoundPredicate(orPredicateWithSubpredicates: labels.map(elementPredicate(labeled:)))
+ }
+
+ override func setUpWithError() throws {
+ _app = XCUIApplication()
+ continueAfterFailure = false
+
+ try app().launch()
+ }
+}
+
+#if os(iOS) || os(tvOS)
+ extension XCUITestCase {
+ var focusPredicate: NSPredicate {
+ NSComparisonPredicate(
+ leftExpression: NSExpression(forKeyPath: \XCUIElement.hasFocus),
+ rightExpression: NSExpression(forConstantValue: true),
+ modifier: .direct,
+ type: .equalTo,
+ options: []
+ )
+ }
+
+ func currentFocus() throws -> XCUIElement {
+ try allElements().descendants(matching: .any).element(matching: focusPredicate)
+ }
+ }
+#endif
+
+#if os(tvOS)
+ enum VerticalFocusSearchMethod {
+ case downFirst
+ case upFirst
+ case downOnly
+ case upOnly
+
+ var singleDirection: Bool {
+ self == .downOnly || self == .upOnly
+ }
+
+ var firstDirection: FocusMoveDirection {
+ switch self {
+ case .downOnly, .downFirst:
+ return .down
+ case .upOnly, .upFirst:
+ return .up
+ }
+ }
+ }
+
+ enum HorizontalFocusSearchMethod {
+ case rightFirst
+ case leftFirst
+ case rightOnly
+ case leftOnly
+
+ var singleDirection: Bool {
+ self == .rightOnly || self == .leftOnly
+ }
+
+ var firstDirection: FocusMoveDirection {
+ switch self {
+ case .rightOnly, .rightFirst:
+ return .right
+ case .leftOnly, .leftFirst:
+ return .left
+ }
+ }
+ }
+
+ enum FocusMoveDirection {
+ case up
+ case down
+ case left
+ case right
+
+ func invert() -> Self {
+ switch self {
+ case .up:
+ return .down
+ case .down:
+ return .up
+ case .left:
+ return .right
+ case .right:
+ return .left
+ }
+ }
+
+ func progressForGridSearch() -> Self {
+ switch self {
+ case .up:
+ return .left
+ case .down:
+ return .right
+ case .left:
+ return .up
+ case .right:
+ return .down
+ }
+ }
+ }
+
+ extension FocusMoveDirection {
+ var remoteDirection: XCUIRemote.Button {
+ switch self {
+ case .up:
+ return .up
+ case .down:
+ return .down
+ case .left:
+ return .left
+ case .right:
+ return .right
+ }
+ }
+ }
+
+ extension XCUITestCase {
+ func findVertically(_ element: XCUIElement, method: VerticalFocusSearchMethod) throws -> Bool {
+ var isEndReached = false
+ var currentDirection: FocusMoveDirection = method.firstDirection
+ while !element.exists || !element.hasFocus {
+ let previous: String = try currentFocus().details()
+ moveFocus(direction: currentDirection)
+ let current: String = try currentFocus().details()
+ if previous == current {
+ if isEndReached || method.singleDirection {
+ return false
+ }
+ isEndReached = true
+ currentDirection = currentDirection.invert()
+ }
+ }
+ return true
+ }
+
+ func findHorizontally(_ element: XCUIElement, method: HorizontalFocusSearchMethod) throws -> Bool {
+ var isEndReached = false
+ var currentDirection: FocusMoveDirection = method.firstDirection
+ while !element.exists || !element.hasFocus {
+ let previous: String = try currentFocus().details()
+ moveFocus(direction: currentDirection)
+ let current: String = try currentFocus().details()
+ if previous == current {
+ if isEndReached || method.singleDirection {
+ return false
+ }
+ isEndReached = true
+ currentDirection = currentDirection.invert()
+ }
+ }
+ return true
+ }
+
+ func findInGrid(_ element: XCUIElement, firstDirection: FocusMoveDirection) throws -> Bool {
+ var isEndReached = false
+ var currentDirection: FocusMoveDirection = firstDirection
+ while !element.exists || !element.hasFocus {
+ let previous: String = try currentFocus().details()
+ moveFocus(direction: currentDirection)
+ let current: String = try currentFocus().details()
+ if previous == current {
+ if isEndReached {
+ return false
+ }
+ isEndReached = true
+ currentDirection = currentDirection.progressForGridSearch()
+ }
+ }
+ return true
+ }
+
+ func moveFocus(direction: FocusMoveDirection) {
+ XCUIRemote.shared.press(direction.remoteDirection)
+ }
+ }
+#endif
diff --git a/Example/Shared/.gitignore b/Example/Shared/.gitignore
new file mode 100644
index 0000000..3b29812
--- /dev/null
+++ b/Example/Shared/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
diff --git a/Example/Shared/Package.swift b/Example/Shared/Package.swift
new file mode 100644
index 0000000..dcf8f78
--- /dev/null
+++ b/Example/Shared/Package.swift
@@ -0,0 +1,31 @@
+// swift-tools-version: 5.4
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "Shared",
+ platforms: [
+ .iOS(.v14),
+ .macOS(.v11),
+ .watchOS(.v7),
+ .tvOS(.v14),
+ ],
+ products: [
+ .library(
+ name: "Shared",
+ targets: ["Shared"]
+ ),
+ ],
+ dependencies: [
+ .package(name: "swiftui-pick-better", path: "../../"),
+ ],
+ targets: [
+ .target(
+ name: "Shared",
+ dependencies: [
+ .product(name: "PickBetter", package: "swiftui-pick-better"),
+ ]
+ ),
+ ]
+)
diff --git a/Example/Shared/README.md b/Example/Shared/README.md
new file mode 100644
index 0000000..4cf9513
--- /dev/null
+++ b/Example/Shared/README.md
@@ -0,0 +1,3 @@
+# Shared
+
+A description of this package.
diff --git a/Example/Shared/Sources/Shared/BackPortedButtonToggleStyle.swift b/Example/Shared/Sources/Shared/BackPortedButtonToggleStyle.swift
new file mode 100644
index 0000000..258b6eb
--- /dev/null
+++ b/Example/Shared/Sources/Shared/BackPortedButtonToggleStyle.swift
@@ -0,0 +1,29 @@
+// BackPortedButtonToggleStyle.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import Foundation
+import SwiftUI
+
+public struct BackPortedButtonToggleStyle: ToggleStyle {
+ public func makeBody(configuration: Configuration) -> some View {
+ Button(
+ action: { configuration.$isOn.wrappedValue.toggle() },
+ label: {
+ ZStack {
+ if configuration.isOn {
+ Color.secondary
+ } else {
+ Color.clear
+ }
+ configuration.label
+ .foregroundColor(configuration.isOn ? .primary : .secondary)
+ }
+ }
+ )
+ }
+}
diff --git a/Example/Shared/Sources/Shared/ContentView.swift b/Example/Shared/Sources/Shared/ContentView.swift
new file mode 100644
index 0000000..36d6731
--- /dev/null
+++ b/Example/Shared/Sources/Shared/ContentView.swift
@@ -0,0 +1,227 @@
+// ContentView.swift
+// PickBetter
+//
+// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import PickBetter
+import SwiftUI
+
+public struct ContentView: View {
+ @State private var tab: TabOption = .singleValue
+ @State private var isGridStyle: Bool = true
+ @State private var isNavigationExpanded: Bool = true
+ #if os(iOS)
+ @Environment(\.horizontalSizeClass) var horizontalSizeClass
+ @Environment(\.verticalSizeClass) var verticalSizeClass
+ #endif
+ private let items: [Item] = (0 ..< 100).map { Item(id: $0) }
+
+ public init() {}
+
+ public var body: some View {
+ #if os(iOS)
+ NavigationView {
+ HStack(spacing: .zero) {
+ navigation()
+ mainBody()
+ }
+ .toolbar {
+ Toggle(
+ isOn: $isGridStyle,
+ label: { Text(isGridStyle ? "Grid Style" : "List Style") }
+ )
+ .accessibilityIdentifier(gridStyleToggle)
+ }
+ .navigationBarTitleDisplayMode(.inline)
+ }
+ .navigationViewStyle(StackNavigationViewStyle())
+ #elseif os(macOS)
+ NavigationView {
+ navigation()
+ }
+ .toolbar {
+ Toggle(
+ isOn: $isGridStyle,
+ label: { Text(isGridStyle ? "Grid Style" : "List Style") }
+ )
+ .accessibilityIdentifier(gridStyleToggle)
+ }
+ #elseif os(tvOS)
+ navigation()
+ #endif
+ }
+
+ // MARK: Accessibility Ids
+
+ private var gridStyleToggle: String { "gridStyleToggle" }
+ private var singleSectionNavItem: String { "singleValueSectionNavItem" }
+ private var singleOptionalSectionNavItem: String { "singleOptionalValueSectionNavItem" }
+ private var multiSectionNavItem: String { "multiValueSectionNavItem" }
+
+ // MARK: Content
+
+ private func singleValue() -> some View {
+ SingleValueSelection(items: items, isGridStyle: isGridStyle)
+ }
+
+ private var singleValueNavLabel: some View {
+ #if os(iOS)
+ Image(systemName: "1.circle")
+ #elseif os(macOS) || os(tvOS)
+ HStack {
+ Image(systemName: "1.circle")
+ Text("Single Value")
+ }
+ .font(.title)
+ #endif
+ }
+
+ private func singleOptionalValue() -> some View {
+ SingleOptionalValueSelection(items: items, isGridStyle: isGridStyle)
+ }
+
+ private var singleOptionalValueNavLabel: some View {
+ #if os(iOS)
+ Image(systemName: "questionmark.circle")
+ #elseif os(macOS) || os(tvOS)
+ HStack {
+ Image(systemName: "questionmark.circle")
+ Text("Single Optional Value")
+ }
+ .font(.title)
+ #endif
+ }
+
+ private func multiValue() -> some View {
+ MultiValueSelection(items: items, isGridStyle: isGridStyle)
+ }
+
+ private var multiValueNavLabel: some View {
+ #if os(iOS)
+ Image(systemName: "n.circle")
+ #elseif os(macOS) || os(tvOS)
+ HStack {
+ Image(systemName: "n.circle")
+ Text("Multi Value")
+ }
+ .font(.title)
+ #endif
+ }
+
+ private func navigationButton