From c119ae91816691d77f47660628ae3dcaf3d51e8a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 27 Nov 2022 21:30:53 -0600 Subject: [PATCH] wip --- 0213-navigation-stacks-pt3/README.md | 5 + .../project.pbxproj | 469 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../StackExploration/ContentView.swift | 271 ++++++++++ .../StackExploration/Info.plist | 11 + .../Preview Assets.xcassets/Contents.json | 6 + .../StackExplorationApp.swift | 16 + .../StackExplorationTests.swift | 36 ++ README.md | 1 + 13 files changed, 860 insertions(+) create mode 100644 0213-navigation-stacks-pt3/README.md create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.pbxproj create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/Contents.json create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration/ContentView.swift create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration/Info.plist create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExploration/StackExplorationApp.swift create mode 100644 0213-navigation-stacks-pt3/StackExploration/StackExplorationTests/StackExplorationTests.swift diff --git a/0213-navigation-stacks-pt3/README.md b/0213-navigation-stacks-pt3/README.md new file mode 100644 index 00000000..9f4e1d58 --- /dev/null +++ b/0213-navigation-stacks-pt3/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [SwiftUI Navigation: Stacks](https://www.pointfree.co/episodes/ep213-swiftui-navigation-stacks) +> +> When all-new SwiftUI navigation tools were announced at WWDC, the one that got the most attention by far was NavigationStack, which powers navigation with an array. It is extremely powerful, but comes with trade-offs and new complexities. diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.pbxproj b/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c7157d02 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.pbxproj @@ -0,0 +1,469 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 4B4D9348291D9C340038F569 /* StackExplorationApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D9347291D9C340038F569 /* StackExplorationApp.swift */; }; + 4B4D934A291D9C340038F569 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D9349291D9C340038F569 /* ContentView.swift */; }; + 4B4D934C291D9C340038F569 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B4D934B291D9C340038F569 /* Assets.xcassets */; }; + 4B4D934F291D9C340038F569 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B4D934E291D9C340038F569 /* Preview Assets.xcassets */; }; + 4B4D9359291D9C350038F569 /* StackExplorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D9358291D9C350038F569 /* StackExplorationTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4B4D9355291D9C350038F569 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4B4D933C291D9C330038F569 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4B4D9343291D9C330038F569; + remoteInfo = StackExploration; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 2AA08C5B291DA2CE00564320 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 4B4D9344291D9C340038F569 /* StackExploration.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StackExploration.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B4D9347291D9C340038F569 /* StackExplorationApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackExplorationApp.swift; sourceTree = ""; }; + 4B4D9349291D9C340038F569 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 4B4D934B291D9C340038F569 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 4B4D934E291D9C340038F569 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 4B4D9354291D9C350038F569 /* StackExplorationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StackExplorationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B4D9358291D9C350038F569 /* StackExplorationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackExplorationTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4B4D9341291D9C330038F569 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4B4D9351291D9C350038F569 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4B4D933B291D9C330038F569 = { + isa = PBXGroup; + children = ( + 4B4D9346291D9C340038F569 /* StackExploration */, + 4B4D9357291D9C350038F569 /* StackExplorationTests */, + 4B4D9345291D9C340038F569 /* Products */, + ); + sourceTree = ""; + }; + 4B4D9345291D9C340038F569 /* Products */ = { + isa = PBXGroup; + children = ( + 4B4D9344291D9C340038F569 /* StackExploration.app */, + 4B4D9354291D9C350038F569 /* StackExplorationTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 4B4D9346291D9C340038F569 /* StackExploration */ = { + isa = PBXGroup; + children = ( + 2AA08C5B291DA2CE00564320 /* Info.plist */, + 4B4D9347291D9C340038F569 /* StackExplorationApp.swift */, + 4B4D9349291D9C340038F569 /* ContentView.swift */, + 4B4D934B291D9C340038F569 /* Assets.xcassets */, + 4B4D934D291D9C340038F569 /* Preview Content */, + ); + path = StackExploration; + sourceTree = ""; + }; + 4B4D934D291D9C340038F569 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 4B4D934E291D9C340038F569 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 4B4D9357291D9C350038F569 /* StackExplorationTests */ = { + isa = PBXGroup; + children = ( + 4B4D9358291D9C350038F569 /* StackExplorationTests.swift */, + ); + path = StackExplorationTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4B4D9343291D9C330038F569 /* StackExploration */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4B4D9368291D9C350038F569 /* Build configuration list for PBXNativeTarget "StackExploration" */; + buildPhases = ( + 4B4D9340291D9C330038F569 /* Sources */, + 4B4D9341291D9C330038F569 /* Frameworks */, + 4B4D9342291D9C330038F569 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StackExploration; + productName = StackExploration; + productReference = 4B4D9344291D9C340038F569 /* StackExploration.app */; + productType = "com.apple.product-type.application"; + }; + 4B4D9353291D9C350038F569 /* StackExplorationTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4B4D936B291D9C350038F569 /* Build configuration list for PBXNativeTarget "StackExplorationTests" */; + buildPhases = ( + 4B4D9350291D9C350038F569 /* Sources */, + 4B4D9351291D9C350038F569 /* Frameworks */, + 4B4D9352291D9C350038F569 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4B4D9356291D9C350038F569 /* PBXTargetDependency */, + ); + name = StackExplorationTests; + productName = StackExplorationTests; + productReference = 4B4D9354291D9C350038F569 /* StackExplorationTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4B4D933C291D9C330038F569 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1410; + LastUpgradeCheck = 1410; + TargetAttributes = { + 4B4D9343291D9C330038F569 = { + CreatedOnToolsVersion = 14.1; + }; + 4B4D9353291D9C350038F569 = { + CreatedOnToolsVersion = 14.1; + TestTargetID = 4B4D9343291D9C330038F569; + }; + }; + }; + buildConfigurationList = 4B4D933F291D9C330038F569 /* Build configuration list for PBXProject "StackExploration" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4B4D933B291D9C330038F569; + productRefGroup = 4B4D9345291D9C340038F569 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4B4D9343291D9C330038F569 /* StackExploration */, + 4B4D9353291D9C350038F569 /* StackExplorationTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4B4D9342291D9C330038F569 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B4D934F291D9C340038F569 /* Preview Assets.xcassets in Resources */, + 4B4D934C291D9C340038F569 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4B4D9352291D9C350038F569 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4B4D9340291D9C330038F569 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B4D934A291D9C340038F569 /* ContentView.swift in Sources */, + 4B4D9348291D9C340038F569 /* StackExplorationApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4B4D9350291D9C350038F569 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B4D9359291D9C350038F569 /* StackExplorationTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4B4D9356291D9C350038F569 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4B4D9343291D9C330038F569 /* StackExploration */; + targetProxy = 4B4D9355291D9C350038F569 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 4B4D9366291D9C350038F569 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + 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 = 16.1; + 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; + }; + 4B4D9367291D9C350038F569 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + 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 = 16.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4B4D9369291D9C350038F569 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"StackExploration/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = StackExploration/Info.plist; + 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"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.StackExploration; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4B4D936A291D9C350038F569 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"StackExploration/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = StackExploration/Info.plist; + 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"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.StackExploration; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 4B4D936C291D9C350038F569 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.StackExplorationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StackExploration.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/StackExploration"; + }; + name = Debug; + }; + 4B4D936D291D9C350038F569 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.StackExplorationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StackExploration.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/StackExploration"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4B4D933F291D9C330038F569 /* Build configuration list for PBXProject "StackExploration" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B4D9366291D9C350038F569 /* Debug */, + 4B4D9367291D9C350038F569 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4B4D9368291D9C350038F569 /* Build configuration list for PBXNativeTarget "StackExploration" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B4D9369291D9C350038F569 /* Debug */, + 4B4D936A291D9C350038F569 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4B4D936B291D9C350038F569 /* Build configuration list for PBXNativeTarget "StackExplorationTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B4D936C291D9C350038F569 /* Debug */, + 4B4D936D291D9C350038F569 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4B4D933C291D9C330038F569 /* Project object */; +} diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AccentColor.colorset/Contents.json b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AppIcon.appiconset/Contents.json b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/Contents.json b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration/ContentView.swift b/0213-navigation-stacks-pt3/StackExploration/StackExploration/ContentView.swift new file mode 100644 index 00000000..00e0e2f5 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration/ContentView.swift @@ -0,0 +1,271 @@ +import SwiftUI + +//struct CounterDestination: Hashable { +// var initialCount = 0 +// var fact: FactSheetState? +//} + +class AppModel: ObservableObject { + @Published var path: [Destination] { + didSet { + self.bind() + } + } + + init(path: [Destination] = []) { + self.path = path + self.bind() + } + + enum Destination: Hashable { + case counter(CounterModel) + case letter(String) + } + + func bind() { + for destination in self.path { + switch destination { + case let .counter(model): + model.onTapLetter = { [weak self] letter in + guard let self else { return } + self.path.append(.letter(letter)) + } + model.onTapNumber = { [weak self] number in + guard let self else { return } + self.path.append(.counter(CounterModel(count: number))) + } + + case .letter: + break + } + } + } +} + +struct ContentView: View { + @ObservedObject var model: AppModel +// { +// var path = NavigationPath() +// path.append("A") +// path.append("B") +// path.append("C") +// path.append(CounterModel(count: 42)) +// path.append( +// CounterModel( +// count: 1_000, +// fact: FactSheetState(message: "1,000 is the number of words a picture is worth.") +// ) +// ) +// return path +// }() + + var body: some View { + VStack { + Button { + self.randomStackButtonTapped() + } label: { + Text("Random stack") + } + Button { + var count = 0 + for destination in self.model.path { + guard case let .counter(counter) = destination + else { continue } + count += counter.count + } + print("Sum", count) + } label: { + Text("Sum the counts") + } + NavigationStack(path: self.$model.path) { + ListView() + .navigationDestination(for: AppModel.Destination.self) { destination in + switch destination { + case let .counter(model): + CounterView(model: model) + case let .letter(letter): + ListView() + .navigationTitle(Text(letter)) + } + } +// .navigationDestination(for: String.self) { letter in +// ListView() +// .navigationTitle(Text(letter)) +// } +// .navigationDestination(for: CounterModel.self) { model in +// CounterView(model: model) +//// ListView() +//// .navigationTitle(Text("\(number)")) +// } + .navigationTitle("Root") + } + .onChange(of: self.model.path) { path in + dump(path) + } + } + } + + func randomStackButtonTapped() { + self.model.path = [] + for _ in 0...Int.random(in: 3...6) { + if Bool.random() { + self.model.path.append(.counter(CounterModel(count: Int.random(in: 1...1_000)))) + } else { + self.model.path.append( + .letter( + String( + Character( + UnicodeScalar( + UInt32.random( + in: "A".unicodeScalars.first!.value..."Z".unicodeScalars.first!.value + ) + )! + ) + ) + ) + ) + } + } + } +} + +class CounterModel: ObservableObject, Hashable { + @Published var count = 0 + @Published var fact: FactSheetState? + + var onTapNumber: (Int) -> Void = { _ in } + var onTapLetter: (String) -> Void = { _ in } + + init( + count: Int = 0, + fact: FactSheetState? = nil + ) { + self.count = count + self.fact = fact + } + + func numberButtonTapped(number: Int) { + self.onTapNumber(number) + } + + func letterButtonTapped(letter: String) { + self.onTapLetter(letter) + } + + func decrementButtonTapped() { + self.count -= 1 + } + func incrementButtonTapped() { + self.count += 1 + } + func factButtonTapped() { + Task { @MainActor in + let (data, _) = try await URLSession.shared + .data(from: URL(string: "http://numbersapi.com/\(self.count)")!) + self.fact = FactSheetState( + message: String(decoding: data, as: UTF8.self) + ) + } + } + + static func == (lhs: CounterModel, rhs: CounterModel) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} + +struct CounterView: View { + @ObservedObject var model: CounterModel +// @StateObject var model = CounterModel() + + var body: some View { + List { + HStack { + Button("-") { self.model.decrementButtonTapped() } + Text("\(self.model.count)") + Button("+") { self.model.incrementButtonTapped() } + } + .buttonStyle(.borderless) + + Button { + self.model.factButtonTapped() + } label: { + Text("Number fact") + } + .buttonStyle(.borderless) + + Section { + Button { + self.model.letterButtonTapped(letter: "A") + } label: { + Text("Go to screen A") + } + Button { + self.model.letterButtonTapped(letter: "B") + } label: { + Text("Go to screen B") + } + Button { + self.model.letterButtonTapped(letter: "C") + } label: { + Text("Go to screen C") + } + Button { + self.model.numberButtonTapped(number: self.model.count) + } label: { + Text("Go to screen \(self.model.count)") + } +// .buttonStyle(.navigationLink) + +// NavigationLink(value: ContentView.Destination.letter("A")) { +// Text("Go to screen A") +// } +// NavigationLink(value: ContentView.Destination.letter("B")) { +// Text("Go to screen B") +// } +// NavigationLink(value: ContentView.Destination.letter("C")) { +// Text("Go to screen C") +// } +// NavigationLink(value: ContentView.Destination.counter(CounterModel(count: self.model.count))) { +// Text("Go to screen \(self.model.count)") +// } + } + } + .sheet(item: self.$model.fact) { fact in + Text(fact.message) + } + .navigationTitle(Text("\(self.model.count)")) + } +} +struct FactSheetState: Identifiable, Hashable { + var message = "" + var id: Self { self } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView(model: AppModel()) + } +} + +struct ListView: View { + var body: some View { + List { + NavigationLink(value: AppModel.Destination.letter("A")) { + Text("Go to screen A") + } + NavigationLink(value: AppModel.Destination.letter("B")) { + Text("Go to screen B") + } + NavigationLink(value: AppModel.Destination.letter("C")) { + Text("Go to screen C") + } + NavigationLink(value: AppModel.Destination.counter(CounterModel(count: 42))) { + Text("Go to screen 42") + } + } + } +} diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration/Info.plist b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Info.plist new file mode 100644 index 00000000..6a6654d9 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Info.plist @@ -0,0 +1,11 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration/Preview Content/Preview Assets.xcassets/Contents.json b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExploration/StackExplorationApp.swift b/0213-navigation-stacks-pt3/StackExploration/StackExploration/StackExplorationApp.swift new file mode 100644 index 00000000..a9555309 --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExploration/StackExplorationApp.swift @@ -0,0 +1,16 @@ +import SwiftUI + +@main +struct StackExplorationApp: App { + var body: some Scene { + WindowGroup { + ContentView( + model: AppModel( + path: [ + .counter(CounterModel()) + ] + ) + ) + } + } +} diff --git a/0213-navigation-stacks-pt3/StackExploration/StackExplorationTests/StackExplorationTests.swift b/0213-navigation-stacks-pt3/StackExploration/StackExplorationTests/StackExplorationTests.swift new file mode 100644 index 00000000..0dc04d0f --- /dev/null +++ b/0213-navigation-stacks-pt3/StackExploration/StackExplorationTests/StackExplorationTests.swift @@ -0,0 +1,36 @@ +// +// StackExplorationTests.swift +// StackExplorationTests +// +// Created by Point-Free on 11/10/22. +// + +import XCTest +@testable import StackExploration + +final class StackExplorationTests: 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. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + 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/README.md b/README.md index 1da6f7f0..cf2132ce 100644 --- a/README.md +++ b/README.md @@ -214,3 +214,4 @@ This repository is the home of code written on episodes of [Point-Free](https:// 1. [Clocks: Controlling Time](0210-clocks-pt2) 1. [SwiftUI Navigation: Recap](0211-navigation-stacks-pt1) 1. [SwiftUI Navigation: Decoupling](0212-navigation-stacks-pt2) +1. [SwiftUI Navigation: Stacks](0213-navigation-stacks-pt3)