diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..660e398 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# macOS +.DS_Store + +# Xcode — user-specific state +xcuserdata/ +*.xcuserstate +*.xcscmblueprint +*.xccheckout + +# Xcode — build output +DerivedData/ +build/ +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# Swift Package Manager +.swiftpm/ +.build/ + +# CocoaPods (not used, prophylactic) +Pods/ + +# Carthage (not used, prophylactic) +Carthage/Build/ + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output diff --git a/README.md b/README.md index 33f9423..663afb3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ This example app is part of our blog article [How to Use the Coordinator Pattern in SwiftUI](https://quickbirdstudios.com/blog/coordinator-pattern-in-swiftui/). While the article introduces the different techniques and components of our approach to the Coordinator Pattern in SwiftUI on a general level, the Recipes App acts as a demonstration and can be used as a starting point to experimenting with it. In a follow-up article [Navigation and Deep-Links in SwiftUI](https://quickbirdstudios.com/blog/swiftui-navigation-deep-links/), we have further adapted the example app to use the [XUI library](https://github.com/quickbirdstudios/XUI). These adaptions can be found on the [xui branch](https://github.com/quickbirdstudios/SwiftUI-Coordinators-Example/tree/xui). + +## Requirements + +iOS 14+, Xcode 12+. Open `Recipes/Recipes.xcodeproj` and run the `Recipes (iOS)` scheme. + ## Recipes App The Recipes App lists different recipes with instructions on how to prepare it and ratings from previous users having tried it. In its current form, the app does not provide this functionality, but rather displays mock data. diff --git a/Recipes/Recipes.xcodeproj/project.pbxproj b/Recipes/Recipes.xcodeproj/project.pbxproj index 92c6612..ae44f20 100644 --- a/Recipes/Recipes.xcodeproj/project.pbxproj +++ b/Recipes/Recipes.xcodeproj/project.pbxproj @@ -8,51 +8,28 @@ /* Begin PBXBuildFile section */ 9B0BC35B25A34D9600C018D3 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0BC35A25A34D9600C018D3 /* SafariView.swift */; }; - 9B0BC36525A394E100C018D3 /* AsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B4FCF9225A2CFC5006BE60E /* AsyncImage.swift */; }; - 9B0BC36A25A394E400C018D3 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0BC35A25A34D9600C018D3 /* SafariView.swift */; }; - 9B0BC36F25A394E600C018D3 /* RatingStars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D00A9259FE1BE00684D22 /* RatingStars.swift */; }; - 9B0BC37425A3952700C018D3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D0097259FDFF100684D22 /* SettingsView.swift */; }; 9B4FCF9425A2CFC5006BE60E /* AsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B4FCF9225A2CFC5006BE60E /* AsyncImage.swift */; }; 9B8D0098259FDFF100684D22 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D0097259FDFF100684D22 /* SettingsView.swift */; }; 9B8D00AA259FE1BE00684D22 /* RatingStars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D00A9259FE1BE00684D22 /* RatingStars.swift */; }; 9BB59DC925A4BDB600946BFB /* RecipeListCoordinatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DC825A4BDB600946BFB /* RecipeListCoordinatorView.swift */; }; - 9BB59DCA25A4BDB600946BFB /* RecipeListCoordinatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DC825A4BDB600946BFB /* RecipeListCoordinatorView.swift */; }; 9BB59DD425A4BDF100946BFB /* RecipeListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DD325A4BDF100946BFB /* RecipeListCoordinator.swift */; }; - 9BB59DD525A4BDF100946BFB /* RecipeListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DD325A4BDF100946BFB /* RecipeListCoordinator.swift */; }; 9BB59DF825A4C19500946BFB /* SheetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DF725A4C19500946BFB /* SheetModifier.swift */; }; - 9BB59DF925A4C19500946BFB /* SheetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DF725A4C19500946BFB /* SheetModifier.swift */; }; 9BB59DFF25A4C1A000946BFB /* PopoverModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DFE25A4C1A000946BFB /* PopoverModifier.swift */; }; - 9BB59E0025A4C1A000946BFB /* PopoverModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DFE25A4C1A000946BFB /* PopoverModifier.swift */; }; 9BB59E0625A4C32200946BFB /* View+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E0525A4C32200946BFB /* View+Navigation.swift */; }; - 9BB59E0725A4C32200946BFB /* View+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E0525A4C32200946BFB /* View+Navigation.swift */; }; 9BB59E1E25A4C5BF00946BFB /* URL+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E1D25A4C5BF00946BFB /* URL+Identifiable.swift */; }; - 9BB59E1F25A4C5BF00946BFB /* URL+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E1D25A4C5BF00946BFB /* URL+Identifiable.swift */; }; 9BE362A62583CD1F00807BFC /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362A52583CD1F00807BFC /* Tests_iOS.swift */; }; - 9BE362B12583CD1F00807BFC /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362B02583CD1F00807BFC /* Tests_macOS.swift */; }; 9BE362B32583CD1F00807BFC /* RecipesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3628A2583CD1700807BFC /* RecipesApp.swift */; }; - 9BE362B42583CD1F00807BFC /* RecipesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3628A2583CD1700807BFC /* RecipesApp.swift */; }; 9BE362B72583CD1F00807BFC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9BE3628C2583CD1E00807BFC /* Assets.xcassets */; }; - 9BE362B82583CD1F00807BFC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9BE3628C2583CD1E00807BFC /* Assets.xcassets */; }; 9BE362CC2583CD3000807BFC /* Recipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362CB2583CD3000807BFC /* Recipe.swift */; }; - 9BE362CD2583CD3000807BFC /* Recipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362CB2583CD3000807BFC /* Recipe.swift */; }; 9BE362D32583CDB300807BFC /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362D22583CDB300807BFC /* HomeCoordinator.swift */; }; - 9BE362D42583CDB300807BFC /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362D22583CDB300807BFC /* HomeCoordinator.swift */; }; 9BE362DA2583CE0200807BFC /* RecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362D92583CE0200807BFC /* RecipeViewModel.swift */; }; - 9BE362DB2583CE0200807BFC /* RecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362D92583CE0200807BFC /* RecipeViewModel.swift */; }; 9BE362E12583CE2C00807BFC /* RatingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362E02583CE2C00807BFC /* RatingViewModel.swift */; }; - 9BE362E22583CE2C00807BFC /* RatingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362E02583CE2C00807BFC /* RatingViewModel.swift */; }; 9BE362E82583CE9400807BFC /* RecipeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362E72583CE9400807BFC /* RecipeService.swift */; }; - 9BE362E92583CE9400807BFC /* RecipeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362E72583CE9400807BFC /* RecipeService.swift */; }; 9BE363042583CFC400807BFC /* HomeCoordinatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE363032583CFC400807BFC /* HomeCoordinatorView.swift */; }; - 9BE363052583CFC400807BFC /* HomeCoordinatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE363032583CFC400807BFC /* HomeCoordinatorView.swift */; }; 9BE3630B2583CFCF00807BFC /* RecipeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3630A2583CFCF00807BFC /* RecipeList.swift */; }; - 9BE3630C2583CFCF00807BFC /* RecipeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3630A2583CFCF00807BFC /* RecipeList.swift */; }; 9BE363212583D19600807BFC /* RecipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE363202583D19600807BFC /* RecipeView.swift */; }; - 9BE363222583D19700807BFC /* RecipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE363202583D19600807BFC /* RecipeView.swift */; }; 9BE363282583D1A200807BFC /* RatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE363272583D1A200807BFC /* RatingView.swift */; }; - 9BE363292583D1A200807BFC /* RatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE363272583D1A200807BFC /* RatingView.swift */; }; 9BE3633B25841BA200807BFC /* RecipeListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3633A25841BA200807BFC /* RecipeListViewModel.swift */; }; - 9BE3633C25841BA200807BFC /* RecipeListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3633A25841BA200807BFC /* RecipeListViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,13 +40,6 @@ remoteGlobalIDString = 9BE362902583CD1E00807BFC; remoteInfo = "Recipes (iOS)"; }; - 9BE362AD2583CD1F00807BFC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 9BE362852583CD1600807BFC /* Project object */; - proxyType = 1; - remoteGlobalIDString = 9BE362982583CD1E00807BFC; - remoteInfo = "Recipes (macOS)"; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -87,13 +57,11 @@ 9BE3628C2583CD1E00807BFC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9BE362912583CD1E00807BFC /* Recipes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Recipes.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9BE362942583CD1E00807BFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9BE362992583CD1E00807BFC /* Recipes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Recipes.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9BE3629B2583CD1E00807BFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9BE3629C2583CD1E00807BFC /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; 9BE362A12583CD1F00807BFC /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9BE362A52583CD1F00807BFC /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; }; 9BE362A72583CD1F00807BFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9BE362AC2583CD1F00807BFC /* Tests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9BE362B02583CD1F00807BFC /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; }; 9BE362B22583CD1F00807BFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9BE362CB2583CD3000807BFC /* Recipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recipe.swift; sourceTree = ""; }; @@ -116,13 +84,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9BE362962583CD1E00807BFC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9BE3629E2583CD1F00807BFC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -130,13 +91,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9BE362A92583CD1F00807BFC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -224,9 +178,7 @@ isa = PBXGroup; children = ( 9BE362912583CD1E00807BFC /* Recipes.app */, - 9BE362992583CD1E00807BFC /* Recipes.app */, 9BE362A12583CD1F00807BFC /* Tests iOS.xctest */, - 9BE362AC2583CD1F00807BFC /* Tests macOS.xctest */, ); name = Products; sourceTree = ""; @@ -335,23 +287,6 @@ productReference = 9BE362912583CD1E00807BFC /* Recipes.app */; productType = "com.apple.product-type.application"; }; - 9BE362982583CD1E00807BFC /* Recipes (macOS) */ = { - isa = PBXNativeTarget; - buildConfigurationList = 9BE362BE2583CD1F00807BFC /* Build configuration list for PBXNativeTarget "Recipes (macOS)" */; - buildPhases = ( - 9BE362952583CD1E00807BFC /* Sources */, - 9BE362962583CD1E00807BFC /* Frameworks */, - 9BE362972583CD1E00807BFC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "Recipes (macOS)"; - productName = "Recipes (macOS)"; - productReference = 9BE362992583CD1E00807BFC /* Recipes.app */; - productType = "com.apple.product-type.application"; - }; 9BE362A02583CD1F00807BFC /* Tests iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 9BE362C12583CD1F00807BFC /* Build configuration list for PBXNativeTarget "Tests iOS" */; @@ -370,24 +305,6 @@ productReference = 9BE362A12583CD1F00807BFC /* Tests iOS.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; - 9BE362AB2583CD1F00807BFC /* Tests macOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 9BE362C42583CD1F00807BFC /* Build configuration list for PBXNativeTarget "Tests macOS" */; - buildPhases = ( - 9BE362A82583CD1F00807BFC /* Sources */, - 9BE362A92583CD1F00807BFC /* Frameworks */, - 9BE362AA2583CD1F00807BFC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 9BE362AE2583CD1F00807BFC /* PBXTargetDependency */, - ); - name = "Tests macOS"; - productName = "Tests macOS"; - productReference = 9BE362AC2583CD1F00807BFC /* Tests macOS.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -400,17 +317,10 @@ 9BE362902583CD1E00807BFC = { CreatedOnToolsVersion = 12.2; }; - 9BE362982583CD1E00807BFC = { - CreatedOnToolsVersion = 12.2; - }; 9BE362A02583CD1F00807BFC = { CreatedOnToolsVersion = 12.2; TestTargetID = 9BE362902583CD1E00807BFC; }; - 9BE362AB2583CD1F00807BFC = { - CreatedOnToolsVersion = 12.2; - TestTargetID = 9BE362982583CD1E00807BFC; - }; }; }; buildConfigurationList = 9BE362882583CD1600807BFC /* Build configuration list for PBXProject "Recipes" */; @@ -427,9 +337,7 @@ projectRoot = ""; targets = ( 9BE362902583CD1E00807BFC /* Recipes (iOS) */, - 9BE362982583CD1E00807BFC /* Recipes (macOS) */, 9BE362A02583CD1F00807BFC /* Tests iOS */, - 9BE362AB2583CD1F00807BFC /* Tests macOS */, ); }; /* End PBXProject section */ @@ -443,14 +351,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9BE362972583CD1E00807BFC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9BE362B82583CD1F00807BFC /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9BE3629F2583CD1F00807BFC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -458,13 +358,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9BE362AA2583CD1F00807BFC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -496,34 +389,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9BE362952583CD1E00807BFC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9BE3633C25841BA200807BFC /* RecipeListViewModel.swift in Sources */, - 9BB59E1F25A4C5BF00946BFB /* URL+Identifiable.swift in Sources */, - 9BE363222583D19700807BFC /* RecipeView.swift in Sources */, - 9BE362CD2583CD3000807BFC /* Recipe.swift in Sources */, - 9BE362E92583CE9400807BFC /* RecipeService.swift in Sources */, - 9BE363292583D1A200807BFC /* RatingView.swift in Sources */, - 9BE362B42583CD1F00807BFC /* RecipesApp.swift in Sources */, - 9BB59E0025A4C1A000946BFB /* PopoverModifier.swift in Sources */, - 9B0BC36A25A394E400C018D3 /* SafariView.swift in Sources */, - 9BB59E0725A4C32200946BFB /* View+Navigation.swift in Sources */, - 9BE362E22583CE2C00807BFC /* RatingViewModel.swift in Sources */, - 9B0BC37425A3952700C018D3 /* SettingsView.swift in Sources */, - 9BB59DCA25A4BDB600946BFB /* RecipeListCoordinatorView.swift in Sources */, - 9BE362DB2583CE0200807BFC /* RecipeViewModel.swift in Sources */, - 9BB59DD525A4BDF100946BFB /* RecipeListCoordinator.swift in Sources */, - 9BE363052583CFC400807BFC /* HomeCoordinatorView.swift in Sources */, - 9BE362D42583CDB300807BFC /* HomeCoordinator.swift in Sources */, - 9BB59DF925A4C19500946BFB /* SheetModifier.swift in Sources */, - 9B0BC36525A394E100C018D3 /* AsyncImage.swift in Sources */, - 9B0BC36F25A394E600C018D3 /* RatingStars.swift in Sources */, - 9BE3630C2583CFCF00807BFC /* RecipeList.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9BE3629D2583CD1F00807BFC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -532,14 +397,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9BE362A82583CD1F00807BFC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9BE362B12583CD1F00807BFC /* Tests_macOS.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -548,11 +405,6 @@ target = 9BE362902583CD1E00807BFC /* Recipes (iOS) */; targetProxy = 9BE362A22583CD1F00807BFC /* PBXContainerItemProxy */; }; - 9BE362AE2583CD1F00807BFC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 9BE362982583CD1E00807BFC /* Recipes (macOS) */; - targetProxy = 9BE362AD2583CD1F00807BFC /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -712,54 +564,6 @@ }; name = Release; }; - 9BE362BF2583CD1F00807BFC /* 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; - DEVELOPMENT_TEAM = 77E79NGPCV; - ENABLE_HARDENED_RUNTIME = 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 = com.quickbirdstudios.Recipes; - PRODUCT_NAME = Recipes; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 9BE362C02583CD1F00807BFC /* 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; - DEVELOPMENT_TEAM = 77E79NGPCV; - ENABLE_HARDENED_RUNTIME = 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 = com.quickbirdstudios.Recipes; - PRODUCT_NAME = Recipes; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; 9BE362C22583CD1F00807BFC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -805,50 +609,6 @@ }; name = Release; }; - 9BE362C52583CD1F00807BFC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 77E79NGPCV; - INFOPLIST_FILE = "Tests macOS/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.Tests-macOS"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = "Recipes (macOS)"; - }; - name = Debug; - }; - 9BE362C62583CD1F00807BFC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 77E79NGPCV; - INFOPLIST_FILE = "Tests macOS/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.Tests-macOS"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = "Recipes (macOS)"; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -870,15 +630,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9BE362BE2583CD1F00807BFC /* Build configuration list for PBXNativeTarget "Recipes (macOS)" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 9BE362BF2583CD1F00807BFC /* Debug */, - 9BE362C02583CD1F00807BFC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 9BE362C12583CD1F00807BFC /* Build configuration list for PBXNativeTarget "Tests iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -888,15 +639,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9BE362C42583CD1F00807BFC /* Build configuration list for PBXNativeTarget "Tests macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 9BE362C52583CD1F00807BFC /* Debug */, - 9BE362C62583CD1F00807BFC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 9BE362852583CD1600807BFC /* Project object */; diff --git a/Recipes/Recipes.xcodeproj/project.xcworkspace/xcuserdata/pauljohanneskraft.xcuserdatad/UserInterfaceState.xcuserstate b/Recipes/Recipes.xcodeproj/project.xcworkspace/xcuserdata/pauljohanneskraft.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 85da9ec..0000000 Binary files a/Recipes/Recipes.xcodeproj/project.xcworkspace/xcuserdata/pauljohanneskraft.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/Recipes/Recipes.xcodeproj/xcuserdata/pauljohanneskraft.xcuserdatad/xcschemes/xcschememanagement.plist b/Recipes/Recipes.xcodeproj/xcuserdata/pauljohanneskraft.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 3dd0406..0000000 --- a/Recipes/Recipes.xcodeproj/xcuserdata/pauljohanneskraft.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - SchemeUserState - - Recipes (iOS).xcscheme_^#shared#^_ - - orderHint - 1 - - Recipes (macOS).xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/Recipes/Shared/Extensions/URL+Identifiable.swift b/Recipes/Shared/Extensions/URL+Identifiable.swift index 456c829..ba9148b 100644 --- a/Recipes/Shared/Extensions/URL+Identifiable.swift +++ b/Recipes/Shared/Extensions/URL+Identifiable.swift @@ -7,7 +7,7 @@ import Foundation -extension URL: Identifiable { +extension URL: @retroactive Identifiable { public var id: String { absoluteString diff --git a/Recipes/Shared/Scenes/Home/HomeCoordinator.swift b/Recipes/Shared/Scenes/Home/HomeCoordinator.swift index f43603c..2848b09 100644 --- a/Recipes/Shared/Scenes/Home/HomeCoordinator.swift +++ b/Recipes/Shared/Scenes/Home/HomeCoordinator.swift @@ -1,5 +1,5 @@ // -// HomeViewModel.swift +// HomeCoordinator.swift // Recipes // // Created by Paul Kraft on 11.12.20. diff --git a/Recipes/Shared/Scenes/Home/HomeCoordinatorView.swift b/Recipes/Shared/Scenes/Home/HomeCoordinatorView.swift index 187857b..c163fea 100644 --- a/Recipes/Shared/Scenes/Home/HomeCoordinatorView.swift +++ b/Recipes/Shared/Scenes/Home/HomeCoordinatorView.swift @@ -1,5 +1,5 @@ // -// HomeCoordinator.swift +// HomeCoordinatorView.swift // Recipes // // Created by Paul Kraft on 11.12.20. diff --git a/Recipes/Shared/Scenes/RecipeList/RecipeListViewModel.swift b/Recipes/Shared/Scenes/RecipeList/RecipeListViewModel.swift index 2316cd3..f849042 100644 --- a/Recipes/Shared/Scenes/RecipeList/RecipeListViewModel.swift +++ b/Recipes/Shared/Scenes/RecipeList/RecipeListViewModel.swift @@ -7,12 +7,6 @@ import SwiftUI -extension Identifiable where ID: Hashable { - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - class RecipeListViewModel: ObservableObject { // MARK: Stored Properties diff --git a/Recipes/Shared/Scenes/Settings/SettingsView.swift b/Recipes/Shared/Scenes/Settings/SettingsView.swift index cb2d87a..73e3b6a 100644 --- a/Recipes/Shared/Scenes/Settings/SettingsView.swift +++ b/Recipes/Shared/Scenes/Settings/SettingsView.swift @@ -21,7 +21,7 @@ struct SettingsView: View { Text("Nymphenburger Str. 13-15") Text("80335 Munich") } - .frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/) + .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(Rectangle()) .onTapGesture(perform: openWebsite) .navigationTitle("Settings") diff --git a/Recipes/Shared/ViewModifiers/PopoverModifier.swift b/Recipes/Shared/ViewModifiers/PopoverModifier.swift index e2b9588..c178657 100644 --- a/Recipes/Shared/ViewModifiers/PopoverModifier.swift +++ b/Recipes/Shared/ViewModifiers/PopoverModifier.swift @@ -7,6 +7,9 @@ import SwiftUI +/// Presents `content(item)` as a popover whenever `item` is non-nil. +/// Pair with `SheetModifier` to let a coordinator pick the presentation +/// style for a leaf view based on device or size class. struct PopoverModifier: ViewModifier { // MARK: Stored Properties diff --git a/Recipes/Shared/ViewModifiers/SheetModifier.swift b/Recipes/Shared/ViewModifiers/SheetModifier.swift index 9c9d348..e8eb4d5 100644 --- a/Recipes/Shared/ViewModifiers/SheetModifier.swift +++ b/Recipes/Shared/ViewModifiers/SheetModifier.swift @@ -7,6 +7,11 @@ import SwiftUI +/// Presents `content(item)` as a sheet whenever `item` is non-nil. +/// +/// Exists as a `ViewModifier` so a coordinator can inject either this or +/// `PopoverModifier` into a leaf view, switching the presentation style +/// (e.g. by device or size class) without the leaf knowing about it. struct SheetModifier: ViewModifier { // MARK: Stored Properties diff --git a/Recipes/Shared/ViewModifiers/View+Navigation.swift b/Recipes/Shared/ViewModifiers/View+Navigation.swift index 872e501..c2b3bec 100644 --- a/Recipes/Shared/ViewModifiers/View+Navigation.swift +++ b/Recipes/Shared/ViewModifiers/View+Navigation.swift @@ -9,6 +9,9 @@ import SwiftUI extension View { + /// Wraps `self` in a hidden `NavigationLink` whose activation fires `action`. + /// Use when the tap should trigger an imperative side effect (typically a + /// coordinator method) rather than navigate to a statically known destination. func onNavigation(_ action: @escaping () -> Void) -> some View { let isActive = Binding( get: { false }, @@ -26,6 +29,10 @@ extension View { } } + /// Pushes `destination(item)` whenever `item` becomes non-nil; clearing + /// `item` (e.g. when the user pops) is propagated back through the binding. + /// Lets a coordinator drive navigation by mutating an optional `@Published` + /// view model rather than embedding a hardcoded `NavigationLink` in the view. func navigation( item: Binding, @ViewBuilder destination: (Item) -> Destination @@ -43,6 +50,9 @@ extension View { } } + /// Pushes `destination()` while `isActive` is true. The destination is + /// constructed lazily — only when the link is active — and the link itself + /// lives in an `overlay` so it doesn't disturb the receiver's layout. func navigation( isActive: Binding, @ViewBuilder destination: () -> Destination @@ -60,6 +70,9 @@ extension View { extension NavigationLink { + /// Optional-item-driven `NavigationLink`: active iff `item` is non-nil, + /// and pops by clearing the binding. Mirrors `sheet(item:content:)` + /// for push-style navigation. init(item: Binding, @ViewBuilder destination: (T) -> D, @ViewBuilder label: () -> Label) where Destination == D? { diff --git a/Recipes/Tests macOS/Info.plist b/Recipes/Tests macOS/Info.plist deleted file mode 100644 index 64d65ca..0000000 --- a/Recipes/Tests macOS/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - 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/Recipes/Tests macOS/Tests_macOS.swift b/Recipes/Tests macOS/Tests_macOS.swift deleted file mode 100644 index 5de9402..0000000 --- a/Recipes/Tests macOS/Tests_macOS.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Tests_macOS.swift -// Tests macOS -// -// Created by Paul Kraft on 11.12.20. -// - -import XCTest - -class Tests_macOS: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - 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 { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/Recipes/macOS/Info.plist b/Recipes/macOS/Info.plist deleted file mode 100644 index bacbc56..0000000 --- a/Recipes/macOS/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - 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/Recipes/macOS/macOS.entitlements b/Recipes/macOS/macOS.entitlements deleted file mode 100644 index f2ef3ae..0000000 --- a/Recipes/macOS/macOS.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - -