diff --git a/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.pbxproj b/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.pbxproj new file mode 100644 index 00000000..4d93149e --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.pbxproj @@ -0,0 +1,501 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 2A6F01D924A3BA3500C64BEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6F01D824A3BA3500C64BEF /* AppDelegate.swift */; }; + 2A6F01DB24A3BA3500C64BEF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6F01DA24A3BA3500C64BEF /* SceneDelegate.swift */; }; + 2A6F01DD24A3BA3500C64BEF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6F01DC24A3BA3500C64BEF /* ContentView.swift */; }; + 2A6F01DF24A3BA3600C64BEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A6F01DE24A3BA3600C64BEF /* Assets.xcassets */; }; + 2A6F01E224A3BA3600C64BEF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A6F01E124A3BA3600C64BEF /* Preview Assets.xcassets */; }; + 2A6F01E524A3BA3600C64BEF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A6F01E324A3BA3600C64BEF /* LaunchScreen.storyboard */; }; + 2A6F01F024A3BA3600C64BEF /* InventoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6F01EF24A3BA3600C64BEF /* InventoryTests.swift */; }; + 2A6F01FC24A3D96900C64BEF /* CasePaths in Frameworks */ = {isa = PBXBuildFile; productRef = 2A6F01FB24A3D96900C64BEF /* CasePaths */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 2A6F01EC24A3BA3600C64BEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2A6F01CD24A3BA3500C64BEF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2A6F01D424A3BA3500C64BEF; + remoteInfo = Inventory; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 2A6F01D524A3BA3500C64BEF /* Inventory.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Inventory.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A6F01D824A3BA3500C64BEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 2A6F01DA24A3BA3500C64BEF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 2A6F01DC24A3BA3500C64BEF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 2A6F01DE24A3BA3600C64BEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2A6F01E124A3BA3600C64BEF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2A6F01E424A3BA3600C64BEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 2A6F01E624A3BA3600C64BEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2A6F01EB24A3BA3600C64BEF /* InventoryTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InventoryTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A6F01EF24A3BA3600C64BEF /* InventoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InventoryTests.swift; sourceTree = ""; }; + 2A6F01F124A3BA3600C64BEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2A6F01D224A3BA3500C64BEF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A6F01FC24A3D96900C64BEF /* CasePaths in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A6F01E824A3BA3600C64BEF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2A6F01CC24A3BA3500C64BEF = { + isa = PBXGroup; + children = ( + 2A6F01D724A3BA3500C64BEF /* Inventory */, + 2A6F01EE24A3BA3600C64BEF /* InventoryTests */, + 2A6F01D624A3BA3500C64BEF /* Products */, + ); + sourceTree = ""; + }; + 2A6F01D624A3BA3500C64BEF /* Products */ = { + isa = PBXGroup; + children = ( + 2A6F01D524A3BA3500C64BEF /* Inventory.app */, + 2A6F01EB24A3BA3600C64BEF /* InventoryTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 2A6F01D724A3BA3500C64BEF /* Inventory */ = { + isa = PBXGroup; + children = ( + 2A6F01D824A3BA3500C64BEF /* AppDelegate.swift */, + 2A6F01DA24A3BA3500C64BEF /* SceneDelegate.swift */, + 2A6F01DC24A3BA3500C64BEF /* ContentView.swift */, + 2A6F01DE24A3BA3600C64BEF /* Assets.xcassets */, + 2A6F01E324A3BA3600C64BEF /* LaunchScreen.storyboard */, + 2A6F01E624A3BA3600C64BEF /* Info.plist */, + 2A6F01E024A3BA3600C64BEF /* Preview Content */, + ); + path = Inventory; + sourceTree = ""; + }; + 2A6F01E024A3BA3600C64BEF /* Preview Content */ = { + isa = PBXGroup; + children = ( + 2A6F01E124A3BA3600C64BEF /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 2A6F01EE24A3BA3600C64BEF /* InventoryTests */ = { + isa = PBXGroup; + children = ( + 2A6F01EF24A3BA3600C64BEF /* InventoryTests.swift */, + 2A6F01F124A3BA3600C64BEF /* Info.plist */, + ); + path = InventoryTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2A6F01D424A3BA3500C64BEF /* Inventory */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A6F01F424A3BA3600C64BEF /* Build configuration list for PBXNativeTarget "Inventory" */; + buildPhases = ( + 2A6F01D124A3BA3500C64BEF /* Sources */, + 2A6F01D224A3BA3500C64BEF /* Frameworks */, + 2A6F01D324A3BA3500C64BEF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Inventory; + packageProductDependencies = ( + 2A6F01FB24A3D96900C64BEF /* CasePaths */, + ); + productName = Inventory; + productReference = 2A6F01D524A3BA3500C64BEF /* Inventory.app */; + productType = "com.apple.product-type.application"; + }; + 2A6F01EA24A3BA3600C64BEF /* InventoryTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A6F01F724A3BA3600C64BEF /* Build configuration list for PBXNativeTarget "InventoryTests" */; + buildPhases = ( + 2A6F01E724A3BA3600C64BEF /* Sources */, + 2A6F01E824A3BA3600C64BEF /* Frameworks */, + 2A6F01E924A3BA3600C64BEF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2A6F01ED24A3BA3600C64BEF /* PBXTargetDependency */, + ); + name = InventoryTests; + productName = InventoryTests; + productReference = 2A6F01EB24A3BA3600C64BEF /* InventoryTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2A6F01CD24A3BA3500C64BEF /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "Point-Free"; + TargetAttributes = { + 2A6F01D424A3BA3500C64BEF = { + CreatedOnToolsVersion = 11.5; + }; + 2A6F01EA24A3BA3600C64BEF = { + CreatedOnToolsVersion = 11.5; + TestTargetID = 2A6F01D424A3BA3500C64BEF; + }; + }; + }; + buildConfigurationList = 2A6F01D024A3BA3500C64BEF /* Build configuration list for PBXProject "Inventory" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2A6F01CC24A3BA3500C64BEF; + packageReferences = ( + 2A6F01FA24A3D96900C64BEF /* XCRemoteSwiftPackageReference "swift-case-paths" */, + ); + productRefGroup = 2A6F01D624A3BA3500C64BEF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2A6F01D424A3BA3500C64BEF /* Inventory */, + 2A6F01EA24A3BA3600C64BEF /* InventoryTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2A6F01D324A3BA3500C64BEF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A6F01E524A3BA3600C64BEF /* LaunchScreen.storyboard in Resources */, + 2A6F01E224A3BA3600C64BEF /* Preview Assets.xcassets in Resources */, + 2A6F01DF24A3BA3600C64BEF /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A6F01E924A3BA3600C64BEF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2A6F01D124A3BA3500C64BEF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A6F01D924A3BA3500C64BEF /* AppDelegate.swift in Sources */, + 2A6F01DB24A3BA3500C64BEF /* SceneDelegate.swift in Sources */, + 2A6F01DD24A3BA3500C64BEF /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A6F01E724A3BA3600C64BEF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A6F01F024A3BA3600C64BEF /* InventoryTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 2A6F01ED24A3BA3600C64BEF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2A6F01D424A3BA3500C64BEF /* Inventory */; + targetProxy = 2A6F01EC24A3BA3600C64BEF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 2A6F01E324A3BA3600C64BEF /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 2A6F01E424A3BA3600C64BEF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 2A6F01F224A3BA3600C64BEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + 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; + }; + 2A6F01F324A3BA3600C64BEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2A6F01F524A3BA3600C64BEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Inventory/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Inventory/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Inventory; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2A6F01F624A3BA3600C64BEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Inventory/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Inventory/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Inventory; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 2A6F01F824A3BA3600C64BEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = InventoryTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.InventoryTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Inventory.app/Inventory"; + }; + name = Debug; + }; + 2A6F01F924A3BA3600C64BEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = InventoryTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.InventoryTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Inventory.app/Inventory"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2A6F01D024A3BA3500C64BEF /* Build configuration list for PBXProject "Inventory" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A6F01F224A3BA3600C64BEF /* Debug */, + 2A6F01F324A3BA3600C64BEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2A6F01F424A3BA3600C64BEF /* Build configuration list for PBXNativeTarget "Inventory" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A6F01F524A3BA3600C64BEF /* Debug */, + 2A6F01F624A3BA3600C64BEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2A6F01F724A3BA3600C64BEF /* Build configuration list for PBXNativeTarget "InventoryTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A6F01F824A3BA3600C64BEF /* Debug */, + 2A6F01F924A3BA3600C64BEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 2A6F01FA24A3D96900C64BEF /* XCRemoteSwiftPackageReference "swift-case-paths" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-case-paths"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.1.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 2A6F01FB24A3D96900C64BEF /* CasePaths */ = { + isa = XCSwiftPackageProductDependency; + package = 2A6F01FA24A3D96900C64BEF /* XCRemoteSwiftPackageReference "swift-case-paths" */; + productName = CasePaths; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 2A6F01CD24A3BA3500C64BEF /* Project object */; +} diff --git a/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/0109-composable-bindings-pt3/Inventory/Inventory/AppDelegate.swift b/0109-composable-bindings-pt3/Inventory/Inventory/AppDelegate.swift new file mode 100644 index 00000000..d3181028 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// Inventory +// +// Created by Point-Free on 6/24/20. +// Copyright © 2020 Point-Free. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/0109-composable-bindings-pt3/Inventory/Inventory/Assets.xcassets/AppIcon.appiconset/Contents.json b/0109-composable-bindings-pt3/Inventory/Inventory/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/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/0109-composable-bindings-pt3/Inventory/Inventory/Assets.xcassets/Contents.json b/0109-composable-bindings-pt3/Inventory/Inventory/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0109-composable-bindings-pt3/Inventory/Inventory/Base.lproj/LaunchScreen.storyboard b/0109-composable-bindings-pt3/Inventory/Inventory/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/0109-composable-bindings-pt3/Inventory/Inventory/ContentView.swift b/0109-composable-bindings-pt3/Inventory/Inventory/ContentView.swift new file mode 100644 index 00000000..954afe0b --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/ContentView.swift @@ -0,0 +1,350 @@ +import SwiftUI + +struct Item: Hashable, Identifiable { + let id = UUID() + var name: String + var color: Color? +// var quantity = 1 +// var isInStock = true +// var isOnBackOrder = false + var status: Status + + enum Status: Hashable { + case inStock(quantity: Int) + case outOfStock(isOnBackOrder: Bool) + + var isInStock: Bool { + guard case .inStock = self else { return false } + return true + } + +// var quantity: Int? { +// get { +// switch self { +// case .inStock(quantity: let quantity): +// return quantity +// case .outOfStock: +// return nil +// } +// } +// set { +//// switch self { +//// case .inStock: +//// self = .inStock(quantity: newValue) +//// case .outOfStock: +//// break +//// } +// guard let quantity = newValue else { return } +// self = .inStock(quantity: quantity) +// } +// } +// +// var isOnBackOrder: Bool? { +// get { +// guard case let .outOfStock(isOnBackOrder) = self else { +//// return false +//// return true +// return nil +// } +// return isOnBackOrder +// } +// set { +//// switch self { +//// case .inStock: +//// break +//// case .outOfStock: +// guard let newValue = newValue else { return } +// self = .outOfStock(isOnBackOrder: newValue) +//// } +// } +// } + } + + enum Color: String, CaseIterable { + case blue + case green + case black + case red + case yellow + case white + + var toSwiftUIColor: SwiftUI.Color { + switch self { + case .blue: + return .blue + case .green: + return .green + case .black: + return .black + case .red: + return .red + case .yellow: + return .yellow + case .white: + return .white + } + } + } + +// static func inStock( +// name: String, +// color: Color?, +// quantity: Int +// ) -> Self { +// Item(name: name, color: color, quantity: quantity, isInStock: true, isOnBackOrder: false) +// } +// +// static func outOfStock( +// name: String, +// color: Color?, +// isOnBackOrder: Bool +// ) -> Self { +// Item(name: name, color: color, quantity: 0, isInStock: false, isOnBackOrder: isOnBackOrder) +// } +} + +extension Binding { + + // (WritableKeyPath) -> (Binding) -> Binding + + // (WritableKeyPath) -> (Binding) -> Binding + + // ((A) -> B) -> ([A]) -> [B] + // ((A) -> B) -> (A?) -> B? + // ((A) -> B) -> (Result) -> Result + + + // pullback: ((A) -> B) -> (Predicate) -> Predicate + // pullback: ((A) -> B) -> (Snapshotting) -> Snapshotting + + + // pullback: (WritableKeyPath) -> (Reducer) -> Reducer + + + + func map(_ keyPath: WritableKeyPath) -> Binding { + + self[dynamicMember: keyPath] + +// Binding( +// get: { self.wrappedValue[keyPath: keyPath] }, +// set: { localValue in self.wrappedValue[keyPath: keyPath] = localValue } +// ) + } +} + +//extension Binding where Value == Optional { +extension Binding { + func unwrap() -> Binding? where Value == Wrapped? { + guard let value = self.wrappedValue else { return nil } + return Binding( + get: { value }, + set: { self.wrappedValue = $0 } + ) + } + + subscript( + casePath: CasePath + ) -> Binding? { + self.matching(casePath) + } + + func matching( + _ casePath: CasePath +// extract: @escaping (Value) -> Case?, +// embed: @escaping (Case) -> Value + ) -> Binding? { + guard let `case` = casePath.extract(from: self.wrappedValue) else { return nil } + return Binding( + get: { `case` }, + set: { `case` in self.wrappedValue = casePath.embed(`case`) } + ) + } +} + +import CasePaths + +struct ItemView: View { + @Binding var item: Item + + var body: some View { + Form { + TextField("Name", text: self.$item.name) + + Picker(selection: self.$item.color, label: Text("Color")) { + Text("None") + .tag(Item.Color?.none) + + ForEach(Item.Color.allCases, id: \.rawValue) { color in + Text(color.rawValue) + .tag(Optional(color)) + } + } + + self.$item.status[/Item.Status.inStock].map { quantity in +// if let quantity = self.$item.status.matching(/Item.Status.inStock) { + Section(header: Text("In stock")) { + Stepper("Quantity: \(quantity.wrappedValue)", value: quantity) + Button("Mark as sold out") { + self.item.status = .outOfStock(isOnBackOrder: false) + } + } + } + +// self.$item.status.quantity.unwrap().map { (quantity: Binding) in +// +// } + +// self.$item.status.isOnBackOrder.unwrap().map { isOnBackOrder in +// if let isOnBackOrder = self.$item.status[/Item.Status.outOfStock] { + self.$item.status[/Item.Status.outOfStock].map { isOnBackOrder in + Section(header: Text("Out of stock")) { + Toggle("Is on back order?", isOn: + isOnBackOrder) + Button("Is back in stock!") { + self.item.status = .inStock(quantity: 1) + } + } + } + } + } +} + +class InventoryViewModel: ObservableObject { + @Published var draft: Item? + @Published var inventory: [Item] + + init( + inventory: [Item] = [] + ) { + self.inventory = inventory + } + + func addButtonTapped() { + self.draft = Item(name: "", color: nil, status: .inStock(quantity: 1)) + } + + func cancelButtonTapped() { + self.draft = nil + } + + func saveButtonTapped() { + if let item = self.draft { + self.inventory.append(item) + } + self.draft = nil + } + + func duplicate(item: Item) { + self.draft = Item(name: item.name, color: item.color, status: item.status) // item.duplicate() + } +} + +struct InventoryView: View { + @ObservedObject var viewModel: InventoryViewModel + + var body: some View { + NavigationView { + List { + ForEach(self.viewModel.inventory, id: \.self) { item in + HStack { + VStack(alignment: .leading) { + Text(item.name) + + Group { () -> Text in + switch item.status { + case let .inStock(quantity): + return Text("In stock: \(quantity)") + case let .outOfStock(isOnBackOrder): + return Text("Out of stock" + (isOnBackOrder ? ": on back order" : "")) + } + } + } + + Spacer() + + item.color.map { color in + Rectangle() + .frame(width: 30, height: 30) + .foregroundColor(color.toSwiftUIColor) + .border(Color.black, width: 1) + } + + Button(action: { self.viewModel.duplicate(item: item) }) { + Image(systemName: "doc.on.doc.fill") + } + .padding(.leading) + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(item.status.isInStock ? nil : Color.gray) + } + } + .navigationBarTitle("Inventory") + .navigationBarItems( + trailing: Button("Add") { + self.viewModel.addButtonTapped() + } + ) +// .sheet(item: self.$viewModel.draft) { _ in +// self.$viewModel.draft.unwrap().map { item in + .sheet(unwrap: self.$viewModel.draft) { item in + NavigationView { + ItemView(item: item) + .navigationBarItems( + leading: Button("Cancel") { self.viewModel.cancelButtonTapped() }, + trailing: Button("Save") { self.viewModel.saveButtonTapped() } + ) + } + } + } + } +} + +extension View { + func sheet( + unwrap item: Binding, + content: @escaping (Binding) -> Content + ) -> some View where Item: Identifiable, Content: View { + self.sheet(item: item) { _ in + item.unwrap().map(content) + } + } +} + +struct ContentView: View { + var body: some View { + Text("Hello, World!") + } +} + +struct InventoryView_Previews: PreviewProvider { + static var previews: some View { + InventoryView( + viewModel: InventoryViewModel( + inventory: [ + Item(name: "Keyboard", color: .blue, status: .inStock(quantity: 100)), + Item(name: "Charger", color: .yellow, status: .inStock(quantity: 20)), + Item(name: "Phone", color: .green, status: .outOfStock(isOnBackOrder: true)), + Item(name: "Headphones", color: .green, status: .outOfStock(isOnBackOrder: false)), + ] + ) + ) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + + struct Wrapper: View { + @State var item = Item(name: "Keyboard", color: .green, status: .inStock(quantity: 1)) + + var body: some View { + ItemView(item: self.$item) + } + } + + return NavigationView { + Wrapper() + } + } +} diff --git a/0109-composable-bindings-pt3/Inventory/Inventory/Info.plist b/0109-composable-bindings-pt3/Inventory/Inventory/Info.plist new file mode 100644 index 00000000..9742bf0f --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/0109-composable-bindings-pt3/Inventory/Inventory/Preview Content/Preview Assets.xcassets/Contents.json b/0109-composable-bindings-pt3/Inventory/Inventory/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0109-composable-bindings-pt3/Inventory/Inventory/SceneDelegate.swift b/0109-composable-bindings-pt3/Inventory/Inventory/SceneDelegate.swift new file mode 100644 index 00000000..68ba2f1c --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/Inventory/SceneDelegate.swift @@ -0,0 +1,18 @@ +import UIKit +import SwiftUI + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + + let contentView = ContentView() + + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + } + } +} diff --git a/0109-composable-bindings-pt3/Inventory/InventoryTests/Info.plist b/0109-composable-bindings-pt3/Inventory/InventoryTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/InventoryTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/0109-composable-bindings-pt3/Inventory/InventoryTests/InventoryTests.swift b/0109-composable-bindings-pt3/Inventory/InventoryTests/InventoryTests.swift new file mode 100644 index 00000000..11de73c3 --- /dev/null +++ b/0109-composable-bindings-pt3/Inventory/InventoryTests/InventoryTests.swift @@ -0,0 +1,34 @@ +// +// InventoryTests.swift +// InventoryTests +// +// Created by Point-Free on 6/24/20. +// Copyright © 2020 Point-Free. All rights reserved. +// + +import XCTest +@testable import Inventory + +class InventoryTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/0109-composable-bindings-pt3/README.md b/0109-composable-bindings-pt3/README.md new file mode 100644 index 00000000..c4d18a7c --- /dev/null +++ b/0109-composable-bindings-pt3/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [Composable SwiftUI Bindings: The Point](https://www.pointfree.co/episodes/ep109-composable-swiftui-bindings-the-point) +> +> It’s time to ask: “what’s the point?” If composing bindings is so important, then why didn’t Apple give us more tools to do it? To understand this we will explore how Apple handles these kinds of problems in their code samples, and compare it to what we have discovered in previous episodes. diff --git a/README.md b/README.md index 99b77948..84d28033 100644 --- a/README.md +++ b/README.md @@ -111,3 +111,4 @@ This repository is the home of code written on episodes of 1. [Combine Schedulers: Erasing Time](0106-combine-schedulers-pt3) 1. [Composable SwiftUI Bindings: The Problem](0107-composable-bindings-pt1) 1. [Composable SwiftUI Bindings: Case Paths](0108-composable-bindings-pt2) +1. [Composable SwiftUI Bindings: The Point](0109-composable-bindings-pt3)