diff --git a/0113-designing-dependencies-pt4/README.md b/0113-designing-dependencies-pt4/README.md index a940f986..f1ad55de 100644 --- a/0113-designing-dependencies-pt4/README.md +++ b/0113-designing-dependencies-pt4/README.md @@ -1,5 +1,5 @@ ## [Point-Free](https://www.pointfree.co) -> #### This directory contains code from Point-Free Episode: [Designing Dependencies: Reachability](https://www.pointfree.co/episodes/ep113-designing-dependencies-core-location) +> #### This directory contains code from Point-Free Episode: [Designing Dependencies: Core Location](https://www.pointfree.co/episodes/ep113-designing-dependencies-core-location) > > Now that we’ve tackled two dependencies of varying complexity we are ready to handle our more complicated dependency yet: Core Location. We will see what it means to control a dependency that communicates with a delegate and captures a complex state machine with many potential flows. diff --git a/0114-designing-dependencies-pt5/README.md b/0114-designing-dependencies-pt5/README.md index 2283276b..569ce618 100644 --- a/0114-designing-dependencies-pt5/README.md +++ b/0114-designing-dependencies-pt5/README.md @@ -1,5 +1,5 @@ ## [Point-Free](https://www.pointfree.co) -> #### This directory contains code from Point-Free Episode: [Designing Dependencies: Reachability](https://www.pointfree.co/episodes/ep113-designing-dependencies-core-location) +> #### This directory contains code from Point-Free Episode: [Designing Dependencies: The Point](https://www.pointfree.co/episodes/ep114-designing-dependencies-the-point) > > So, what’s the point of forgoing the protocols and designing dependencies with simple data types? It can be summed up in 3 words: testing, testing, testing. We can now easily write tests that exercise every aspect of our application, including its reliance on internet connectivity and location services. diff --git a/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.pbxproj b/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.pbxproj new file mode 100644 index 00000000..cc9b9355 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.pbxproj @@ -0,0 +1,483 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + DC0B829024EDC1C2003D2BF0 /* ArticlesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B827D24EDC1C1003D2BF0 /* ArticlesApp.swift */; }; + DC0B829124EDC1C2003D2BF0 /* ArticlesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B827D24EDC1C1003D2BF0 /* ArticlesApp.swift */; }; + DC0B829224EDC1C2003D2BF0 /* Vanilla.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B827E24EDC1C1003D2BF0 /* Vanilla.swift */; }; + DC0B829324EDC1C2003D2BF0 /* Vanilla.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B827E24EDC1C1003D2BF0 /* Vanilla.swift */; }; + DC0B829424EDC1C2003D2BF0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC0B827F24EDC1C2003D2BF0 /* Assets.xcassets */; }; + DC0B829524EDC1C2003D2BF0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC0B827F24EDC1C2003D2BF0 /* Assets.xcassets */; }; + DC0B829F24EDC234003D2BF0 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B829E24EDC234003D2BF0 /* Shared.swift */; }; + DC0B82A024EDC234003D2BF0 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B829E24EDC234003D2BF0 /* Shared.swift */; }; + DC0B82A224EDC37B003D2BF0 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B82A124EDC37B003D2BF0 /* ActivityIndicator.swift */; }; + DC0B82A324EDC37B003D2BF0 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B82A124EDC37B003D2BF0 /* ActivityIndicator.swift */; }; + DC0B82A624EDCC8E003D2BF0 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = DC0B82A524EDCC8E003D2BF0 /* ComposableArchitecture */; }; + DC0B82A824EDCCA0003D2BF0 /* Composable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B82A724EDCCA0003D2BF0 /* Composable.swift */; }; + DC0B82A924EDCCA0003D2BF0 /* Composable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B82A724EDCCA0003D2BF0 /* Composable.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + DC0B827D24EDC1C1003D2BF0 /* ArticlesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticlesApp.swift; sourceTree = ""; }; + DC0B827E24EDC1C1003D2BF0 /* Vanilla.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vanilla.swift; sourceTree = ""; }; + DC0B827F24EDC1C2003D2BF0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + DC0B828424EDC1C2003D2BF0 /* Articles.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Articles.app; sourceTree = BUILT_PRODUCTS_DIR; }; + DC0B828724EDC1C2003D2BF0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DC0B828C24EDC1C2003D2BF0 /* Articles.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Articles.app; sourceTree = BUILT_PRODUCTS_DIR; }; + DC0B828E24EDC1C2003D2BF0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DC0B828F24EDC1C2003D2BF0 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; + DC0B829E24EDC234003D2BF0 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; + DC0B82A124EDC37B003D2BF0 /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; }; + DC0B82A724EDCCA0003D2BF0 /* Composable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Composable.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + DC0B828124EDC1C2003D2BF0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC0B82A624EDCC8E003D2BF0 /* ComposableArchitecture in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC0B828924EDC1C2003D2BF0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + DC0B827724EDC1C1003D2BF0 = { + isa = PBXGroup; + children = ( + DC0B827C24EDC1C1003D2BF0 /* Shared */, + DC0B828624EDC1C2003D2BF0 /* iOS */, + DC0B828D24EDC1C2003D2BF0 /* macOS */, + DC0B828524EDC1C2003D2BF0 /* Products */, + ); + sourceTree = ""; + }; + DC0B827C24EDC1C1003D2BF0 /* Shared */ = { + isa = PBXGroup; + children = ( + DC0B827D24EDC1C1003D2BF0 /* ArticlesApp.swift */, + DC0B829E24EDC234003D2BF0 /* Shared.swift */, + DC0B827E24EDC1C1003D2BF0 /* Vanilla.swift */, + DC0B82A724EDCCA0003D2BF0 /* Composable.swift */, + DC0B82A124EDC37B003D2BF0 /* ActivityIndicator.swift */, + DC0B827F24EDC1C2003D2BF0 /* Assets.xcassets */, + ); + path = Shared; + sourceTree = ""; + }; + DC0B828524EDC1C2003D2BF0 /* Products */ = { + isa = PBXGroup; + children = ( + DC0B828424EDC1C2003D2BF0 /* Articles.app */, + DC0B828C24EDC1C2003D2BF0 /* Articles.app */, + ); + name = Products; + sourceTree = ""; + }; + DC0B828624EDC1C2003D2BF0 /* iOS */ = { + isa = PBXGroup; + children = ( + DC0B828724EDC1C2003D2BF0 /* Info.plist */, + ); + path = iOS; + sourceTree = ""; + }; + DC0B828D24EDC1C2003D2BF0 /* macOS */ = { + isa = PBXGroup; + children = ( + DC0B828E24EDC1C2003D2BF0 /* Info.plist */, + DC0B828F24EDC1C2003D2BF0 /* macOS.entitlements */, + ); + path = macOS; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + DC0B828324EDC1C2003D2BF0 /* Articles (iOS) */ = { + isa = PBXNativeTarget; + buildConfigurationList = DC0B829824EDC1C2003D2BF0 /* Build configuration list for PBXNativeTarget "Articles (iOS)" */; + buildPhases = ( + DC0B828024EDC1C2003D2BF0 /* Sources */, + DC0B828124EDC1C2003D2BF0 /* Frameworks */, + DC0B828224EDC1C2003D2BF0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Articles (iOS)"; + packageProductDependencies = ( + DC0B82A524EDCC8E003D2BF0 /* ComposableArchitecture */, + ); + productName = "Articles (iOS)"; + productReference = DC0B828424EDC1C2003D2BF0 /* Articles.app */; + productType = "com.apple.product-type.application"; + }; + DC0B828B24EDC1C2003D2BF0 /* Articles (macOS) */ = { + isa = PBXNativeTarget; + buildConfigurationList = DC0B829B24EDC1C2003D2BF0 /* Build configuration list for PBXNativeTarget "Articles (macOS)" */; + buildPhases = ( + DC0B828824EDC1C2003D2BF0 /* Sources */, + DC0B828924EDC1C2003D2BF0 /* Frameworks */, + DC0B828A24EDC1C2003D2BF0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Articles (macOS)"; + productName = "Articles (macOS)"; + productReference = DC0B828C24EDC1C2003D2BF0 /* Articles.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + DC0B827824EDC1C1003D2BF0 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1200; + LastUpgradeCheck = 1200; + TargetAttributes = { + DC0B828324EDC1C2003D2BF0 = { + CreatedOnToolsVersion = 12.0; + }; + DC0B828B24EDC1C2003D2BF0 = { + CreatedOnToolsVersion = 12.0; + }; + }; + }; + buildConfigurationList = DC0B827B24EDC1C1003D2BF0 /* Build configuration list for PBXProject "Articles" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = DC0B827724EDC1C1003D2BF0; + packageReferences = ( + DC0B82A424EDCC8E003D2BF0 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, + ); + productRefGroup = DC0B828524EDC1C2003D2BF0 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + DC0B828324EDC1C2003D2BF0 /* Articles (iOS) */, + DC0B828B24EDC1C2003D2BF0 /* Articles (macOS) */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + DC0B828224EDC1C2003D2BF0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC0B829424EDC1C2003D2BF0 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC0B828A24EDC1C2003D2BF0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC0B829524EDC1C2003D2BF0 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + DC0B828024EDC1C2003D2BF0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC0B82A224EDC37B003D2BF0 /* ActivityIndicator.swift in Sources */, + DC0B829F24EDC234003D2BF0 /* Shared.swift in Sources */, + DC0B829224EDC1C2003D2BF0 /* Vanilla.swift in Sources */, + DC0B82A824EDCCA0003D2BF0 /* Composable.swift in Sources */, + DC0B829024EDC1C2003D2BF0 /* ArticlesApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC0B828824EDC1C2003D2BF0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC0B82A324EDC37B003D2BF0 /* ActivityIndicator.swift in Sources */, + DC0B82A024EDC234003D2BF0 /* Shared.swift in Sources */, + DC0B829324EDC1C2003D2BF0 /* Vanilla.swift in Sources */, + DC0B82A924EDCCA0003D2BF0 /* Composable.swift in Sources */, + DC0B829124EDC1C2003D2BF0 /* ArticlesApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + DC0B829624EDC1C2003D2BF0 /* 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_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; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + DC0B829724EDC1C2003D2BF0 /* 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_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; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + DC0B829924EDC1C2003D2BF0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Articles; + PRODUCT_NAME = Articles; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DC0B829A24EDC1C2003D2BF0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Articles; + PRODUCT_NAME = Articles; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + DC0B829C24EDC1C2003D2BF0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = macOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Articles; + PRODUCT_NAME = Articles; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + DC0B829D24EDC1C2003D2BF0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = macOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Articles; + PRODUCT_NAME = Articles; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + DC0B827B24EDC1C1003D2BF0 /* Build configuration list for PBXProject "Articles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC0B829624EDC1C2003D2BF0 /* Debug */, + DC0B829724EDC1C2003D2BF0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC0B829824EDC1C2003D2BF0 /* Build configuration list for PBXNativeTarget "Articles (iOS)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC0B829924EDC1C2003D2BF0 /* Debug */, + DC0B829A24EDC1C2003D2BF0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC0B829B24EDC1C2003D2BF0 /* Build configuration list for PBXNativeTarget "Articles (macOS)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC0B829C24EDC1C2003D2BF0 /* Debug */, + DC0B829D24EDC1C2003D2BF0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + DC0B82A424EDCC8E003D2BF0 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.7.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + DC0B82A524EDCC8E003D2BF0 /* ComposableArchitecture */ = { + isa = XCSwiftPackageProductDependency; + package = DC0B82A424EDCC8E003D2BF0 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; + productName = ComposableArchitecture; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = DC0B827824EDC1C1003D2BF0 /* Project object */; +} diff --git a/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Articles.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/ActivityIndicator.swift b/0115-redacted-swiftui-pt1/Articles/Shared/ActivityIndicator.swift new file mode 100644 index 00000000..63fd75f0 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/ActivityIndicator.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct ActivityIndicator: UIViewRepresentable { + init() {} + + func makeUIView(context: Context) -> UIActivityIndicatorView { + let view = UIActivityIndicatorView(style: .large) + view.startAnimating() + return view + } + + func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {} +} diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/ArticlesApp.swift b/0115-redacted-swiftui-pt1/Articles/Shared/ArticlesApp.swift new file mode 100644 index 00000000..69749d4a --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/ArticlesApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct ArticlesApp: App { + var body: some Scene { + WindowGroup { + VanillaArticlesView() + } + } +} diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json b/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..c136eaff --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,148 @@ +{ + "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" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/Contents.json b/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/Composable.swift b/0115-redacted-swiftui-pt1/Articles/Shared/Composable.swift new file mode 100644 index 00000000..faa3e978 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/Composable.swift @@ -0,0 +1,179 @@ +import ComposableArchitecture +import SwiftUI + +struct ArticlesState: Equatable { + var articles: [Article] = [] + var isLoading = false + var readingArticle: Article? +} + +enum ArticlesAction { + case article(index: Int, action: ArticleAction) + case articlesResponse([Article]?) + case articleTapped(Article) + case dismissArticle + case onAppear +} + +enum ArticleAction { + case favoriteTapped + case hideTapped + case readLaterTapped +} + +let articlesReducer = Reducer { state, action, environment in + switch action { + case let .article(index: index, action: .favoriteTapped): + state.articles[index].isFavorite.toggle() + return .none + + case let .article(index: index, action: .hideTapped): + state.articles[index].isHidden.toggle() + return .none + + case let .article(index: index, action: .readLaterTapped): + state.articles[index].willReadLater.toggle() + return .none + + case let .articlesResponse(articles): + state.isLoading = false + state.articles = articles ?? [] + return .none + + case let .articleTapped(article): + state.readingArticle = article + return .none + + case .dismissArticle: + state.readingArticle = nil + return .none + + case .onAppear: + state.isLoading = true + return Effect(value: .articlesResponse(liveArticles)) + .delay(for: 4, scheduler: DispatchQueue.main) + .eraseToEffect() + } +} + +struct ComposableArticlesView: View { + let store: Store + + var body: some View { + WithViewStore(self.store) { viewStore in + NavigationView { + List { + if viewStore.isLoading { + ActivityIndicator() + .padding() + .frame(maxWidth: .infinity) + } + + ArticlesListView(store: self.store) + } + .sheet( + item: viewStore.binding(get: \.readingArticle, send: .dismissArticle) + ) { article in + NavigationView { + ArticleDetailView(article: article) + .navigationTitle(article.title) + } + } + .navigationTitle("Articles") + } + .onAppear { viewStore.send(.onAppear) } + } + } +} + +struct ArticlesListView: View { + let store: Store + + var body: some View { + WithViewStore(self.store) { viewStore in + ForEachStore( + self.store.scope(state: \.articles, action: ArticlesAction.article) + ) { articleStore in + WithViewStore(articleStore) { articleViewStore in + Button(action: { viewStore.send(.articleTapped(articleViewStore.state)) }) { + ArticleRowView(store: articleStore) + } + .buttonStyle(PlainButtonStyle()) + } + } + } + } +} + +private struct ArticleRowView: View { + let store: Store + + var body: some View { + WithViewStore(self.store) { viewStore in + HStack(alignment: .top) { + Image("") + .frame(width: 80, height: 80) + .background(Color(white: 0.9)) + .padding([.trailing]) + + VStack(alignment: .leading) { + Text(viewStore.title) + .font(.title) + + Text(articleDateFormatter.string(from: viewStore.date)) + .bold() + + Text(viewStore.blurb) + .padding(.top, 6) + + HStack { + Spacer() + + Button(action: { viewStore.send(.favoriteTapped) }) { + Image(systemName: "star.fill") + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(viewStore.isFavorite ? .red : .blue) + .padding() + + Button(action: { viewStore.send(.readLaterTapped) }) { + Image(systemName: "book.fill") + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(viewStore.willReadLater ? .yellow : .blue) + .padding() + + Button(action: { viewStore.send(.hideTapped) }) { + Image(systemName: "eye.slash.fill") + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(.blue) + .padding() + } + } + } + .padding([.top, .bottom]) + .buttonStyle(PlainButtonStyle()) + } + } +} + +private struct ArticleDetailView: View { + let article: Article + + var body: some View { + Text(self.article.blurb) + } +} + +struct Composable_Previews: PreviewProvider { + static var previews: some View { + ComposableArticlesView( + store: Store( + initialState: ArticlesState(), + reducer: articlesReducer, + environment: () + ) + ) + } +} diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/Shared.swift b/0115-redacted-swiftui-pt1/Articles/Shared/Shared.swift new file mode 100644 index 00000000..baf19963 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/Shared.swift @@ -0,0 +1,78 @@ +import Foundation + +struct Article: Equatable, Identifiable { + var blurb: String + var date: Date + let id: UUID + var isHidden = false + var isFavorite = false + var title: String + var willReadLater = false +} + +let placeholderArticles = (0...10).map { _ in + Article( + blurb: String(repeating: " ", count: .random(in: 50...100)), + date: .init(), + id: .init(), + isFavorite: Bool.random(), + title: String(repeating: " ", count: .random(in: 10...30)), + willReadLater: Bool.random() + ) +} + +let liveArticles = [ + Article( + blurb: "What makes functions special, contrasting them with the way we usually write code, and have some exploratory discussions about operators and composition.", + date: Date(timeIntervalSince1970: 1_517_206_269), + id: .init(), + title: "Functions" + ), + Article( + blurb: "Side effects: can’t live with ’em; can’t write a program without ’em. Let’s explore a few kinds of side effects we encounter every day, why they make code difficult to reason about and test, and how we can control them without losing composition.", + date: Date(timeIntervalSince1970: 1_517_811_069), + id: .init(), + title: "Side-effects" + ), + Article( + blurb: "Parsing is a difficult, but surprisingly ubiquitous programming problem, and functional programming has a lot to say about it. Let’s take a moment to understand the problem space of parsing, and see what tools Swift and Apple gives us to parse complex text formats.", + date: .init(timeIntervalSince1970: 1557122400), + id: .init(), + title: "Parsers" + ), + Article( + blurb: "In this article we will build a library from first principles inspired by protocol-oriented programming, and clearly show the downsides to this approach. Then, we’ll scrap the protocols, use simple concrete data types, and discover a whole new world of composition.", + date: .init(timeIntervalSince1970: 1545116400), + id: .init(), + title: "Snapshot testing" + ), + Article( + blurb: "Architecture is a tough problem and there’s no shortage of articles, videos and open source projects attempting to solve the problem once and for all. In this collection we systematically develop an architecture from first principles, with an eye on building something that is composable, modular, testable, and more.", + date: .init(timeIntervalSince1970: 1564984800), + id: .init(), + title: "Composable Architecture" + ), +] + +struct FullArticle: Equatable { + var body: String + var comments: [Comment] + + struct Comment: Equatable, Identifiable { + var body: String + var commenter: User + var date: Date + let id = UUID() + } +} + +struct User: Equatable { + var name: String +} + +let articleDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "EEEE MMM d, yyyy" + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter +}() diff --git a/0115-redacted-swiftui-pt1/Articles/Shared/Vanilla.swift b/0115-redacted-swiftui-pt1/Articles/Shared/Vanilla.swift new file mode 100644 index 00000000..08829442 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/Shared/Vanilla.swift @@ -0,0 +1,170 @@ +import SwiftUI + +class AppViewModel: ObservableObject { + @Published var articles: [Article] = [] + @Published var isLoading = false + @Published var readingArticle: Article? + + init() { + self.isLoading = true + + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + self.articles = liveArticles + self.isLoading = false + } + } + + func tapped(article: Article) { + self.readingArticle = article + } +} + +extension RedactionReasons { + static let loading = RedactionReasons(rawValue: 1) +} + +struct VanillaArticlesView: View { + @ObservedObject private var viewModel = AppViewModel() + + var body: some View { + NavigationView { + List { + if self.viewModel.isLoading { + ActivityIndicator() + .frame(maxWidth: .infinity) + .padding() + } + + ForEach( + self.viewModel.isLoading + ? placeholderArticles + : self.viewModel.articles.filter { !$0.isHidden } + ) { article in + Button( + action: { + guard !self.viewModel.isLoading else { return } + self.viewModel.tapped(article: article) + } + ) { + ArticleRowView(article: article) + } + } + .redacted(reason: self.viewModel.isLoading ? .placeholder : []) + .disabled(self.viewModel.isLoading) + } + .sheet(item: self.$viewModel.readingArticle) { article in + NavigationView { + ArticleDetailView(article: article) + .navigationTitle(article.title) + } + } + .navigationTitle("Articles") + } + } +} + +class ArticleViewModel: ObservableObject { + @Published var article: Article + + init(article: Article) { + self.article = article + } + + func favorite() { + // Make API request to favorite article on server + self.article.isFavorite.toggle() + } + + func readLater() { + // Make API request to add article to read later list + self.article.willReadLater.toggle() + } + + func hide() { + // Make API request to hide article so we never see it again + self.article.isHidden.toggle() + } +} + +private struct ArticleRowView: View { + @StateObject var viewModel: ArticleViewModel + @Environment(\.redactionReasons) var redactionReasons + + @State var value = 1 + + init(article: Article) { + self._viewModel = StateObject(wrappedValue: ArticleViewModel(article: article)) + } + + var body: some View { + HStack(alignment: .top) { + Image("") + .frame(width: 80, height: 80) + .background(Color.init(white: 0.9)) + .padding([.trailing]) + + VStack(alignment: .leading) { + Text(self.viewModel.article.title) + .font(.title) + + Text(articleDateFormatter.string(from: self.viewModel.article.date)) + .bold() + + Text(self.viewModel.article.blurb) + .padding(.top, 6) + + HStack { + Spacer() + + Button( + action: { + guard self.redactionReasons.isEmpty else { return } + self.viewModel.favorite() + }) { + Image(systemName: "star.fill") + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(self.viewModel.article.isFavorite ? .red : .blue) + .padding() + + Button( + action: { + guard self.redactionReasons.isEmpty else { return } + self.viewModel.readLater() + }) { + Image(systemName: "book.fill") + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(self.viewModel.article.willReadLater ? .yellow : .blue) + .padding() + + Button( + action: { + guard self.redactionReasons.isEmpty else { return } + self.viewModel.hide() + }) { + Image(systemName: "eye.slash.fill") + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(.blue) + .padding() + } + } + } + .padding([.top, .bottom]) + .buttonStyle(PlainButtonStyle()) + } +} + +fileprivate struct ArticleDetailView: View { + let article: Article + + var body: some View { + Text(article.blurb) + } +} +struct Vanilla_Previews: PreviewProvider { + static var previews: some View { + VanillaArticlesView() + } +} diff --git a/0115-redacted-swiftui-pt1/Articles/iOS/Info.plist b/0115-redacted-swiftui-pt1/Articles/iOS/Info.plist new file mode 100644 index 00000000..efc211a0 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/iOS/Info.plist @@ -0,0 +1,50 @@ + + + + + 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 + + + UIApplicationSupportsIndirectInputEvents + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/0115-redacted-swiftui-pt1/Articles/macOS/Info.plist b/0115-redacted-swiftui-pt1/Articles/macOS/Info.plist new file mode 100644 index 00000000..bacbc568 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/macOS/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + + diff --git a/0115-redacted-swiftui-pt1/Articles/macOS/macOS.entitlements b/0115-redacted-swiftui-pt1/Articles/macOS/macOS.entitlements new file mode 100644 index 00000000..f2ef3ae0 --- /dev/null +++ b/0115-redacted-swiftui-pt1/Articles/macOS/macOS.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/0115-redacted-swiftui-pt1/README.md b/0115-redacted-swiftui-pt1/README.md new file mode 100644 index 00000000..8c8c18fc --- /dev/null +++ b/0115-redacted-swiftui-pt1/README.md @@ -0,0 +1,6 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [█████ed SwiftUI: The Problem +](https://www.pointfree.co/episodes/ep115-redacted-swiftui-the-problem) +> +> SwiftUI has introduced the concept of “████ed views”, which gives you a really nice way to ████ the text and images from views. This is really powerful, but just because the view has been ████ed it doesn’t mean the logic has also been ████ed. We show why this is problematic and why we want to fix it. diff --git a/README.md b/README.md index 6d019995..58a75938 100644 --- a/README.md +++ b/README.md @@ -117,3 +117,4 @@ This repository is the home of code written on episodes of 1. [Designing Dependencies: Reachability](0112-designing-dependencies-pt3) 1. [Designing Dependencies: Core Location](0113-designing-dependencies-pt4) 1. [Designing Dependencies: The Point](0114-designing-dependencies-pt5) +1. [████ed SwiftUI: The Problem](0115-redacted-swiftui-pt1)