Repository: FiveStarsBlog/CodeSamples Branch: main Commit: 110455689a05 Files: 161 Total size: 272.9 KB Directory structure: gitextract_1sm4fo4w/ ├── .gitignore ├── Adaptive-Views/ │ ├── AdaptiveViews/ │ │ ├── AdaptiveViews/ │ │ │ ├── AdaptiveExampleView.swift │ │ │ ├── AdaptiveView.swift │ │ │ ├── AdaptiveViewsApp.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── apple.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── google.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── twitter.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── ExperimentalView.swift │ │ │ ├── Info.plist │ │ │ ├── SignInButton.swift │ │ │ ├── SocialSignInView.swift │ │ │ └── View+ReadSize.swift │ │ └── AdaptiveViews.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── App-State-In-SwiftUI/ │ ├── AppState.swift │ └── README.md ├── Blending/ │ ├── README.md │ └── content.swift ├── Button-Styles/ │ ├── README.md │ └── content.swift ├── Composing-SwiftUI-Views/ │ ├── ComposingSwiftUIViews/ │ │ ├── ComposingSwiftUIViews/ │ │ │ ├── ComposingSwiftUIViewsApp.swift │ │ │ ├── FSTextField.swift │ │ │ ├── Info.plist │ │ │ └── _FSTextField.swift │ │ └── ComposingSwiftUIViews.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── Content-Friendly-Layouts/ │ ├── Flexible/ │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── FlexibleApp.swift │ │ ├── Info.plist │ │ ├── ReadjustingStackView.swift │ │ ├── SizeReader.swift │ │ └── _ReadjustingStackView.swift │ ├── Flexible.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── Custom-SwiftUI-Styles/ │ ├── ContentView.swift │ └── README.md ├── Displaying-Text-SwiftUI/ │ ├── DisplayingText/ │ │ ├── DisplayingText/ │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── DisplayingTextApp.swift │ │ │ ├── FSButton.swift │ │ │ ├── Info.plist │ │ │ ├── en.lproj/ │ │ │ │ └── Localizable.strings │ │ │ └── th.lproj/ │ │ │ └── Localizable.strings │ │ └── DisplayingText.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── Flexible-SwiftUI/ │ ├── Flexible/ │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── FlexibleApp.swift │ │ ├── FlexibleView.swift │ │ ├── Info.plist │ │ ├── SizeReader.swift │ │ └── _FlexibleView.swift │ ├── Flexible.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── Hashable-Bindings/ │ ├── ContentView.swift │ └── README.md ├── Hierarchy-List/ │ ├── ContentView-1.swift │ ├── ContentView-2.swift │ ├── ContentView-xcode-11.swift │ └── README.md ├── Identifiable-Navigation/ │ ├── IdentifiableNavigation/ │ │ ├── IdentifiableNavigation/ │ │ │ ├── ContentView.swift │ │ │ ├── IdentifiableNavigationApp.swift │ │ │ ├── Info.plist │ │ │ ├── NavigationLink+Identifiable.swift │ │ │ └── View+Navigation.swift │ │ └── IdentifiableNavigation.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── LICENSE ├── README.md ├── SafeAreaInset/ │ ├── README.md │ └── content.swift ├── ScrollView-Offset/ │ ├── README.md │ └── ScrollViewOffset.swift ├── Stack-vs-Grid/ │ ├── LazyStacks/ │ │ ├── LazyStacks/ │ │ │ ├── ContentView.swift │ │ │ ├── Info.plist │ │ │ ├── Label.swift │ │ │ ├── LazyStacksApp.swift │ │ │ └── LazyVStackMock.swift │ │ └── LazyStacks.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── SwiftUI-Clipping/ │ ├── Clipping/ │ │ ├── Clipping/ │ │ │ ├── ClippingApp.swift │ │ │ ├── ContentView.swift │ │ │ ├── FSViews.swift │ │ │ ├── Shapes.swift │ │ │ └── propertyWrapper+Clamping.swift │ │ └── Clipping.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── SwiftUI-HUD/ │ ├── README.md │ ├── global.swift │ └── local.swift ├── SwiftUI-Masking/ │ ├── Masking/ │ │ ├── Masking/ │ │ │ ├── ContentView.swift │ │ │ ├── FSViews.swift │ │ │ └── MaskingApp.swift │ │ └── Masking.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── README.md ├── SwiftUI-Reverse-Mask/ │ ├── README.md │ └── Reverse-Masking/ │ ├── Reverse-Masking/ │ │ ├── ContentView.swift │ │ ├── FSViews.swift │ │ ├── ReverseMaskingApp.swift │ │ ├── Star.swift │ │ └── View+reverseMask.swift │ └── Reverse-Masking.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── SwiftUI-read-a-view-size/ │ ├── README.md │ └── View+readSize.swift ├── Truncable-Text/ │ ├── ContentView.swift │ └── README.md └── Windows/ ├── README.md ├── SwiftUI-life-cycle/ │ ├── FSSwiftUILifecycleApp/ │ │ ├── FSAppDelegate.swift │ │ ├── FSSceneDelegate.swift │ │ ├── FSSwiftUILifecycleApp.swift │ │ ├── HudSceneView.swift │ │ ├── HudState.swift │ │ ├── MainSceneView.swift │ │ ├── PassThroughWindow.swift │ │ └── View+hud.swift │ └── FSSwiftUILifecycleApp.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist └── UIKit-life-cycle/ ├── FSUIKitLifecycleApp/ │ ├── Base.lproj/ │ │ └── LaunchScreen.storyboard │ ├── FSAppDelegate.swift │ ├── FSSceneDelegate.swift │ ├── HudSceneView.swift │ ├── HudState.swift │ ├── Info.plist │ ├── MainSceneView.swift │ ├── PassThroughWindow.swift │ └── View+hud.swift └── FSUIKitLifecycleApp.xcodeproj/ ├── project.pbxproj └── project.xcworkspace/ ├── contents.xcworkspacedata └── xcshareddata/ └── IDEWorkspaceChecks.plist ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/xcode,swift,swiftpackagemanager,swiftpm # Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift,swiftpackagemanager,swiftpm *.DS_Store ### Swift ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # Accio dependency management Dependencies/ .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ### SwiftPackageManager ### Packages xcuserdata #*.xcodeproj ### SwiftPM ### ### Xcode ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Gcc Patch /*.gcno ### Xcode Patch ### *.xcodeproj/* !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings # End of https://www.toptal.com/developers/gitignore/api/xcode,swift,swiftpackagemanager,swiftpm ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/AdaptiveExampleView.swift ================================================ // import SwiftUI struct AdaptiveExampleView: View { var body: some View { AdaptiveView { RoundedRectangle(cornerRadius: 40.0, style: .continuous) .fill( LinearGradient( gradient: Gradient(colors: [Color(red: 224 / 255.0, green: 21 / 255.0, blue: 90 / 255.0, opacity: 1), .pink]), startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(maxHeight: 400) VStack { Text("Title") .bold() .font(.title) Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") .fixedSize(horizontal: false, vertical: true) } } .padding() } } struct AdaptiveExampleView_Previews: PreviewProvider { static var previews: some View { Group { AdaptiveExampleView() .previewLayout(.fixed(width: 568, height: 420)) AdaptiveExampleView() .previewLayout(.fixed(width: 320, height: 528)) } } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/AdaptiveView.swift ================================================ // import SwiftUI // MARK: Size class // Run this on a plus size device in landscape or on an iPad to see the regular // size class. /* struct AdaptiveView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass var content: Content public init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { if horizontalSizeClass == .regular { HStack { content } } else { VStack { content } } } } */ // MARK: Dynamic Type // Change the system dynamic type to switch between layouts. /* struct AdaptiveView: View { @Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory var content: Content public init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { if sizeCategory.isAccessibilityCategory { VStack { content } } else { HStack { content } } } } */ // MARK: Custom threshold struct AdaptiveView: View { @Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory @State private var availableWidth: CGFloat = 0 var threshold: CGFloat var content: Content public init(threshold: CGFloat = 500, @ViewBuilder content: () -> Content) { self.threshold = threshold self.content = content() } var body: some View { ZStack { Color.clear .frame(height: 1) .readSize { size in availableWidth = size.width } if availableWidth > threshold { HStack { content } } else { VStack { content } } } } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/AdaptiveViewsApp.swift ================================================ // import SwiftUI @main struct AdaptiveViewsApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/apple.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Apple.pdf", "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/google.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Google.pdf", "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/twitter.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Twitter.pdf", "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/ContentView.swift ================================================ // import SwiftUI struct ContentView: View { var body: some View { NavigationView { List { NavigationLink("Image/Text", destination: AdaptiveExampleView()) NavigationLink("Social Sign In", destination: SocialSignInView()) NavigationLink("Experiment", destination: ExperimentalView()) } .navigationBarTitle("Adaptive View Examples", displayMode: .inline) } } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/ExperimentalView.swift ================================================ // import SwiftUI struct ExperimentalView: View { @State var currentWidth: CGFloat = 0 @State var padding: CGFloat = 8 @State var threshold: CGFloat = 100 var body: some View { VStack { AdaptiveView(threshold: threshold) { RoundedRectangle(cornerRadius: 40.0, style: .continuous) .fill( Color(red: 224 / 255.0, green: 21 / 255.0, blue: 90 / 255.0, opacity: 1) ) RoundedRectangle(cornerRadius: 40.0, style: .continuous) .fill( Color.pink ) } .readSize { size in currentWidth = size.width } .overlay( Rectangle() .stroke(lineWidth: 2) .frame(width: threshold) ) .padding(.horizontal, padding) Text("Current width: \(Int(currentWidth))") HStack { Text("Threshold: \(Int(threshold))") Slider(value: $threshold, in: 0...500, step: 1) { Text("") } } HStack { Text("Padding:") Slider(value: $padding, in: 0...500, step: 1) { Text("") } } } .padding() } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/Info.plist ================================================ 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 ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/SignInButton.swift ================================================ // import SwiftUI extension Color { static let appleTint = Color.black static let googleTint = Color(red: 222 / 255.0, green: 82 / 255.0, blue: 70 / 255.0) static let twitterTint = Color(red: 29 / 255.0, green: 161 / 255.0, blue: 242 / 255.0) } struct SignInButton: View { enum Mode { case regular case compact } var action: () -> Void var tintColor: Color var imageName: String var mode: Mode var body: some View { Button(action: action) { switch mode { case .compact: Circle() .fill(tintColor) .overlay(Image(imageName)) .frame(width: 44, height: 44) case .regular: HStack { Text("Sign in with") Image(imageName) } .padding() .background( Capsule() .fill(tintColor) ) } } .foregroundColor(.white) } } struct SignInButton_Previews: PreviewProvider { static var previews: some View { Group { SignInButton(action: {}, tintColor: .appleTint, imageName: "apple", mode: .regular) SignInButton(action: {}, tintColor: .appleTint, imageName: "apple", mode: .compact) SignInButton(action: {}, tintColor: .googleTint, imageName: "google", mode: .regular) SignInButton(action: {}, tintColor: .googleTint, imageName: "google", mode: .compact) SignInButton(action: {}, tintColor: .twitterTint, imageName: "twitter", mode: .regular) SignInButton(action: {}, tintColor: .twitterTint, imageName: "twitter", mode: .compact) } .previewLayout(.sizeThatFits) } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/SocialSignInView.swift ================================================ // import SwiftUI struct SocialSignInView: View { @State private var availableWidth: CGFloat = 0 private var buttonMode: SignInButton.Mode { availableWidth > 500 ? .regular : .compact } var body: some View { ZStack { Color.clear .frame(height: 1) .readSize { size in availableWidth = size.width } HStack { SignInButton(action: {}, tintColor: .appleTint, imageName: "apple", mode: buttonMode) SignInButton(action: {}, tintColor: .googleTint, imageName: "google", mode: buttonMode) SignInButton(action: {}, tintColor: .twitterTint, imageName: "twitter", mode: buttonMode) } } } } struct SocialSignInView_Previews: PreviewProvider { static var previews: some View { Group { SocialSignInView() .previewLayout(.fixed(width: 568, height: 320)) SocialSignInView() .previewLayout(.fixed(width: 320, height: 528)) } } } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews/View+ReadSize.swift ================================================ // import SwiftUI extension View { func readSize(onChange: @escaping (CGSize) -> Void) -> some View { background( GeometryReader { geometryProxy in Color.clear .preference(key: SizePreferenceKey.self, value: geometryProxy.size) } ) .onPreferenceChange(SizePreferenceKey.self, perform: onChange) } } private struct SizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 64B79F03253EBD9000168B88 /* AdaptiveViewsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F02253EBD9000168B88 /* AdaptiveViewsApp.swift */; }; 64B79F05253EBD9000168B88 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F04253EBD9000168B88 /* ContentView.swift */; }; 64B79F16253EBDE600168B88 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64B79F15253EBDE600168B88 /* Assets.xcassets */; }; 64B79F1E253EBDF100168B88 /* SocialSignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F18253EBDF100168B88 /* SocialSignInView.swift */; }; 64B79F1F253EBDF100168B88 /* ExperimentalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F19253EBDF100168B88 /* ExperimentalView.swift */; }; 64B79F20253EBDF100168B88 /* View+ReadSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F1A253EBDF100168B88 /* View+ReadSize.swift */; }; 64B79F21253EBDF100168B88 /* AdaptiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F1B253EBDF100168B88 /* AdaptiveView.swift */; }; 64B79F22253EBDF100168B88 /* AdaptiveExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F1C253EBDF100168B88 /* AdaptiveExampleView.swift */; }; 64B79F23253EBDF100168B88 /* SignInButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B79F1D253EBDF100168B88 /* SignInButton.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64B79EFF253EBD9000168B88 /* AdaptiveViews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AdaptiveViews.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64B79F02253EBD9000168B88 /* AdaptiveViewsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveViewsApp.swift; sourceTree = ""; }; 64B79F04253EBD9000168B88 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 64B79F0B253EBD9100168B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64B79F15253EBDE600168B88 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64B79F18253EBDF100168B88 /* SocialSignInView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialSignInView.swift; sourceTree = ""; }; 64B79F19253EBDF100168B88 /* ExperimentalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExperimentalView.swift; sourceTree = ""; }; 64B79F1A253EBDF100168B88 /* View+ReadSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+ReadSize.swift"; sourceTree = ""; }; 64B79F1B253EBDF100168B88 /* AdaptiveView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveView.swift; sourceTree = ""; }; 64B79F1C253EBDF100168B88 /* AdaptiveExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveExampleView.swift; sourceTree = ""; }; 64B79F1D253EBDF100168B88 /* SignInButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignInButton.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64B79EFC253EBD9000168B88 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64B79EF6253EBD9000168B88 = { isa = PBXGroup; children = ( 64B79F01253EBD9000168B88 /* AdaptiveViews */, 64B79F00253EBD9000168B88 /* Products */, ); sourceTree = ""; }; 64B79F00253EBD9000168B88 /* Products */ = { isa = PBXGroup; children = ( 64B79EFF253EBD9000168B88 /* AdaptiveViews.app */, ); name = Products; sourceTree = ""; }; 64B79F01253EBD9000168B88 /* AdaptiveViews */ = { isa = PBXGroup; children = ( 64B79F02253EBD9000168B88 /* AdaptiveViewsApp.swift */, 64B79F04253EBD9000168B88 /* ContentView.swift */, 64B79F0B253EBD9100168B88 /* Info.plist */, 64B79F15253EBDE600168B88 /* Assets.xcassets */, 64B79F1C253EBDF100168B88 /* AdaptiveExampleView.swift */, 64B79F1B253EBDF100168B88 /* AdaptiveView.swift */, 64B79F19253EBDF100168B88 /* ExperimentalView.swift */, 64B79F1D253EBDF100168B88 /* SignInButton.swift */, 64B79F18253EBDF100168B88 /* SocialSignInView.swift */, 64B79F1A253EBDF100168B88 /* View+ReadSize.swift */, ); path = AdaptiveViews; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64B79EFE253EBD9000168B88 /* AdaptiveViews */ = { isa = PBXNativeTarget; buildConfigurationList = 64B79F0E253EBD9100168B88 /* Build configuration list for PBXNativeTarget "AdaptiveViews" */; buildPhases = ( 64B79EFB253EBD9000168B88 /* Sources */, 64B79EFC253EBD9000168B88 /* Frameworks */, 64B79EFD253EBD9000168B88 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AdaptiveViews; productName = AdaptiveViews; productReference = 64B79EFF253EBD9000168B88 /* AdaptiveViews.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64B79EF7253EBD9000168B88 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1200; TargetAttributes = { 64B79EFE253EBD9000168B88 = { CreatedOnToolsVersion = 12.0.1; }; }; }; buildConfigurationList = 64B79EFA253EBD9000168B88 /* Build configuration list for PBXProject "AdaptiveViews" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 64B79EF6253EBD9000168B88; productRefGroup = 64B79F00253EBD9000168B88 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64B79EFE253EBD9000168B88 /* AdaptiveViews */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64B79EFD253EBD9000168B88 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64B79F16253EBDE600168B88 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64B79EFB253EBD9000168B88 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64B79F20253EBDF100168B88 /* View+ReadSize.swift in Sources */, 64B79F23253EBDF100168B88 /* SignInButton.swift in Sources */, 64B79F1E253EBDF100168B88 /* SocialSignInView.swift in Sources */, 64B79F21253EBDF100168B88 /* AdaptiveView.swift in Sources */, 64B79F05253EBD9000168B88 /* ContentView.swift in Sources */, 64B79F1F253EBDF100168B88 /* ExperimentalView.swift in Sources */, 64B79F22253EBDF100168B88 /* AdaptiveExampleView.swift in Sources */, 64B79F03253EBD9000168B88 /* AdaptiveViewsApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 64B79F0C253EBD9100168B88 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 64B79F0D253EBD9100168B88 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 64B79F0F253EBD9100168B88 /* 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 = AdaptiveViews/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.AdaptiveViews; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64B79F10253EBD9100168B88 /* 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 = AdaptiveViews/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.AdaptiveViews; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64B79EFA253EBD9000168B88 /* Build configuration list for PBXProject "AdaptiveViews" */ = { isa = XCConfigurationList; buildConfigurations = ( 64B79F0C253EBD9100168B88 /* Debug */, 64B79F0D253EBD9100168B88 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64B79F0E253EBD9100168B88 /* Build configuration list for PBXNativeTarget "AdaptiveViews" */ = { isa = XCConfigurationList; buildConfigurations = ( 64B79F0F253EBD9100168B88 /* Debug */, 64B79F10253EBD9100168B88 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64B79EF7253EBD9000168B88 /* Project object */; } ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Adaptive-Views/README.md ================================================ Final project from [Adaptive SwiftUI views][fs]. ![][gif] [fs]: https://fivestars.blog/swiftui/adaptive-swiftui-views.html [gif]: layouts.png ================================================ FILE: App-State-In-SwiftUI/AppState.swift ================================================ // Gist from https://fivestars.blog/swiftui/app-state.html import SwiftUI @main struct FiveStarsApp: App { @StateObject var appStateContainer = AppStateContainer() var body: some Scene { WindowGroup { ContentView() .environmentObject(appStateContainer) .environmentObject(appStateContainer.tabViewState) } } } enum Tab: Hashable { case home case settings } class TabViewState: ObservableObject { @Published var selectedTab: Tab = .home } class AppStateContainer: ObservableObject { var tabViewState = TabViewState() } struct ContentView: View { @EnvironmentObject var state: TabViewState var body: some View { TabView(selection: $state.selectedTab) { HomeView() .tabItem { Label("Home", systemImage: "house.fill") } .tag(Tab.home) SettingsView() .tabItem { Label("Settings", systemImage: "gear") } .tag(Tab.settings) } } } struct HomeView: View { @EnvironmentObject var container: AppStateContainer var body: some View { VStack { Button("go to settings") { container.tabViewState.selectedTab = .settings } Text("Home") } } } struct SettingsView: View { var body: some View { Text("Settings") } } ================================================ FILE: App-State-In-SwiftUI/README.md ================================================ Code snippet from [App-wide state in SwiftUI][fs]. [fs]: https://fivestars.blog/swiftui/app-state.html ================================================ FILE: Blending/README.md ================================================ Code snippet from [SwiftUI blending][fs]. [fs]: https://www.fivestars.blog/articles/swiftui-blend-modes/ ================================================ FILE: Blending/content.swift ================================================ import SwiftUI struct ContentView: View { let edge: Double = 600 let blendModes: [BlendMode] = [ .colorDodge, .lighten, .screen, .plusLighter, // Lighten .colorBurn, .darken, .multiply, .plusDarker, // Darken .overlay, .softLight, .hardLight, // Contrast .hue, .saturation, .color, .luminosity, // Component .sourceAtop, .destinationOver, .destinationOut, // Compositing .difference, .exclusion, // Invert .normal // Normal ] var body: some View { List { ForEach(blendModes, id: \.self) { blendMode in Section("\(blendMode)") { Matrix(blendMode: blendMode) .frame(width: edge, height: edge) .padding() } } } } } struct BlendingView: View { let blendMode: BlendMode let sourceView: SourceView let destinationView: DestinationView var body: some View { ZStack { destinationView sourceView .blendMode(blendMode) } } } struct Rainbow: View { let hueColors = stride(from: 0, to: 1, by: 0.01).map { Color(hue: $0, saturation: 1, brightness: 1) } var body: some View { LinearGradient( gradient: Gradient(colors: self.hueColors), startPoint: .leading, endPoint: .trailing ) } } struct BlackToWhite: View { var body: some View { LinearGradient( gradient: Gradient(colors: [.black, .white]), startPoint: .top, endPoint: .bottom ) } } struct BlackToTransparent: View { var body: some View { LinearGradient( gradient: Gradient(colors: [.black, .black.opacity(0)]), startPoint: .top, endPoint: .bottom ) } } struct WhiteToTransparent: View { var body: some View { LinearGradient( gradient: Gradient(colors: [.white, .white.opacity(0)]), startPoint: .top, endPoint: .bottom ) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .previewLayout(.fixed(width: 300, height: 1000)) } } struct Matrix: View { let blendMode: BlendMode var body: some View { VStack { HStack { Color.clear .overlay(alignment: .trailing) { Text("Destination →") } .overlay(alignment: .bottom) { Text("Source\n↓") .multilineTextAlignment(.center) } Rainbow() BlackToWhite() } MatrixRow(blendMode: blendMode, mainView: Rainbow()) MatrixRow(blendMode: blendMode, mainView: BlackToWhite()) // MatrixRow(blendMode: blendMode, mainView: BlackToTransparent()) // MatrixRow(blendMode: blendMode, mainView: WhiteToTransparent()) } } } struct MatrixRow: View { let blendMode: BlendMode let mainView: MainView var body: some View { HStack { mainView BlendingView(blendMode: blendMode, sourceView: mainView, destinationView: Rainbow()) BlendingView(blendMode: blendMode, sourceView: mainView, destinationView: BlackToWhite()) // BlendingView(blendMode: blendMode, sourceView: mainView, destinationView: BlackToTransparent()) // BlendingView(blendMode: blendMode, sourceView: mainView, destinationView: WhiteToTransparent()) } } } ================================================ FILE: Button-Styles/README.md ================================================ Code snippet from [Meet the new Button styling][fs]. ![][gif] [fs]: https://www.fivestars.blog/articles/button-styles-2/ [gif]: gist.png ================================================ FILE: Button-Styles/content.swift ================================================ import SwiftUI /// Usage: /// Button(...) { /// ... /// } /// .buttonStyle(.fiveStars) extension ButtonStyle where Self == FiveStarsButtonStyle { static func fiveStars(cornerRadius: Double = 8) -> Self { FiveStarsButtonStyle(cornerRadius: cornerRadius) } static var fiveStars: FiveStarsButtonStyle { fiveStars() } } struct FiveStarsButtonStyle: ButtonStyle { @Environment(\.controlSize) var controlSize: ControlSize @Environment(\.controlProminence) var controlProminence: Prominence @Environment(\.isEnabled) var isEnabled: Bool @ScaledMetric var cornerRadius: Double func makeBody(configuration: Configuration) -> some View { configuration .label .frame(maxWidth: controlSize == .large ? .infinity : nil) .padding(.horizontal, horizontalPadding) .padding(.vertical, verticalPadding) .foregroundColor(foregroundColor(for: configuration.role)) .background { backgroundColor(for: configuration.role).cornerRadius(cornerRadius) if let borderColor = borderColor(for: configuration.role) { RoundedRectangle(cornerRadius: cornerRadius) .strokeBorder(borderColor, lineWidth: 2) } } .opacity(configuration.isPressed ? 0.5 : 1) } var horizontalPadding: Double { switch controlSize { case .mini: return 8 case .small: return 16 case .regular: return 32 case .large: return 0 // no need. @unknown default: return 0 } } var verticalPadding: Double { switch controlSize { case .mini: return 2 case .small: return 4 case .regular: return 8 case .large: return 16 @unknown default: return 0 } } func foregroundColor(for role: ButtonRole?) -> Color? { guard isEnabled else { return Color(uiColor: .secondaryLabel) } switch (controlProminence, role) { case (.standard, .destructive?): return .red case (.increased, .destructive?): return .white case (.increased, _): return .black case (_, _): return nil } } func backgroundColor(for role: ButtonRole?) -> Color? { switch controlProminence { case .standard: break case .increased: return color(for: role) @unknown default: break } return nil } func color(for role: ButtonRole?) -> Color { guard isEnabled else { return Color(uiColor: .secondarySystemFill) } switch role { case .cancel?, .destructive?: return Color.red default: return Color.accentColor } } func borderColor(for role: ButtonRole?) -> Color? { switch controlProminence { case .standard: return color(for: role) case .increased: fallthrough @unknown default: return nil } } } // MARK: - Previews struct Previews: PreviewProvider { static var previews: some View { HStack(spacing: 0) { previewContent .accentColor(Color(hue: 80 / 360.0, saturation: 0.98, brightness: 1)) previewContent previewContent .accentColor(Color(hue: 80 / 360.0, saturation: 0.98, brightness: 1)) .colorScheme(.dark) previewContent .colorScheme(.dark) } .buttonStyle(.fiveStars) .navigationViewStyle(.stack) .previewLayout(.fixed(width: 1280, height: 2600)) } static var previewContent: some View { NavigationView { List { Section(header: Text("Default role")) { buttons() } Section(header: Text("Cancel Role")) { buttons(role: .cancel) } Section(header: Text("Destructive Role")) { buttons(role: .destructive) } Section(header: Text("Increased Prominence")) { buttons() .controlProminence(.increased) } Section(header: Text("Cancel Role + Increased prominence")) { buttons(role: .cancel) .controlProminence(.increased) } Section(header: Text("Cancel Role + Increased prominence")) { buttons(role: .destructive) .controlProminence(.increased) } Section(header: Text("Disabled")) { buttons() .disabled(true) } Section(header: Text("Disabled + Increased prominence")) { buttons(role: .destructive) .disabled(true) .controlProminence(.increased) } Section(header: Text("Accessibility5")) { buttons() .environment(\.dynamicTypeSize, .accessibility5) } } .listStyle(GroupedListStyle()) .navigationBarTitle(Text("Buttons")) } } @ViewBuilder static func buttons(role: ButtonRole? = nil) -> some View { Button("Mini", role: role, action: action) .controlSize(.mini) Button("Small", role: role, action: action) .controlSize(.small) Button("Regular", role: role, action: action) .controlSize(.regular) Button("Large", role: role, action: action) .controlSize(.large) } static func action() -> Void { } } ================================================ FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/ComposingSwiftUIViewsApp.swift ================================================ // import SwiftUI @main struct ComposingSwiftUIViewsApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/FSTextField.swift ================================================ // import SwiftUI struct FSTextField: View { var placeholder: LocalizedStringKey = "Placeholder" @Binding var text: String var appearance: Appearance = .default var topContent: TopContent init( placeholder: LocalizedStringKey = "", text: Binding, appearance: Appearance = .default, @ViewBuilder topContent: () -> TopContent ) { self.placeholder = placeholder self._text = text self.appearance = appearance self.topContent = topContent() } enum Appearance { case `default` case error } var body: some View { VStack { topContent _FSTextField( placeholder: placeholder, text: $text, borderColor: borderColor ) } } var borderColor: Color { switch appearance { case .default: return .green case .error: return .red } } } extension FSTextField where TopContent == EmptyView { init( placeholder: LocalizedStringKey = "", text: Binding, appearance: Appearance = .default ) { self.placeholder = placeholder self._text = text self.appearance = appearance self.topContent = EmptyView() } } extension FSTextField { init( title: LocalizedStringKey, placeholder: LocalizedStringKey = "", text: Binding, appearance: Appearance = .default, @ViewBuilder topTrailingContent: () -> TopTrailingContent ) where TopContent == HStack> { self.placeholder = placeholder self._text = text self.appearance = appearance self.topContent = { HStack { Text(title) .bold() Spacer() topTrailingContent() } }() } } extension FSTextField { init( title: LocalizedStringKey, placeholder: LocalizedStringKey = "", text: Binding, appearance: Appearance = .default ) where TopContent == HStack> { self.init( title: title, placeholder: placeholder, text: text, appearance: appearance, topTrailingContent: EmptyView.init ) } } struct FSTextField_Previews: PreviewProvider { static var previews: some View { Group { FSTextField( placeholder: "Placeholder", text: .constant("") ) { Text("Title Centered") .bold() } FSTextField( placeholder: "Placeholder", text: .constant("") ) FSTextField( title: "Title", text: .constant(""), topTrailingContent: { Text("Message") .font(.caption) }) } .padding() .previewLayout(.sizeThatFits) } } ================================================ FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/Info.plist ================================================ 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 ================================================ FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/_FSTextField.swift ================================================ // import SwiftUI struct _FSTextField: View { var placeholder: LocalizedStringKey = "" @Binding var text: String var borderColor: Color var body: some View { TextField( placeholder, text: $text ) .padding(.horizontal, 8) .padding(.vertical, 4) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) .strokeBorder(borderColor) ) } } struct _FSTextField_Previews: PreviewProvider { static var previews: some View { Group { _FSTextField(placeholder: "Placeholder", text: .constant(""), borderColor: .red) _FSTextField(placeholder: "Placeholder", text: .constant(""), borderColor: .green) } .padding() .previewLayout(.sizeThatFits) } } ================================================ FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 647F0AA725358718009ED3F5 /* ComposingSwiftUIViewsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F0AA625358718009ED3F5 /* ComposingSwiftUIViewsApp.swift */; }; 647F0ABC2535874F009ED3F5 /* FSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F0AB92535874F009ED3F5 /* FSTextField.swift */; }; 647F0ABD2535874F009ED3F5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F0ABA2535874F009ED3F5 /* ContentView.swift */; }; 647F0ABE2535874F009ED3F5 /* _FSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F0ABB2535874F009ED3F5 /* _FSTextField.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 647F0AA325358718009ED3F5 /* ComposingSwiftUIViews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ComposingSwiftUIViews.app; sourceTree = BUILT_PRODUCTS_DIR; }; 647F0AA625358718009ED3F5 /* ComposingSwiftUIViewsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingSwiftUIViewsApp.swift; sourceTree = ""; }; 647F0AAF2535871A009ED3F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 647F0AB92535874F009ED3F5 /* FSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FSTextField.swift; sourceTree = ""; }; 647F0ABA2535874F009ED3F5 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContentView.swift; path = ../../../../../Desktop/playground/Playground/Playground/ContentView.swift; sourceTree = ""; }; 647F0ABB2535874F009ED3F5 /* _FSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _FSTextField.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 647F0AA025358718009ED3F5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 647F0A9A25358717009ED3F5 = { isa = PBXGroup; children = ( 647F0AA525358718009ED3F5 /* ComposingSwiftUIViews */, 647F0AA425358718009ED3F5 /* Products */, ); sourceTree = ""; }; 647F0AA425358718009ED3F5 /* Products */ = { isa = PBXGroup; children = ( 647F0AA325358718009ED3F5 /* ComposingSwiftUIViews.app */, ); name = Products; sourceTree = ""; }; 647F0AA525358718009ED3F5 /* ComposingSwiftUIViews */ = { isa = PBXGroup; children = ( 647F0AA625358718009ED3F5 /* ComposingSwiftUIViewsApp.swift */, 647F0ABA2535874F009ED3F5 /* ContentView.swift */, 647F0AB92535874F009ED3F5 /* FSTextField.swift */, 647F0ABB2535874F009ED3F5 /* _FSTextField.swift */, 647F0AAF2535871A009ED3F5 /* Info.plist */, ); path = ComposingSwiftUIViews; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 647F0AA225358718009ED3F5 /* ComposingSwiftUIViews */ = { isa = PBXNativeTarget; buildConfigurationList = 647F0AB22535871A009ED3F5 /* Build configuration list for PBXNativeTarget "ComposingSwiftUIViews" */; buildPhases = ( 647F0A9F25358718009ED3F5 /* Sources */, 647F0AA025358718009ED3F5 /* Frameworks */, 647F0AA125358718009ED3F5 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ComposingSwiftUIViews; productName = ComposingSwiftUIViews; productReference = 647F0AA325358718009ED3F5 /* ComposingSwiftUIViews.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 647F0A9B25358717009ED3F5 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1200; TargetAttributes = { 647F0AA225358718009ED3F5 = { CreatedOnToolsVersion = 12.0.1; }; }; }; buildConfigurationList = 647F0A9E25358717009ED3F5 /* Build configuration list for PBXProject "ComposingSwiftUIViews" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 647F0A9A25358717009ED3F5; productRefGroup = 647F0AA425358718009ED3F5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 647F0AA225358718009ED3F5 /* ComposingSwiftUIViews */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 647F0AA125358718009ED3F5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 647F0A9F25358718009ED3F5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 647F0AA725358718009ED3F5 /* ComposingSwiftUIViewsApp.swift in Sources */, 647F0ABE2535874F009ED3F5 /* _FSTextField.swift in Sources */, 647F0ABD2535874F009ED3F5 /* ContentView.swift in Sources */, 647F0ABC2535874F009ED3F5 /* FSTextField.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 647F0AB02535871A009ED3F5 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 647F0AB12535871A009ED3F5 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 647F0AB32535871A009ED3F5 /* 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 = ComposingSwiftUIViews/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.ComposingSwiftUIViews; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 647F0AB42535871A009ED3F5 /* 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 = ComposingSwiftUIViews/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.ComposingSwiftUIViews; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 647F0A9E25358717009ED3F5 /* Build configuration list for PBXProject "ComposingSwiftUIViews" */ = { isa = XCConfigurationList; buildConfigurations = ( 647F0AB02535871A009ED3F5 /* Debug */, 647F0AB12535871A009ED3F5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 647F0AB22535871A009ED3F5 /* Build configuration list for PBXNativeTarget "ComposingSwiftUIViews" */ = { isa = XCConfigurationList; buildConfigurations = ( 647F0AB32535871A009ED3F5 /* Debug */, 647F0AB42535871A009ED3F5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 647F0A9B25358717009ED3F5 /* Project object */; } ================================================ FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Composing-SwiftUI-Views/README.md ================================================ Final project from [Composing SwiftUI views][fs]. ![][gif] [fs]: https://fivestars.blog/swiftui/design-system-composing-views.html [gif]: textFields.png ================================================ FILE: Content-Friendly-Layouts/Flexible/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Content-Friendly-Layouts/Flexible/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Content-Friendly-Layouts/Flexible/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Content-Friendly-Layouts/Flexible/ContentView.swift ================================================ // import SwiftUI class ContentViewModel: ObservableObject { @Published var originalItems = [ "Here’s", "to", "the", "crazy", "ones", "the", "misfits", "the", "rebels", "the", "troublemakers", "the", "round", "pegs", "in", "the", "square", "holes", "the", "ones", "who", "see", "things", "differently", "they’re", "not", "fond", "of", "rules", "You", "can", "quote", "them", "disagree", "with", "them", "glorify", "or", "vilify", "them", "but", "the", "only", "thing", "you", "can’t", "do", "is", "ignore", "them", "because", "they", "change", "things", "they", "push", "the", "human", "race", "forward", "and", "while", "some", "may", "see", "them", "as", "the", "crazy", "ones", "we", "see", "genius", "because", "the", "ones", "who", "are", "crazy", "enough", "to", "think", "that", "they", "can", "change", "the", "world", "are", "the", "ones", "who", "do" ] @Published var spacing: CGFloat = 8 @Published var wordCount: Int = 5 var words: [String] { Array(originalItems.prefix(wordCount)) } } struct ContentView: View { @StateObject var model = ContentViewModel() var body: some View { VStack { ReadjustingStackView(data: model.words, spacing: model.spacing) { item in Text(verbatim: item) .padding(8) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.gray.opacity(0.2)) ) } // .border(Color.black) .padding(.horizontal) } .frame(maxHeight: .infinity) .overlay(Settings(model: model), alignment: .bottom) } } struct Settings: View { @ObservedObject var model: ContentViewModel var body: some View { VStack { Stepper(value: $model.wordCount, in: 0...model.originalItems.count) { Text("\(model.wordCount) words") } HStack { Text("Spacing") Slider(value: $model.spacing, in: 0...40) { Text("") } } Button { model.originalItems.shuffle() } label: { Text("Shuffle") } } .padding() .background(Color(UIColor.systemBackground)) .clipShape(RoundedRectangle(cornerRadius: 20)) .overlay( RoundedRectangle(cornerRadius: 20) .stroke(Color.primary, lineWidth: 4) ) .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ================================================ FILE: Content-Friendly-Layouts/Flexible/FlexibleApp.swift ================================================ // import SwiftUI @main struct FlexibleApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Content-Friendly-Layouts/Flexible/Info.plist ================================================ 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 UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Content-Friendly-Layouts/Flexible/ReadjustingStackView.swift ================================================ // import SwiftUI /// Facade of our view, its main responsibility is to get the available width /// and pass it down to the real implementation, `_ReadjustingStackView`. struct ReadjustingStackView: View where Data.Element: Hashable { let data: Data let spacing: CGFloat let content: (Data.Element) -> Content @State private var availableWidth: CGFloat = 0 var body: some View { ZStack { Color.clear .frame(height: 1) .readSize { size in availableWidth = size.width } _ReadjustingStackView( availableWidth: availableWidth, data: data, spacing: spacing, content: content ) } } } ================================================ FILE: Content-Friendly-Layouts/Flexible/SizeReader.swift ================================================ // import SwiftUI extension View { func readSize(onChange: @escaping (CGSize) -> Void) -> some View { background( GeometryReader { geometryProxy in Color.clear .preference(key: SizePreferenceKey.self, value: geometryProxy.size) } ) .onPreferenceChange(SizePreferenceKey.self, perform: onChange) } } private struct SizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} } ================================================ FILE: Content-Friendly-Layouts/Flexible/_ReadjustingStackView.swift ================================================ // import SwiftUI /// This view is responsible to lay the given elements in either axis. struct _ReadjustingStackView: View where Data.Element: Hashable { let availableWidth: CGFloat let data: Data let spacing: CGFloat let content: (Data.Element) -> Content @State var elementsSize: [Data.Element: CGSize] = [:] var body : some View { if isHorizontal() { HStack(spacing: spacing) { elementsViews } } else { VStack(spacing: spacing) { elementsViews } } } var elementsViews: some View { ForEach(data, id: \.self) { element in content(element) .fixedSize() .readSize { size in elementsSize[element] = size } } } func isHorizontal() -> Bool { let desiredStackViewWidth = data.reduce(into: 0) { totalWidth, element in let elementSize = elementsSize[element, default: CGSize(width: availableWidth, height: 1)] totalWidth += elementSize.width } + CGFloat(data.count - 1) * spacing return availableWidth >= desiredStackViewWidth } } ================================================ FILE: Content-Friendly-Layouts/Flexible.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 64DF03E524DFBD0000909495 /* FlexibleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03E424DFBD0000909495 /* FlexibleApp.swift */; }; 64DF03E724DFBD0000909495 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03E624DFBD0000909495 /* ContentView.swift */; }; 64DF03E924DFBD0200909495 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64DF03E824DFBD0200909495 /* Assets.xcassets */; }; 64DF03F924DFBD8000909495 /* _ReadjustingStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03F724DFBD7F00909495 /* _ReadjustingStackView.swift */; }; 64DF03FA24DFBD8000909495 /* ReadjustingStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03F824DFBD7F00909495 /* ReadjustingStackView.swift */; }; 64DF03FC24E03FDE00909495 /* SizeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03FB24E03FDE00909495 /* SizeReader.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64DF03E124DFBD0000909495 /* Flexible.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Flexible.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64DF03E424DFBD0000909495 /* FlexibleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexibleApp.swift; sourceTree = ""; }; 64DF03E624DFBD0000909495 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 64DF03E824DFBD0200909495 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64DF03ED24DFBD0200909495 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64DF03F724DFBD7F00909495 /* _ReadjustingStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _ReadjustingStackView.swift; sourceTree = ""; }; 64DF03F824DFBD7F00909495 /* ReadjustingStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadjustingStackView.swift; sourceTree = ""; }; 64DF03FB24E03FDE00909495 /* SizeReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeReader.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64DF03DE24DFBD0000909495 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64DF03D824DFBD0000909495 = { isa = PBXGroup; children = ( 64DF03E324DFBD0000909495 /* Flexible */, 64DF03E224DFBD0000909495 /* Products */, ); sourceTree = ""; }; 64DF03E224DFBD0000909495 /* Products */ = { isa = PBXGroup; children = ( 64DF03E124DFBD0000909495 /* Flexible.app */, ); name = Products; sourceTree = ""; }; 64DF03E324DFBD0000909495 /* Flexible */ = { isa = PBXGroup; children = ( 64DF03E424DFBD0000909495 /* FlexibleApp.swift */, 64DF03E624DFBD0000909495 /* ContentView.swift */, 64DF03F824DFBD7F00909495 /* ReadjustingStackView.swift */, 64DF03F724DFBD7F00909495 /* _ReadjustingStackView.swift */, 64DF03FB24E03FDE00909495 /* SizeReader.swift */, 64DF03E824DFBD0200909495 /* Assets.xcassets */, 64DF03ED24DFBD0200909495 /* Info.plist */, ); path = Flexible; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64DF03E024DFBD0000909495 /* Flexible */ = { isa = PBXNativeTarget; buildConfigurationList = 64DF03F024DFBD0200909495 /* Build configuration list for PBXNativeTarget "Flexible" */; buildPhases = ( 64DF03DD24DFBD0000909495 /* Sources */, 64DF03DE24DFBD0000909495 /* Frameworks */, 64DF03DF24DFBD0000909495 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Flexible; productName = Flexible; productReference = 64DF03E124DFBD0000909495 /* Flexible.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64DF03D924DFBD0000909495 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1200; TargetAttributes = { 64DF03E024DFBD0000909495 = { CreatedOnToolsVersion = 12.0; }; }; }; buildConfigurationList = 64DF03DC24DFBD0000909495 /* Build configuration list for PBXProject "Flexible" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 64DF03D824DFBD0000909495; productRefGroup = 64DF03E224DFBD0000909495 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64DF03E024DFBD0000909495 /* Flexible */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64DF03DF24DFBD0000909495 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64DF03E924DFBD0200909495 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64DF03DD24DFBD0000909495 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64DF03E724DFBD0000909495 /* ContentView.swift in Sources */, 64DF03E524DFBD0000909495 /* FlexibleApp.swift in Sources */, 64DF03FA24DFBD8000909495 /* ReadjustingStackView.swift in Sources */, 64DF03F924DFBD8000909495 /* _ReadjustingStackView.swift in Sources */, 64DF03FC24E03FDE00909495 /* SizeReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 64DF03EE24DFBD0200909495 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 64DF03EF24DFBD0200909495 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 64DF03F124DFBD0200909495 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Flexible/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Flexible; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64DF03F224DFBD0200909495 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Flexible/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Flexible; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64DF03DC24DFBD0000909495 /* Build configuration list for PBXProject "Flexible" */ = { isa = XCConfigurationList; buildConfigurations = ( 64DF03EE24DFBD0200909495 /* Debug */, 64DF03EF24DFBD0200909495 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64DF03F024DFBD0200909495 /* Build configuration list for PBXNativeTarget "Flexible" */ = { isa = XCConfigurationList; buildConfigurations = ( 64DF03F124DFBD0200909495 /* Debug */, 64DF03F224DFBD0200909495 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64DF03D924DFBD0000909495 /* Project object */; } ================================================ FILE: Content-Friendly-Layouts/Flexible.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Content-Friendly-Layouts/Flexible.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Content-Friendly-Layouts/README.md ================================================ Proof of concept from [Build content-friendly layouts][fs]. ![][gif] [fs]: https://fivestars.blog/ios/content-friendly-layouts.html [gif]: content-friendly.gif ================================================ FILE: Custom-SwiftUI-Styles/ContentView.swift ================================================ // Gist from http://fivestars.blog/swiftui/custom-view-styles.html import SwiftUI // This gist shows you how to build your own style for your own SwiftUI views/ // components. // // Here's the list of steps: // 1. Create view // 2. Create view style protocol // 3. Create style configuration // 4. Implement base view styles // 5. Define view default style // 6. Setup style environment (key + environment value + style eraser) // 7. Define `.xxxStyle(_:)` convenience view modifier // 8. Update view to take advantage of environment style // MARK: 1. Create view //struct Card: View { // var content: () -> Content // // var body: some View { // content() // } //} // MARK: 2. Create view style protocol protocol CardStyle { associatedtype Body: View typealias Configuration = CardStyleConfiguration func makeBody(configuration: Self.Configuration) -> Self.Body } // MARK: 3. Create style configuration struct CardStyleConfiguration { /// A type-erased content of a `Card`. struct Label: View { init(content: Content) { body = AnyView(content) } var body: AnyView } let label: CardStyleConfiguration.Label } // MARK: 4. Implement base view styles struct RoundedRectangleCardStyle: CardStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.title) .padding() .background(RoundedRectangle(cornerRadius: 16).strokeBorder()) } } struct CapsuleCardStyle: CardStyle { var color: Color func makeBody(configuration: Configuration) -> some View { configuration.label .font(.title) .foregroundColor(.white) .padding() .background( Capsule().fill(color) ) .background( Capsule().fill(color.opacity(0.4)).rotationEffect(.init(degrees: -8)) ) .background( Capsule().fill(color.opacity(0.4)).rotationEffect(.init(degrees: 4)) ) .background( Capsule().fill(color.opacity(0.4)).rotationEffect(.init(degrees: 8)) ) .background( Capsule().fill(color.opacity(0.4)).rotationEffect(.init(degrees: -4)) ) } } struct ShadowCardStyle: CardStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.title) .foregroundColor(.black) .padding() .background(Color.white.cornerRadius(16)) .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) } } struct ColorfulCardStyle: CardStyle { var color: Color func makeBody(configuration: Configuration) -> some View { configuration.label .font(.title) .foregroundColor(.white) .shadow(color: Color.white.opacity(0.8), radius: 4, x: 0, y: 2) .padding() .background( color.cornerRadius(16) ) .shadow(color: color, radius: 8, x: 0, y: 4) } } // MARK: 5. Define view default style struct DefaultCardStyle: CardStyle { func makeBody(configuration: Configuration) -> some View { #if os(iOS) return ShadowCardStyle().makeBody(configuration: configuration) #else return RoundedRectangleCardStyle().makeBody(configuration: configuration) #endif } } // MARK: 6. Setup style environment (key + environment value + style eraser) struct AnyCardStyle: CardStyle { private var _makeBody: (Configuration) -> AnyView init(style: S) { _makeBody = { configuration in AnyView(style.makeBody(configuration: configuration)) } } func makeBody(configuration: Configuration) -> some View { _makeBody(configuration) } } struct CardStyleKey: EnvironmentKey { static var defaultValue = AnyCardStyle(style: DefaultCardStyle()) } extension EnvironmentValues { var cardStyle: AnyCardStyle { get { self[CardStyleKey.self] } set { self[CardStyleKey.self] = newValue } } } // MARK: 7. Define `.xxxStyle(_:)` convenience view modifier extension View { func cardStyle(_ style: S) -> some View { environment(\.cardStyle, AnyCardStyle(style: style)) } } // MARK: 8. Update view to take advantage of environment style struct Card: View { @Environment(\.cardStyle) var style var content: () -> Content var body: some View { style .makeBody( configuration: CardStyleConfiguration( label: CardStyleConfiguration.Label(content: content()) ) ) } } // MARK: 9. Enjoy :) struct ContentView: View { var body: some View { ScrollView { LazyVStack { // Default style Section { sectionContent } // RoundedRectangleCardStyle Section { sectionContent } .cardStyle(RoundedRectangleCardStyle()) // CapsuleCardStyle - green Section { sectionContent } .cardStyle(CapsuleCardStyle(color: .green)) // CapsuleCardStyle - blue Section { sectionContent } .cardStyle(CapsuleCardStyle(color: .blue)) // ColorfulCardStyle - purple Section { sectionContent } .cardStyle(ColorfulCardStyle(color: .purple)) // ColorfulCardStyle - pink Section { sectionContent } .cardStyle(ColorfulCardStyle(color: .pink)) // ColorfulCardStyle - red Section { sectionContent } .cardStyle(ColorfulCardStyle(color: .red)) } } } var sectionContent: some View { ScrollView(.horizontal) { LazyHStack { ForEach(1..<5) { _ in Card { Text(verbatim: "Five Stars") } } } .padding() } } } struct ContentView_Preview: PreviewProvider { static var previews: some View { ContentView() .padding() .previewLayout(.sizeThatFits) } } ================================================ FILE: Custom-SwiftUI-Styles/README.md ================================================ Code snippet from [Custom view styles][fs]. ![][image] [fs]: http://fivestars.blog/swiftui/custom-view-styles.html [image]: image.png ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/ContentView.swift ================================================ // import SwiftUI struct ContentView: View { var backendString: String = "some backend string" var body: some View { let marketingText: Text = Text("Please please ").italic() + Text("tap me ") + Text("NOW!").underline().bold().font(.title) let exampleText: Text = Text("Default ") + Text("italic ").italic() + Text("Big ").font(.title) + Text("Red ").foregroundColor(.red) + Text("underline").underline() VStack { FSButton(titleKey: "my_localized_title") {} FSButton(backendString) {} FSButton(marketingText) {} FSButton(exampleText) {} } .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .padding() .previewLayout(.sizeThatFits) .environment(\.locale, .init(identifier: "th")) } } ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/DisplayingTextApp.swift ================================================ // import SwiftUI @main struct DisplayingTextApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/FSButton.swift ================================================ // import SwiftUI struct FSButton: View { let title: Text let action: () -> Void init(_ title: Text, action: @escaping () -> Void) { self.title = title self.action = action } init(_ content: S, action: @escaping () -> Void) { self.title = Text(content) self.action = action } init(titleKey: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil, action: @escaping () -> Void) { self.title = Text(titleKey, tableName: tableName, bundle: bundle, comment: comment) self.action = action } var body: some View { Button(action: action, label: { title.bold() }) .buttonStyle(FSButtonStyle()) } } private struct FSButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { HStack { Spacer() configuration.label .foregroundColor(.white) Spacer() } .padding() .background( RoundedRectangle(cornerRadius: 8, style: .continuous).fill(Color.green) ) .opacity(configuration.isPressed ? 0.5 : 1) } } ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Info.plist ================================================ 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 ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/en.lproj/Localizable.strings ================================================ "my_localized_title" = "Button title"; ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText/th.lproj/Localizable.strings ================================================ "my_localized_title" = "ชื่อปุ่ม"; ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 64DF69EF254D7EF300099987 /* DisplayingTextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF69EE254D7EF300099987 /* DisplayingTextApp.swift */; }; 64DF69F1254D7EF300099987 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF69F0254D7EF300099987 /* ContentView.swift */; }; 64DF69F3254D7EF400099987 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64DF69F2254D7EF400099987 /* Assets.xcassets */; }; 64DF6A00254D7F1F00099987 /* FSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF69FF254D7F1F00099987 /* FSButton.swift */; }; 64DF6A06254D7F9300099987 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 64DF6A08254D7F9300099987 /* Localizable.strings */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64DF69EB254D7EF300099987 /* DisplayingText.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DisplayingText.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64DF69EE254D7EF300099987 /* DisplayingTextApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayingTextApp.swift; sourceTree = ""; }; 64DF69F0254D7EF300099987 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 64DF69F2254D7EF400099987 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64DF69F7254D7EF400099987 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64DF69FF254D7F1F00099987 /* FSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSButton.swift; sourceTree = ""; }; 64DF6A07254D7F9300099987 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 64DF6A0A254D7F9400099987 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64DF69E8254D7EF300099987 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64DF69E2254D7EF300099987 = { isa = PBXGroup; children = ( 64DF69ED254D7EF300099987 /* DisplayingText */, 64DF69EC254D7EF300099987 /* Products */, ); sourceTree = ""; }; 64DF69EC254D7EF300099987 /* Products */ = { isa = PBXGroup; children = ( 64DF69EB254D7EF300099987 /* DisplayingText.app */, ); name = Products; sourceTree = ""; }; 64DF69ED254D7EF300099987 /* DisplayingText */ = { isa = PBXGroup; children = ( 64DF69EE254D7EF300099987 /* DisplayingTextApp.swift */, 64DF6A08254D7F9300099987 /* Localizable.strings */, 64DF69F0254D7EF300099987 /* ContentView.swift */, 64DF69FF254D7F1F00099987 /* FSButton.swift */, 64DF69F2254D7EF400099987 /* Assets.xcassets */, 64DF69F7254D7EF400099987 /* Info.plist */, ); path = DisplayingText; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64DF69EA254D7EF300099987 /* DisplayingText */ = { isa = PBXNativeTarget; buildConfigurationList = 64DF69FA254D7EF400099987 /* Build configuration list for PBXNativeTarget "DisplayingText" */; buildPhases = ( 64DF69E7254D7EF300099987 /* Sources */, 64DF69E8254D7EF300099987 /* Frameworks */, 64DF69E9254D7EF300099987 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = DisplayingText; productName = DisplayingText; productReference = 64DF69EB254D7EF300099987 /* DisplayingText.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64DF69E3254D7EF300099987 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1210; LastUpgradeCheck = 1210; TargetAttributes = { 64DF69EA254D7EF300099987 = { CreatedOnToolsVersion = 12.1; }; }; }; buildConfigurationList = 64DF69E6254D7EF300099987 /* Build configuration list for PBXProject "DisplayingText" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, th, ); mainGroup = 64DF69E2254D7EF300099987; productRefGroup = 64DF69EC254D7EF300099987 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64DF69EA254D7EF300099987 /* DisplayingText */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64DF69E9254D7EF300099987 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64DF69F3254D7EF400099987 /* Assets.xcassets in Resources */, 64DF6A06254D7F9300099987 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64DF69E7254D7EF300099987 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64DF69F1254D7EF300099987 /* ContentView.swift in Sources */, 64DF6A00254D7F1F00099987 /* FSButton.swift in Sources */, 64DF69EF254D7EF300099987 /* DisplayingTextApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 64DF6A08254D7F9300099987 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 64DF6A07254D7F9300099987 /* en */, 64DF6A0A254D7F9400099987 /* th */, ); name = Localizable.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 64DF69F8254D7EF400099987 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.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; }; 64DF69F9254D7EF400099987 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.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; }; 64DF69FB254D7EF400099987 /* 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 = DisplayingText/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.DisplayingText; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64DF69FC254D7EF400099987 /* 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 = DisplayingText/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.DisplayingText; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64DF69E6254D7EF300099987 /* Build configuration list for PBXProject "DisplayingText" */ = { isa = XCConfigurationList; buildConfigurations = ( 64DF69F8254D7EF400099987 /* Debug */, 64DF69F9254D7EF400099987 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64DF69FA254D7EF400099987 /* Build configuration list for PBXNativeTarget "DisplayingText" */ = { isa = XCConfigurationList; buildConfigurations = ( 64DF69FB254D7EF400099987 /* Debug */, 64DF69FC254D7EF400099987 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64DF69E3254D7EF300099987 /* Project object */; } ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Displaying-Text-SwiftUI/README.md ================================================ Final project from [Displaying text in SwiftUI][fs]. ![][buttons] [fs]: https://fivestars.blog/swiftui/displaying-text-swiftui.html [buttons]: buttons.png ================================================ FILE: Flexible-SwiftUI/Flexible/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Flexible-SwiftUI/Flexible/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Flexible-SwiftUI/Flexible/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Flexible-SwiftUI/Flexible/ContentView.swift ================================================ // import SwiftUI class ContentViewModel: ObservableObject { @Published var originalItems = [ "Here’s", "to", "the", "crazy", "ones", "the", "misfits", "the", "rebels", "the", "troublemakers", "the", "round", "pegs", "in", "the", "square", "holes", "the", "ones", "who", "see", "things", "differently", "they’re", "not", "fond", "of", "rules", "You", "can", "quote", "them", "disagree", "with", "them", "glorify", "or", "vilify", "them", "but", "the", "only", "thing", "you", "can’t", "do", "is", "ignore", "them", "because", "they", "change", "things", "they", "push", "the", "human", "race", "forward", "and", "while", "some", "may", "see", "them", "as", "the", "crazy", "ones", "we", "see", "genius", "because", "the", "ones", "who", "are", "crazy", "enough", "to", "think", "that", "they", "can", "change", "the", "world", "are", "the", "ones", "who", "do" ] @Published var spacing: CGFloat = 8 @Published var padding: CGFloat = 8 @Published var wordCount: Int = 75 @Published var alignmentIndex = 0 var words: [String] { Array(originalItems.prefix(wordCount)) } let alignments: [HorizontalAlignment] = [.leading, .center, .trailing] var alignment: HorizontalAlignment { alignments[alignmentIndex] } } struct ContentView: View { @StateObject var model = ContentViewModel() var body: some View { ScrollView { FlexibleView( data: model.words, spacing: model.spacing, alignment: model.alignment ) { item in Text(verbatim: item) .padding(8) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.gray.opacity(0.2)) ) } .padding(.horizontal, model.padding) } .overlay(Settings(model: model), alignment: .bottom) } } struct Settings: View { @ObservedObject var model: ContentViewModel let alignmentName: [String] = ["leading", "center", "trailing"] var body: some View { VStack { Stepper(value: $model.wordCount, in: 0...model.originalItems.count) { Text("\(model.wordCount) words") } HStack { Text("Padding") Slider(value: $model.padding, in: 0...60) { Text("") } } HStack { Text("Spacing") Slider(value: $model.spacing, in: 0...40) { Text("") } } HStack { Text("Alignment") Picker("Choose alignment", selection: $model.alignmentIndex) { ForEach(0..: View where Data.Element: Hashable { let data: Data let spacing: CGFloat let alignment: HorizontalAlignment let content: (Data.Element) -> Content // The initial width should not be `0`, otherwise all items will be layouted in one row, // and the actual layout width may exceed the value we desired. @State private var availableWidth: CGFloat = 10 var body: some View { ZStack(alignment: Alignment(horizontal: alignment, vertical: .center)) { Color.clear .frame(height: 1) .readSize { size in availableWidth = size.width } _FlexibleView( availableWidth: availableWidth, data: data, spacing: spacing, alignment: alignment, content: content ) } } } ================================================ FILE: Flexible-SwiftUI/Flexible/Info.plist ================================================ 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 UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Flexible-SwiftUI/Flexible/SizeReader.swift ================================================ // import SwiftUI extension View { func readSize(onChange: @escaping (CGSize) -> Void) -> some View { background( GeometryReader { geometryProxy in Color.clear .preference(key: SizePreferenceKey.self, value: geometryProxy.size) } ) .onPreferenceChange(SizePreferenceKey.self, perform: onChange) } } private struct SizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} } ================================================ FILE: Flexible-SwiftUI/Flexible/_FlexibleView.swift ================================================ // import SwiftUI /// This view is responsible to lay down the given elements and wrap them into /// multiple rows if needed. struct _FlexibleView: View where Data.Element: Hashable { let availableWidth: CGFloat let data: Data let spacing: CGFloat let alignment: HorizontalAlignment let content: (Data.Element) -> Content @State var elementsSize: [Data.Element: CGSize] = [:] var body : some View { VStack(alignment: alignment, spacing: spacing) { ForEach(computeRows(), id: \.self) { rowElements in HStack(spacing: spacing) { ForEach(rowElements, id: \.self) { element in content(element) .fixedSize() .readSize { size in elementsSize[element] = size } } } } } } func computeRows() -> [[Data.Element]] { var rows: [[Data.Element]] = [[]] var currentRow = 0 var remainingWidth = availableWidth for element in data { let elementSize = elementsSize[element, default: CGSize(width: availableWidth, height: 1)] if remainingWidth - (elementSize.width + spacing) >= 0 { rows[currentRow].append(element) } else { currentRow = currentRow + 1 rows.append([element]) remainingWidth = availableWidth } remainingWidth = remainingWidth - (elementSize.width + spacing) } return rows } } ================================================ FILE: Flexible-SwiftUI/Flexible.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 64DF03E524DFBD0000909495 /* FlexibleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03E424DFBD0000909495 /* FlexibleApp.swift */; }; 64DF03E724DFBD0000909495 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03E624DFBD0000909495 /* ContentView.swift */; }; 64DF03E924DFBD0200909495 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64DF03E824DFBD0200909495 /* Assets.xcassets */; }; 64DF03F924DFBD8000909495 /* _FlexibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03F724DFBD7F00909495 /* _FlexibleView.swift */; }; 64DF03FA24DFBD8000909495 /* FlexibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03F824DFBD7F00909495 /* FlexibleView.swift */; }; 64DF03FC24E03FDE00909495 /* SizeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DF03FB24E03FDE00909495 /* SizeReader.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64DF03E124DFBD0000909495 /* Flexible.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Flexible.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64DF03E424DFBD0000909495 /* FlexibleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexibleApp.swift; sourceTree = ""; }; 64DF03E624DFBD0000909495 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 64DF03E824DFBD0200909495 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64DF03ED24DFBD0200909495 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64DF03F724DFBD7F00909495 /* _FlexibleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _FlexibleView.swift; sourceTree = ""; }; 64DF03F824DFBD7F00909495 /* FlexibleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlexibleView.swift; sourceTree = ""; }; 64DF03FB24E03FDE00909495 /* SizeReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeReader.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64DF03DE24DFBD0000909495 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64DF03D824DFBD0000909495 = { isa = PBXGroup; children = ( 64DF03E324DFBD0000909495 /* Flexible */, 64DF03E224DFBD0000909495 /* Products */, ); sourceTree = ""; }; 64DF03E224DFBD0000909495 /* Products */ = { isa = PBXGroup; children = ( 64DF03E124DFBD0000909495 /* Flexible.app */, ); name = Products; sourceTree = ""; }; 64DF03E324DFBD0000909495 /* Flexible */ = { isa = PBXGroup; children = ( 64DF03E424DFBD0000909495 /* FlexibleApp.swift */, 64DF03E624DFBD0000909495 /* ContentView.swift */, 64DF03F824DFBD7F00909495 /* FlexibleView.swift */, 64DF03F724DFBD7F00909495 /* _FlexibleView.swift */, 64DF03FB24E03FDE00909495 /* SizeReader.swift */, 64DF03E824DFBD0200909495 /* Assets.xcassets */, 64DF03ED24DFBD0200909495 /* Info.plist */, ); path = Flexible; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64DF03E024DFBD0000909495 /* Flexible */ = { isa = PBXNativeTarget; buildConfigurationList = 64DF03F024DFBD0200909495 /* Build configuration list for PBXNativeTarget "Flexible" */; buildPhases = ( 64DF03DD24DFBD0000909495 /* Sources */, 64DF03DE24DFBD0000909495 /* Frameworks */, 64DF03DF24DFBD0000909495 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Flexible; productName = Flexible; productReference = 64DF03E124DFBD0000909495 /* Flexible.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64DF03D924DFBD0000909495 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1200; TargetAttributes = { 64DF03E024DFBD0000909495 = { CreatedOnToolsVersion = 12.0; }; }; }; buildConfigurationList = 64DF03DC24DFBD0000909495 /* Build configuration list for PBXProject "Flexible" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 64DF03D824DFBD0000909495; productRefGroup = 64DF03E224DFBD0000909495 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64DF03E024DFBD0000909495 /* Flexible */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64DF03DF24DFBD0000909495 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64DF03E924DFBD0200909495 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64DF03DD24DFBD0000909495 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64DF03E724DFBD0000909495 /* ContentView.swift in Sources */, 64DF03E524DFBD0000909495 /* FlexibleApp.swift in Sources */, 64DF03FA24DFBD8000909495 /* FlexibleView.swift in Sources */, 64DF03F924DFBD8000909495 /* _FlexibleView.swift in Sources */, 64DF03FC24E03FDE00909495 /* SizeReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 64DF03EE24DFBD0200909495 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 64DF03EF24DFBD0200909495 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 64DF03F124DFBD0200909495 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Flexible/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Flexible; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64DF03F224DFBD0200909495 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Flexible/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Flexible; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64DF03DC24DFBD0000909495 /* Build configuration list for PBXProject "Flexible" */ = { isa = XCConfigurationList; buildConfigurations = ( 64DF03EE24DFBD0200909495 /* Debug */, 64DF03EF24DFBD0200909495 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64DF03F024DFBD0200909495 /* Build configuration list for PBXNativeTarget "Flexible" */ = { isa = XCConfigurationList; buildConfigurations = ( 64DF03F124DFBD0200909495 /* Debug */, 64DF03F224DFBD0200909495 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64DF03D924DFBD0000909495 /* Project object */; } ================================================ FILE: Flexible-SwiftUI/Flexible.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Flexible-SwiftUI/Flexible.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Flexible-SwiftUI/README.md ================================================ Proof of concept from [Flexible SwiftUI][fs]. ![][gif] ## Credits This article and code is inspired by [Mauricio Meirelles][mmtw]'s [GridView][gv]. [fs]: https://fivestars.blog/swiftui/flexible-swiftui.html [gif]: flexible.gif [mmtw]: https://twitter.com/MauricioM [gv]: https://github.com/mauriciomeirelles/GridView ================================================ FILE: Hashable-Bindings/ContentView.swift ================================================ import SwiftUI enum ContentViewGroup: Hashable { case a case b case c } struct ContentView: View { @State var showingContent: ContentViewGroup? var body: some View { List { DisclosureGroup( "Tap to show content A", tag: .a, selection: $showingContent) { Text("Content A") } DisclosureGroup( "Tap to show content B", tag: .b, selection: $showingContent) { Text("Content B") } DisclosureGroup( "Tap to show content C", tag: .c, selection: $showingContent) { Text("Content C") } } } } extension DisclosureGroup where Label == Text { public init( _ label: S, tag: V, selection: Binding, content: @escaping () -> Content) { let boolBinding: Binding = Binding( get: { selection.wrappedValue == tag }, set: { newValue in if newValue { selection.wrappedValue = tag } else { selection.wrappedValue = nil } } ) self.init( label, isExpanded: boolBinding, content: content ) } } ================================================ FILE: Hashable-Bindings/README.md ================================================ Code snippet from [Hashable SwiftUI bindings][fs]. ![][gif] [fs]: https://fivestars.blog/swiftui/hashable-bindings.html [gif]: hashable.gif ================================================ FILE: Hierarchy-List/ContentView-1.swift ================================================ // Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html import SwiftUI struct FileItem: Identifiable { let name: String var children: [FileItem]? var id: String { name } static let spmData: [FileItem] = [ FileItem(name: ".gitignore"), FileItem(name: "Package.swift"), FileItem(name: "README.md"), FileItem(name: "Sources", children: [ FileItem(name: "fivestars", children: [ FileItem(name: "main.swift") ]), ]), FileItem(name: "Tests", children: [ FileItem(name: "fivestarsTests", children: [ FileItem(name: "fivestarsTests.swift"), FileItem(name: "XCTestManifests.swift"), ]), FileItem(name: "LinuxMain.swift") ]) ] } struct ContentView: View { let data: [FileItem] = .spmData var body: some View { // List(data, children: \.children, rowContent: { Text($0.name) }) HierarchyList(data: data, children: \.children, rowContent: { Text($0.name) }) } } public struct HierarchyList: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { private let recursiveView: RecursiveView public init(data: Data, children: KeyPath, rowContent: @escaping (Data.Element) -> RowContent) { self.recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent) } public var body: some View { List { recursiveView } } } private struct RecursiveView: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { let data: Data let children: KeyPath let rowContent: (Data.Element) -> RowContent var body: some View { ForEach(data) { child in if let subChildren = child[keyPath: children] { DisclosureGroup(content: { RecursiveView(data: subChildren, children: children, rowContent: rowContent) }, label: { rowContent(child) }) } else { rowContent(child) } } } } ================================================ FILE: Hierarchy-List/ContentView-2.swift ================================================ // Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html import SwiftUI struct FileItem: Identifiable { let name: String var children: [FileItem]? var id: String { name } static let spmData: [FileItem] = [ FileItem(name: ".gitignore"), FileItem(name: "Package.swift"), FileItem(name: "README.md"), FileItem(name: "Sources", children: [ FileItem(name: "fivestars", children: [ FileItem(name: "main.swift") ]), ]), FileItem(name: "Tests", children: [ FileItem(name: "fivestarsTests", children: [ FileItem(name: "fivestarsTests.swift"), FileItem(name: "XCTestManifests.swift"), ]), FileItem(name: "LinuxMain.swift") ]) ] } struct ContentView: View { var data: [FileItem] = .spmData var body: some View { // List(data, children: \.children, rowContent: { Text($0.name) }) HierarchyList(data: data, children: \.children, rowContent: { Text($0.name) }) } } public struct HierarchyList: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { private let recursiveView: RecursiveView public init(data: Data, children: KeyPath, rowContent: @escaping (Data.Element) -> RowContent) { self.recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent) } public var body: some View { List { recursiveView } } } private struct RecursiveView: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { let data: Data let children: KeyPath let rowContent: (Data.Element) -> RowContent var body: some View { ForEach(data) { child in if let subChildren = child[keyPath: children] { FSDisclosureGroup(content: { RecursiveView(data: subChildren, children: children, rowContent: rowContent) }, label: { rowContent(child) }) } else { rowContent(child) } } } } struct FSDisclosureGroup: View where Label: View, Content: View { @State var isExpanded: Bool = true var content: () -> Content var label: () -> Label @ViewBuilder var body: some View { DisclosureGroup(isExpanded: $isExpanded, content: content, label: label) } } ================================================ FILE: Hierarchy-List/ContentView-xcode-11.swift ================================================ // Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html import SwiftUI struct FileItem: Identifiable { let name: String var children: [FileItem]? var id: String { name } static let spmData: [FileItem] = [ FileItem(name: ".gitignore"), FileItem(name: "Package.swift"), FileItem(name: "README.md"), FileItem(name: "Sources", children: [ FileItem(name: "fivestars", children: [ FileItem(name: "main.swift") ]), ]), FileItem(name: "Tests", children: [ FileItem(name: "fivestarsTests", children: [ FileItem(name: "fivestarsTests.swift"), FileItem(name: "XCTestManifests.swift"), ]), FileItem(name: "LinuxMain.swift") ]) ] } struct ContentView: View { let data: [FileItem] = FileItem.spmData var body: some View { // List(data, children: \.children, rowContent: { Text($0.name) }) HierarchyList(data: data, children: \.children, rowContent: { Text($0.name) }) } } public struct HierarchyList: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { private let recursiveView: RecursiveView public init(data: Data, children: KeyPath, rowContent: @escaping (Data.Element) -> RowContent) { self.recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent) } public var body: some View { List { recursiveView } } } private struct RecursiveView: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { let data: Data let children: KeyPath let rowContent: (Data.Element) -> RowContent var body: some View { ForEach(data) { child in if self.containsSub(child) { FSDisclosureGroup(content: { RecursiveView(data: child[keyPath: self.children]!, children: self.children, rowContent: self.rowContent) .padding(.leading) }, label: { self.rowContent(child) }) } else { self.rowContent(child) } } } func containsSub(_ element: Data.Element) -> Bool { element[keyPath: children] != nil } } struct FSDisclosureGroup: View where Label: View, Content: View { @State var isExpanded: Bool = false var content: () -> Content var label: () -> Label @ViewBuilder var body: some View { Button(action: { self.isExpanded.toggle() }, label: { label().foregroundColor(.blue) }) if isExpanded { content() } } } ================================================ FILE: Hierarchy-List/README.md ================================================ Code snippet from [SwiftUI Hierarchy Lists][fs]. ![][gif] [fs]: https://fivestars.blog/articles/swiftui-hierarchy-list/ [gif]: spm.gif ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/ContentView.swift ================================================ // import SwiftUI enum ContentViewNavigation: Identifiable { case one case two(number: Int) case three(text: String) // MARK: Identifiable var id: Int { switch self { case .one: return 1 case .two: return 2 case .three: return 3 } } } struct ContentView: View { @State private var showingNavigation: ContentViewNavigation? var body: some View { NavigationView { VStack { Button("Go to navigation one") { showingNavigation = .one } Button("Go to navigation two") { showingNavigation = .two(number: Int.random(in: 1...5)) } Button("Go to navigation three") { showingNavigation = .three(text: ["five", "stars"].randomElement()!) } } .navigation(item: $showingNavigation, destination: presentNavigation) } } @ViewBuilder func presentNavigation(_ navigation: ContentViewNavigation) -> some View { switch navigation { case .one: Text(verbatim: "one") case .two(let number): Text("two \(number)") case .three(let text): Text("three \(text)") } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/IdentifiableNavigationApp.swift ================================================ // import SwiftUI @main struct IdentifiableNavigationApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/Info.plist ================================================ 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 ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/NavigationLink+Identifiable.swift ================================================ // import SwiftUI /// The extension from the article broke in Xcode 12.5 and no longer work /// properly. /// /// This updated extension is uglier but works as expected. extension NavigationLink where Label == EmptyView, Destination == AnyView { public init( item: Binding, destination: @escaping (V) -> Destination2 ) { let value: V? = item.wrappedValue let isActive: Binding = Binding( get: { item.wrappedValue != nil }, set: { value in if !value { item.wrappedValue = nil } } ) self.init( destination: AnyView(Group { if let value = value { destination(value) } else { EmptyView() } }), isActive: isActive, label: EmptyView.init ) } } ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/View+Navigation.swift ================================================ // import SwiftUI extension View { func navigation( item: Binding, destination: @escaping (V) -> Destination ) -> some View { background(NavigationLink(item: item, destination: destination)) } } ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 64B446EA25727737003B2E60 /* IdentifiableNavigationApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B446E925727737003B2E60 /* IdentifiableNavigationApp.swift */; }; 64B446EC25727738003B2E60 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B446EB25727738003B2E60 /* ContentView.swift */; }; 64B446FD2572778F003B2E60 /* NavigationLink+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B446FC2572778F003B2E60 /* NavigationLink+Identifiable.swift */; }; 64B44700257277C3003B2E60 /* View+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B446FF257277C3003B2E60 /* View+Navigation.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64B446E625727737003B2E60 /* IdentifiableNavigation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IdentifiableNavigation.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64B446E925727737003B2E60 /* IdentifiableNavigationApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableNavigationApp.swift; sourceTree = ""; }; 64B446EB25727738003B2E60 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 64B446F22572773A003B2E60 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64B446FC2572778F003B2E60 /* NavigationLink+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Identifiable.swift"; sourceTree = ""; }; 64B446FF257277C3003B2E60 /* View+Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Navigation.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64B446E325727737003B2E60 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64B446DD25727737003B2E60 = { isa = PBXGroup; children = ( 64B446E825727737003B2E60 /* IdentifiableNavigation */, 64B446E725727737003B2E60 /* Products */, ); sourceTree = ""; }; 64B446E725727737003B2E60 /* Products */ = { isa = PBXGroup; children = ( 64B446E625727737003B2E60 /* IdentifiableNavigation.app */, ); name = Products; sourceTree = ""; }; 64B446E825727737003B2E60 /* IdentifiableNavigation */ = { isa = PBXGroup; children = ( 64B446E925727737003B2E60 /* IdentifiableNavigationApp.swift */, 64B446EB25727738003B2E60 /* ContentView.swift */, 64B446FC2572778F003B2E60 /* NavigationLink+Identifiable.swift */, 64B446FF257277C3003B2E60 /* View+Navigation.swift */, 64B446F22572773A003B2E60 /* Info.plist */, ); path = IdentifiableNavigation; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64B446E525727737003B2E60 /* IdentifiableNavigation */ = { isa = PBXNativeTarget; buildConfigurationList = 64B446F52572773A003B2E60 /* Build configuration list for PBXNativeTarget "IdentifiableNavigation" */; buildPhases = ( 64B446E225727737003B2E60 /* Sources */, 64B446E325727737003B2E60 /* Frameworks */, 64B446E425727737003B2E60 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = IdentifiableNavigation; productName = IdentifiableNavigation; productReference = 64B446E625727737003B2E60 /* IdentifiableNavigation.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64B446DE25727737003B2E60 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1220; LastUpgradeCheck = 1220; TargetAttributes = { 64B446E525727737003B2E60 = { CreatedOnToolsVersion = 12.2; }; }; }; buildConfigurationList = 64B446E125727737003B2E60 /* Build configuration list for PBXProject "IdentifiableNavigation" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 64B446DD25727737003B2E60; productRefGroup = 64B446E725727737003B2E60 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64B446E525727737003B2E60 /* IdentifiableNavigation */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64B446E425727737003B2E60 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64B446E225727737003B2E60 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64B446EC25727738003B2E60 /* ContentView.swift in Sources */, 64B446FD2572778F003B2E60 /* NavigationLink+Identifiable.swift in Sources */, 64B44700257277C3003B2E60 /* View+Navigation.swift in Sources */, 64B446EA25727737003B2E60 /* IdentifiableNavigationApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 64B446F32572773A003B2E60 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.2; 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; }; 64B446F42572773A003B2E60 /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 64B446F62572773A003B2E60 /* 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 = IdentifiableNavigation/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.IdentifiableNavigation; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64B446F72572773A003B2E60 /* 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 = IdentifiableNavigation/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.IdentifiableNavigation; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64B446E125727737003B2E60 /* Build configuration list for PBXProject "IdentifiableNavigation" */ = { isa = XCConfigurationList; buildConfigurations = ( 64B446F32572773A003B2E60 /* Debug */, 64B446F42572773A003B2E60 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64B446F52572773A003B2E60 /* Build configuration list for PBXNativeTarget "IdentifiableNavigation" */ = { isa = XCConfigurationList; buildConfigurations = ( 64B446F62572773A003B2E60 /* Debug */, 64B446F72572773A003B2E60 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64B446DE25727737003B2E60 /* Project object */; } ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Identifiable-Navigation/README.md ================================================ Working proof of concept of an improved SwiftUI navigation API, refer to [The future of SwiftUI navigation (?)][fs] for more details. ![][gif] [fs]: https://fivestars.blog/swiftui/programmatic-navigation.html [gif]: identifiable.gif ================================================ FILE: LICENSE ================================================ Copyright (c) 2020, Federico Zanetello All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Federico Zanetello nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ Code Samples from [fivestars.blog][fs]. [fs]: http://fivestars.blog/ ================================================ FILE: SafeAreaInset/README.md ================================================ Code snippet from [Backport SwiftUI safe area insets to iOS 13 and 14][fs]. ![][gif] [fs]: https://www.fivestars.blog/articles/safe-area-insets-2/ [gif]: stack.gif ================================================ FILE: SafeAreaInset/content.swift ================================================ // Gist from https://www.fivestars.blog/articles/safe-area-insets-2/ import SwiftUI struct ContentView: View { var body: some View { ScrollView { scrollViewContent ExtraBottomSafeAreaInset() } .bottomSafeAreaInset(overlayContent) .bottomSafeAreaInset(overlayContent) .bottomSafeAreaInset(overlayContent) .bottomSafeAreaInset(overlayContent) .bottomSafeAreaInset(overlayContent) } var scrollViewContent: some View { ForEach(1..<60) { _ in Text("Five Stars") .font(.title) .frame(maxWidth: .infinity) } } var overlayContent: some View { Button { // ... } label: { Text("Continue") .foregroundColor(.white) .padding() .frame(maxWidth: .infinity) .background(Color.accentColor.cornerRadius(8)) .padding(.horizontal) } } } @available(iOS, introduced: 13, deprecated: 15, message: "Use .safeAreaInset() directly") extension View { @ViewBuilder func bottomSafeAreaInset(_ overlayContent: OverlayContent) -> some View { if #available(iOS 15.0, *) { self.safeAreaInset(edge: .bottom, spacing: 0, content: { overlayContent }) } else { self.modifier(BottomInsetViewModifier(overlayContent: overlayContent)) } } } extension View { func readHeight(onChange: @escaping (CGFloat) -> Void) -> some View { background( GeometryReader { geometryProxy in Spacer() .preference( key: HeightPreferenceKey.self, value: geometryProxy.size.height ) } ) .onPreferenceChange(HeightPreferenceKey.self, perform: onChange) } } private struct HeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = .zero static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} } struct BottomInsetViewModifier: ViewModifier { @Environment(\.bottomSafeAreaInset) var ancestorBottomSafeAreaInset: CGFloat var overlayContent: OverlayContent @State var overlayContentHeight: CGFloat = 0 func body(content: Self.Content) -> some View { content .environment(\.bottomSafeAreaInset, overlayContentHeight + ancestorBottomSafeAreaInset) .overlay( overlayContent .readHeight { overlayContentHeight = $0 } .padding(.bottom, ancestorBottomSafeAreaInset) , alignment: .bottom ) } } struct BottomSafeAreaInsetKey: EnvironmentKey { static var defaultValue: CGFloat = 0 } extension EnvironmentValues { var bottomSafeAreaInset: CGFloat { get { self[BottomSafeAreaInsetKey.self] } set { self[BottomSafeAreaInsetKey.self] = newValue } } } struct ExtraBottomSafeAreaInset: View { @Environment(\.bottomSafeAreaInset) var bottomSafeAreaInset: CGFloat var body: some View { Spacer(minLength: bottomSafeAreaInset) } } ================================================ FILE: ScrollView-Offset/README.md ================================================ Final code snippet from [SwiftUI ScrollView offset][fs]. ![][gif] [fs]: https://fivestars.blog/swiftui/scrollview-offset.html [gif]: rainbow.gif ================================================ FILE: ScrollView-Offset/ScrollViewOffset.swift ================================================ // import SwiftUI struct ScrollViewOffset: View { let onOffsetChange: (CGFloat) -> Void let content: () -> Content init( onOffsetChange: @escaping (CGFloat) -> Void, @ViewBuilder content: @escaping () -> Content ) { self.onOffsetChange = onOffsetChange self.content = content } var body: some View { ScrollView { offsetReader content() .padding(.top, -8) } .coordinateSpace(name: "frameLayer") .onPreferenceChange(OffsetPreferenceKey.self, perform: onOffsetChange) } var offsetReader: some View { GeometryReader { proxy in Color.clear .preference( key: OffsetPreferenceKey.self, value: proxy.frame(in: .named("frameLayer")).minY ) } .frame(height: 0) } } private struct OffsetPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = .zero static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} } ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks/ContentView.swift ================================================ // import SwiftUI struct ContentView: View { var body: some View { HStack { ScrollView { LazyVStack( alignment: .leading, spacing: 20, pinnedViews: [.sectionHeaders] ) { Section(header: header(title: "Original")) { content } } } ScrollView { LazyVStackMock( alignment: .leading, spacing: 20, pinnedViews: [.sectionHeaders] ) { Section(header: header(title: "Mock")) { content } } } } .font(.title) } var content: some View { ForEach(1...40, id: \.self) { count in Label("Placeholder \(count)", colorfulSystemImage: "leaf.fill") } } func header(title: String) -> some View { Text(verbatim: title) .bold() .padding(.horizontal) .padding(.vertical, 4) .foregroundColor(.white) .background( Capsule().foregroundColor(.green) ) } } ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks/Info.plist ================================================ 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 ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks/Label.swift ================================================ // import SwiftUI extension Label where Title == Text, Icon == Image { init(_ title: LocalizedStringKey, colorfulSystemImage systemImage: String) { self.init { Text(title) } icon: { Image(systemName: systemImage) .renderingMode(.original) } } } ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks/LazyStacksApp.swift ================================================ // import SwiftUI @main struct LazyStacksApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks/LazyVStackMock.swift ================================================ // import SwiftUI public struct LazyVStackMock: View { let alignment: HorizontalAlignment let spacing: CGFloat? let pinnedViews: PinnedScrollableViews let content: Content public init( alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, pinnedViews: PinnedScrollableViews = .init(), @ViewBuilder content: () -> Content ) { self.alignment = alignment self.spacing = spacing self.pinnedViews = pinnedViews self.content = content() } public var body: some View { LazyVGrid( columns: [GridItem(.flexible())], alignment: alignment, spacing: spacing, pinnedViews: pinnedViews, content: { content } ) } } ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 64998CF92522334B00DA960A /* LazyStacksApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64998CF82522334B00DA960A /* LazyStacksApp.swift */; }; 64998CFB2522334B00DA960A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64998CFA2522334B00DA960A /* ContentView.swift */; }; 64998D0C2522338000DA960A /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64998D0B2522338000DA960A /* Label.swift */; }; 64998D0F252233A600DA960A /* LazyVStackMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64998D0E252233A600DA960A /* LazyVStackMock.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64998CF52522334B00DA960A /* LazyStacks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LazyStacks.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64998CF82522334B00DA960A /* LazyStacksApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyStacksApp.swift; sourceTree = ""; }; 64998CFA2522334B00DA960A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 64998D012522334C00DA960A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64998D0B2522338000DA960A /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; 64998D0E252233A600DA960A /* LazyVStackMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyVStackMock.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64998CF22522334B00DA960A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64998CEC2522334B00DA960A = { isa = PBXGroup; children = ( 64998CF72522334B00DA960A /* LazyStacks */, 64998CF62522334B00DA960A /* Products */, ); sourceTree = ""; }; 64998CF62522334B00DA960A /* Products */ = { isa = PBXGroup; children = ( 64998CF52522334B00DA960A /* LazyStacks.app */, ); name = Products; sourceTree = ""; }; 64998CF72522334B00DA960A /* LazyStacks */ = { isa = PBXGroup; children = ( 64998CFA2522334B00DA960A /* ContentView.swift */, 64998D0B2522338000DA960A /* Label.swift */, 64998CF82522334B00DA960A /* LazyStacksApp.swift */, 64998D0E252233A600DA960A /* LazyVStackMock.swift */, 64998D012522334C00DA960A /* Info.plist */, ); path = LazyStacks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64998CF42522334B00DA960A /* LazyStacks */ = { isa = PBXNativeTarget; buildConfigurationList = 64998D042522334C00DA960A /* Build configuration list for PBXNativeTarget "LazyStacks" */; buildPhases = ( 64998CF12522334B00DA960A /* Sources */, 64998CF22522334B00DA960A /* Frameworks */, 64998CF32522334B00DA960A /* Resources */, ); buildRules = ( ); dependencies = ( ); name = LazyStacks; productName = LazyStacks; productReference = 64998CF52522334B00DA960A /* LazyStacks.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64998CED2522334B00DA960A /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1200; TargetAttributes = { 64998CF42522334B00DA960A = { CreatedOnToolsVersion = 12.0.1; }; }; }; buildConfigurationList = 64998CF02522334B00DA960A /* Build configuration list for PBXProject "LazyStacks" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 64998CEC2522334B00DA960A; productRefGroup = 64998CF62522334B00DA960A /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64998CF42522334B00DA960A /* LazyStacks */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64998CF32522334B00DA960A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64998CF12522334B00DA960A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64998CFB2522334B00DA960A /* ContentView.swift in Sources */, 64998D0F252233A600DA960A /* LazyVStackMock.swift in Sources */, 64998CF92522334B00DA960A /* LazyStacksApp.swift in Sources */, 64998D0C2522338000DA960A /* Label.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 64998D022522334C00DA960A /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 64998D032522334C00DA960A /* 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; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 64998D052522334C00DA960A /* 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 = LazyStacks/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.LazyStacks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64998D062522334C00DA960A /* 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 = LazyStacks/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.LazyStacks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64998CF02522334B00DA960A /* Build configuration list for PBXProject "LazyStacks" */ = { isa = XCConfigurationList; buildConfigurations = ( 64998D022522334C00DA960A /* Debug */, 64998D032522334C00DA960A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64998D042522334C00DA960A /* Build configuration list for PBXNativeTarget "LazyStacks" */ = { isa = XCConfigurationList; buildConfigurations = ( 64998D052522334C00DA960A /* Debug */, 64998D062522334C00DA960A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64998CED2522334B00DA960A /* Project object */; } ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Stack-vs-Grid/LazyStacks/LazyStacks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Stack-vs-Grid/README.md ================================================ Final code snippet from [Lazy stacks secrets][fs]. ![][gif] [fs]: https://fivestars.blog/swiftui/lazy-stack-grid.html [gif]: final.gif ================================================ FILE: SwiftUI-Clipping/Clipping/Clipping/ClippingApp.swift ================================================ import SwiftUI @main struct ClippingApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: SwiftUI-Clipping/Clipping/Clipping/ContentView.swift ================================================ import SwiftUI struct ContentView: View { var body: some View { NavigationView { List { Section("Introduction") { NavigationLink("no clip", destination: FSView0()) } Section("Clipping") { NavigationLink("clipped()", destination: FSView1()) NavigationLink("cornerRadius(:)", destination: FSView2()) NavigationLink("clipShape(Circle())", destination: FSView3()) NavigationLink("clipShape(Star())", destination: FSView4()) NavigationLink("clipShape + even-odd rule", destination: FSView7()) NavigationLink("clipShape + animation", destination: FSView8()) } Section("Shapes") { NavigationLink("DoubleEllipse", destination: FSView5()) NavigationLink("DoubleEllipse even-odd rule", destination: FSView6()) } } .navigationTitle("View Clipping") } } } ================================================ FILE: SwiftUI-Clipping/Clipping/Clipping/FSViews.swift ================================================ import SwiftUI struct FSView0: View { var body: some View { Text("Five stars") .background(Color.yellow) .font(.system(size: 90)) .fixedSize() .border(Color.blue) .frame(width: 200, height: 100) .border(Color.red) } } struct FSView1: View { var body: some View { Text("Five stars") .background(Color.yellow) .font(.system(size: 90)) .fixedSize() .border(Color.blue) .frame(width: 200, height: 100) .border(Color.red) .clipped() } } struct FSView2: View { @State var cornerRadius: Double = 16 var body: some View { VStack { Text("Five stars") .background(Color.yellow) .font(.system(size: 90)) .fixedSize() .frame(width: 200, height: 100) .cornerRadius(cornerRadius) HStack { Text("Corner radius") Slider(value: $cornerRadius, in: 0...50) } } .padding() } } struct FSView3: View { var body: some View { Text("Five stars") .background(Color.yellow) .font(.system(size: 90)) .fixedSize() .frame(width: 200, height: 100) .clipShape(Circle()) } } struct FSView4: View { @State var points: Int = 5 @State var angle: Double = 53 var body: some View { VStack { Text("Five stars") .background(Color.yellow) .font(.system(size: 90)) .fixedSize() .frame(width: 200, height: 100) .clipShape(Star(points: points).rotation(.degrees(angle))) Stepper("Star points", value: $points, in: 2...16) HStack { Text("Rotation Angle") Slider(value: $angle, in: 0.0...360.0) } } .padding() } } struct FSView5: View { @State var overlapping: Double = 0.1 var body: some View { VStack { DoubleEllipse(overlapping: overlapping) .frame(width: 300, height: 100) HStack { Text("Overlapping") Slider(value: $overlapping, in: 0.0...1.0) } } .padding() } } struct FSView6: View { @State var overlapping: Double = 0.1 var body: some View { VStack { DoubleEllipse(overlapping: overlapping) .fill(style: FillStyle(eoFill: true, antialiased: true)) .frame(width: 300, height: 100) HStack { Text("Overlapping") Slider(value: $overlapping, in: 0.0...1.0) } } .padding() } } struct FSView7: View { @State var ellipsesNumber: Int = 8 @State var overlapping: Double = 0 var body: some View { VStack { Text("Five stars") .background(Color.yellow) .font(.system(size: 80)) .clipShape( OverlappingEllipses(ellipsesNumber: ellipsesNumber, overlapping: overlapping), style: FillStyle(eoFill: true, antialiased: false) ) Stepper("Ellipses number:", value: $ellipsesNumber, in: 2...16) HStack { Text("Overlapping") Slider(value: $overlapping, in: 0.0...1.0) } } .padding() } } struct FSView8: View { @State var shapeNumber: Int = 8 @State var overlapping: Double = 0 var body: some View { VStack(spacing: 16) { Text("Five stars") .background(Color.yellow) .font(.system(size: 80)) .clipShape( OverlappingEllipses( ellipsesNumber: shapeNumber, overlapping: overlapping), style: FillStyle(eoFill: true, antialiased: false) ) Text("Five stars") .background(Color.yellow) .font(.system(size: 80)) .clipShape( OverlappingRectangles(rectanglesNumber: shapeNumber, overlapping: overlapping), style: FillStyle(eoFill: true, antialiased: false) ) Button("Show/Hide") { withAnimation(.easeInOut(duration: 2)) { overlapping = overlapping == 1 ? 0 : 1 } } } .padding() } } ================================================ FILE: SwiftUI-Clipping/Clipping/Clipping/Shapes.swift ================================================ import SwiftUI struct Star: Shape { @Clamping(0...Int.max) var points: Int = 5 var innerRatio = 0.4 func path(in rect : CGRect) -> Path { let center = CGPoint(x: rect.midX, y: rect.midY) let angle: Double = .pi / Double(points) var path = Path() var startPoint: CGPoint = rect.origin let outerRadius = min(rect.width / 2, rect.height / 2) let innerRadius = outerRadius * innerRatio let maxCorners = 2 * points for corner in 0 ..< maxCorners { let radius = (corner % 2) == 0 ? outerRadius : innerRadius let x = center.x + cos(Double(corner) * angle) * radius let y = center.y + sin(Double(corner) * angle) * radius let point = CGPoint(x: x, y: y) if corner == 0 { startPoint = point path.move(to: point) } else { path.addLine(to: point) } if corner == (maxCorners - 1) { path.addLine(to: startPoint) } } return path } } struct DoubleEllipse: Shape { /// 1 = complete overlap /// 0 = no overlap @Clamping(0.0...1.0) var overlapping: Double = 0 func path(in rect: CGRect) -> Path { let rectSize = CGSize(width: (rect.width / 2) * (1 + overlapping), height: rect.height) var path = Path() path.addEllipse(in: CGRect(origin: .zero, size: rectSize)) let secondEllipseOrigin = CGPoint(x: (rect.width / 2) * (1 - overlapping), y: rect.origin.y) path.addEllipse(in: CGRect(origin: secondEllipseOrigin, size: rectSize)) return path } } struct OverlappingEllipses: Shape { @Clamping(1...Int.max) var ellipsesNumber: Int = 2 @Clamping(0.0...1.0) var overlapping: Double = 0 var animatableData: CGFloat { get { overlapping } set { overlapping = newValue } } func path(in rect: CGRect) -> Path { let rectWidth = (rect.width / Double(ellipsesNumber)) * (1 + Double(ellipsesNumber - 1) * overlapping) let rectSize = CGSize(width: rectWidth, height: rect.height) var path = Path() for index in 0.. Path { let rectWidth = (rect.width / Double(rectanglesNumber)) * (1 + Double(rectanglesNumber - 1) * overlapping) let rectSize = CGSize(width: rectWidth, height: rect.height) var path = Path() for index in 0.. { var value: Value let range: ClosedRange public init(wrappedValue: Value, _ range: ClosedRange) { precondition(range.contains(wrappedValue)) self.range = range self.value = wrappedValue } public var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } ================================================ FILE: SwiftUI-Clipping/Clipping/Clipping.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 55; objects = { /* Begin PBXBuildFile section */ 6458843C26DB583C00667211 /* ClippingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458843B26DB583C00667211 /* ClippingApp.swift */; }; 6458843E26DB583C00667211 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458843D26DB583C00667211 /* ContentView.swift */; }; 6458844B26DB58A100667211 /* FSViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458844A26DB58A100667211 /* FSViews.swift */; }; 6458844D26DB58CF00667211 /* propertyWrapper+Clamping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458844C26DB58CF00667211 /* propertyWrapper+Clamping.swift */; }; 6458844F26DB590300667211 /* Shapes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458844E26DB590300667211 /* Shapes.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 6458843826DB583C00667211 /* Clipping.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Clipping.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6458843B26DB583C00667211 /* ClippingApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClippingApp.swift; sourceTree = ""; }; 6458843D26DB583C00667211 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 6458844A26DB58A100667211 /* FSViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSViews.swift; sourceTree = ""; }; 6458844C26DB58CF00667211 /* propertyWrapper+Clamping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "propertyWrapper+Clamping.swift"; sourceTree = ""; }; 6458844E26DB590300667211 /* Shapes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapes.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 6458843526DB583C00667211 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 6458842F26DB583C00667211 = { isa = PBXGroup; children = ( 6458843A26DB583C00667211 /* Clipping */, 6458843926DB583C00667211 /* Products */, ); sourceTree = ""; }; 6458843926DB583C00667211 /* Products */ = { isa = PBXGroup; children = ( 6458843826DB583C00667211 /* Clipping.app */, ); name = Products; sourceTree = ""; }; 6458843A26DB583C00667211 /* Clipping */ = { isa = PBXGroup; children = ( 6458843B26DB583C00667211 /* ClippingApp.swift */, 6458843D26DB583C00667211 /* ContentView.swift */, 6458844E26DB590300667211 /* Shapes.swift */, 6458844C26DB58CF00667211 /* propertyWrapper+Clamping.swift */, 6458844A26DB58A100667211 /* FSViews.swift */, ); path = Clipping; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 6458843726DB583C00667211 /* Clipping */ = { isa = PBXNativeTarget; buildConfigurationList = 6458844626DB583D00667211 /* Build configuration list for PBXNativeTarget "Clipping" */; buildPhases = ( 6458843426DB583C00667211 /* Sources */, 6458843526DB583C00667211 /* Frameworks */, 6458843626DB583C00667211 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Clipping; productName = Clipping; productReference = 6458843826DB583C00667211 /* Clipping.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 6458843026DB583C00667211 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1300; TargetAttributes = { 6458843726DB583C00667211 = { CreatedOnToolsVersion = 13.0; }; }; }; buildConfigurationList = 6458843326DB583C00667211 /* Build configuration list for PBXProject "Clipping" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 6458842F26DB583C00667211; productRefGroup = 6458843926DB583C00667211 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 6458843726DB583C00667211 /* Clipping */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 6458843626DB583C00667211 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 6458843426DB583C00667211 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6458844D26DB58CF00667211 /* propertyWrapper+Clamping.swift in Sources */, 6458844B26DB58A100667211 /* FSViews.swift in Sources */, 6458844F26DB590300667211 /* Shapes.swift in Sources */, 6458843E26DB583C00667211 /* ContentView.swift in Sources */, 6458843C26DB583C00667211 /* ClippingApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 6458844426DB583D00667211 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 6458844526DB583D00667211 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 6458844726DB583D00667211 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Clipping; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 6458844826DB583D00667211 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Clipping; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 6458843326DB583C00667211 /* Build configuration list for PBXProject "Clipping" */ = { isa = XCConfigurationList; buildConfigurations = ( 6458844426DB583D00667211 /* Debug */, 6458844526DB583D00667211 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6458844626DB583D00667211 /* Build configuration list for PBXNativeTarget "Clipping" */ = { isa = XCConfigurationList; buildConfigurations = ( 6458844726DB583D00667211 /* Debug */, 6458844826DB583D00667211 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 6458843026DB583C00667211 /* Project object */; } ================================================ FILE: SwiftUI-Clipping/Clipping/Clipping.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SwiftUI-Clipping/Clipping/Clipping.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: SwiftUI-Clipping/README.md ================================================ Companion app of [View clipping in SwiftUI][fs]. ![][gif] [fs]: https://fivestars.blog/articles/swiftui-clipping/ [gif]: animation.mp4 ================================================ FILE: SwiftUI-HUD/README.md ================================================ Code snippets from [Custom HUD in SwiftUI][fs]. ![][gif] [fs]: https://fivestars.blog/swiftui/swiftui-hud.html [gif]: HUD.gif ================================================ FILE: SwiftUI-HUD/global.swift ================================================ // import SwiftUI @main struct FiveStarsApp: App { @StateObject var hudState = HUDState() var body: some Scene { WindowGroup { ContentView() .hud(isPresented: $hudState.isPresented) { Label(hudState.title, systemImage: hudState.systemImage) } .environmentObject(hudState) } } } final class HUDState: ObservableObject { @Published var isPresented: Bool = false private(set) var title: String = "" private(set) var systemImage: String = "" func show(title: String, systemImage: String) { self.title = title self.systemImage = systemImage withAnimation { isPresented = true } } } struct ContentView: View { @EnvironmentObject private var hud: HUDState var body: some View { NavigationView { Button("Show/hide HUD") { hud.show(title: "Five stars", systemImage: "star.fill") } } } } extension View { func hud( isPresented: Binding, @ViewBuilder content: () -> Content ) -> some View { ZStack(alignment: .top) { self if isPresented.wrappedValue { HUD(content: content) .transition(AnyTransition.move(edge: .top).combined(with: .opacity)) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 3) { withAnimation { isPresented.wrappedValue = false } } } .zIndex(1) } } } } struct HUD: View { @ViewBuilder let content: Content var body: some View { content .padding(.horizontal, 12) .padding(16) .background( Capsule() .foregroundColor(Color.white) .shadow(color: Color(.black).opacity(0.16), radius: 12, x: 0, y: 5) ) } } ================================================ FILE: SwiftUI-HUD/local.swift ================================================ // import SwiftUI struct ContentView: View { @State private var showingHUD = false var body: some View { NavigationView { VStack { Button("Show/hide HUD") { withAnimation { showingHUD.toggle() } } } .navigationTitle("Content View") } .hud(isPresented: $showingHUD) { Label("Five stars", systemImage: "star.fill") } } } extension View { func hud( isPresented: Binding, @ViewBuilder content: () -> Content ) -> some View { ZStack(alignment: .top) { self if isPresented.wrappedValue { HUD(content: content) .transition(AnyTransition.move(edge: .top).combined(with: .opacity)) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 3) { withAnimation { isPresented.wrappedValue = false } } } .zIndex(1) } } } } struct HUD: View { @ViewBuilder let content: Content var body: some View { content .padding(.horizontal, 12) .padding(16) .background( Capsule() .foregroundColor(Color.white) .shadow(color: Color(.black).opacity(0.16), radius: 12, x: 0, y: 5) ) } } ================================================ FILE: SwiftUI-Masking/Masking/Masking/ContentView.swift ================================================ import SwiftUI struct ContentView: View { var body: some View { NavigationView { List { Section("Masking") { NavigationLink("Alignment", destination: FSView1()) NavigationLink("Views as masks", destination: FSView2()) NavigationLink("Content bleeding", destination: FSView3()) NavigationLink("Opacity gradient", destination: FSView4()) NavigationLink("List + no mask", destination: FSView5()) NavigationLink("List + fade", destination: FSView6()) NavigationLink("List + blur", destination: FSView7()) } } .navigationTitle("View Masking") } } } ================================================ FILE: SwiftUI-Masking/Masking/Masking/FSViews.swift ================================================ import SwiftUI struct FSView1: View { private let alignments: [Alignment] = [ .center, .leading, .trailing, .top, .bottom, .topLeading, .topTrailing, .bottomLeading, .bottomTrailing ] @State var alignment: Alignment = .center var body: some View { VStack { Color.yellow .frame(width: 200, height: 200) .mask(alignment: alignment) { Rectangle() .frame(width: 60, height: 60) } .border(.red) Button("Random alignment") { withAnimation { alignment = alignments.filter { $0 != alignment } .randomElement()! } } } } } struct FSView2: View { var body: some View { Color.yellow .frame(width: 200, height: 200) .mask { Text("MASK") .fontWeight(.black) .font(.system(size: 60)) } .border(Color.red) } } struct FSView3: View { var body: some View { Color.yellow .frame(width: 300, height: 300) .frame(width: 200, height: 200) .mask { Text("MASK") .fontWeight(.black) .font(.system(size: 80)) .fixedSize() // 👈🏻 Ignores the proposed 200x200 points size } .border(Color.red) } } struct FSView4: View { var body: some View { Color.yellow .frame(width: 200, height: 200) .mask { LinearGradient(colors: [.clear, .black, .clear], startPoint: .leading, endPoint: .trailing) } .border(Color.red) } } struct FSView5: View { var body: some View { ScrollView { ForEach(1..<30) { _ in Text("Five Stars") .font(.largeTitle) } .frame(maxWidth: .infinity) } .safeAreaInset(edge: .bottom) { Button { } label: { Text("Continue") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) .padding(.horizontal) } } } struct FSView6: View { var body: some View { ScrollView { ForEach(1..<30) { _ in Text("Five Stars") .font(.largeTitle) } .frame(maxWidth: .infinity) } .safeAreaInset(edge: .bottom) { Button { } label: { Text("Continue") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) .padding(.horizontal) .background { Color(uiColor: .systemBackground) .mask(alignment: .top) { VStack(spacing: 0) { LinearGradient( stops: [ Gradient.Stop(color: .clear, location: .zero), Gradient.Stop(color: .black, location: 1.0) ], startPoint: .top, endPoint: .bottom ) .frame(height: 32) Color.black } } .padding(.top, -32) .ignoresSafeArea(.all, edges: .bottom) } } } } struct FSView7: View { var body: some View { ScrollView { ForEach(1..<30) { _ in Text("Five Stars") .font(.largeTitle) } .frame(maxWidth: .infinity) } .safeAreaInset(edge: .bottom) { Button { } label: { Text("Continue") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) .padding(.horizontal) .background { Color.clear .background(.ultraThinMaterial) .mask(alignment: .top) { VStack(spacing: 0) { LinearGradient( stops: [ Gradient.Stop(color: .clear, location: .zero), Gradient.Stop(color: .black, location: 1.0) ], startPoint: .top, endPoint: .bottom ) .frame(height: 32) Color.black } } .padding(.top, -32) .ignoresSafeArea(.all, edges: .bottom) } } } } ================================================ FILE: SwiftUI-Masking/Masking/Masking/MaskingApp.swift ================================================ import SwiftUI @main struct MaskingApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: SwiftUI-Masking/Masking/Masking.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 55; objects = { /* Begin PBXBuildFile section */ 6458843C26DB583C00667211 /* MaskingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458843B26DB583C00667211 /* MaskingApp.swift */; }; 6458843E26DB583C00667211 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458843D26DB583C00667211 /* ContentView.swift */; }; 6458844B26DB58A100667211 /* FSViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458844A26DB58A100667211 /* FSViews.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 6458843826DB583C00667211 /* Masking.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Masking.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6458843B26DB583C00667211 /* MaskingApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaskingApp.swift; sourceTree = ""; }; 6458843D26DB583C00667211 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 6458844A26DB58A100667211 /* FSViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSViews.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 6458843526DB583C00667211 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 6458842F26DB583C00667211 = { isa = PBXGroup; children = ( 6458843A26DB583C00667211 /* Masking */, 6458843926DB583C00667211 /* Products */, ); sourceTree = ""; }; 6458843926DB583C00667211 /* Products */ = { isa = PBXGroup; children = ( 6458843826DB583C00667211 /* Masking.app */, ); name = Products; sourceTree = ""; }; 6458843A26DB583C00667211 /* Masking */ = { isa = PBXGroup; children = ( 6458843B26DB583C00667211 /* MaskingApp.swift */, 6458843D26DB583C00667211 /* ContentView.swift */, 6458844A26DB58A100667211 /* FSViews.swift */, ); path = Masking; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 6458843726DB583C00667211 /* Masking */ = { isa = PBXNativeTarget; buildConfigurationList = 6458844626DB583D00667211 /* Build configuration list for PBXNativeTarget "Masking" */; buildPhases = ( 6458843426DB583C00667211 /* Sources */, 6458843526DB583C00667211 /* Frameworks */, 6458843626DB583C00667211 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Masking; productName = Masking; productReference = 6458843826DB583C00667211 /* Masking.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 6458843026DB583C00667211 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1300; TargetAttributes = { 6458843726DB583C00667211 = { CreatedOnToolsVersion = 13.0; }; }; }; buildConfigurationList = 6458843326DB583C00667211 /* Build configuration list for PBXProject "Masking" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 6458842F26DB583C00667211; productRefGroup = 6458843926DB583C00667211 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 6458843726DB583C00667211 /* Masking */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 6458843626DB583C00667211 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 6458843426DB583C00667211 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6458844B26DB58A100667211 /* FSViews.swift in Sources */, 6458843E26DB583C00667211 /* ContentView.swift in Sources */, 6458843C26DB583C00667211 /* MaskingApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 6458844426DB583D00667211 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 6458844526DB583D00667211 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 6458844726DB583D00667211 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Masking; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 6458844826DB583D00667211 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.Masking; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 6458843326DB583C00667211 /* Build configuration list for PBXProject "Masking" */ = { isa = XCConfigurationList; buildConfigurations = ( 6458844426DB583D00667211 /* Debug */, 6458844526DB583D00667211 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6458844626DB583D00667211 /* Build configuration list for PBXNativeTarget "Masking" */ = { isa = XCConfigurationList; buildConfigurations = ( 6458844726DB583D00667211 /* Debug */, 6458844826DB583D00667211 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 6458843026DB583C00667211 /* Project object */; } ================================================ FILE: SwiftUI-Masking/Masking/Masking.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SwiftUI-Masking/Masking/Masking.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: SwiftUI-Masking/README.md ================================================ Companion app of [View masking in SwiftUI][fs]. [fs]: https://fivestars.blog/articles/swiftui-masking/ ================================================ FILE: SwiftUI-Reverse-Mask/README.md ================================================ Companion app of [How to apply a reverse mask in SwiftUI][fs]. [fs]: https://fivestars.blog/articles/reverse-masks-how-to/ ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/ContentView.swift ================================================ import SwiftUI struct ContentView: View { var body: some View { NavigationView { List { Section("Reverse masking") { NavigationLink("Star Mask", destination: FSView1()) NavigationLink("Text Mask", destination: FSView2()) NavigationLink("Fading effect", destination: FSView3()) } Section("Blending") { NavigationLink("Rectangle vs Circle", destination: FSView4()) NavigationLink("Fade effect vs Circle", destination: FSView5()) } } .navigationTitle("View Masking") } } } ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/FSViews.swift ================================================ import SwiftUI struct FSView1: View { var body: some View { HStack { Color.yellow .frame(width: 200, height: 200) .mask { Star() } .border(.red) Color.yellow .frame(width: 200, height: 200) .reverseMask { Star() } .border(.red) } .padding() } } struct FSView2: View { var body: some View { HStack { Color.yellow .frame(width: 200, height: 200) .mask { Text("MASK") .font(.system(size: 60).weight(.black)) } .border(.red) Color.yellow .frame(width: 200, height: 200) .reverseMask { Text("MASK") .font(.system(size: 60).weight(.black)) } .border(.red) } .padding() } } struct FSView3: View { var body: some View { HStack { Color.yellow .frame(width: 200, height: 200) .mask { LinearGradient( colors: [.clear, .black], startPoint: .leading, endPoint: .trailing ) } .border(.red) Color.yellow .frame(width: 200, height: 200) .reverseMask { LinearGradient( colors: [.clear, .black], startPoint: .leading, endPoint: .trailing ) } .border(.red) } .padding() } } struct FSView4: View { var body: some View { HStack { ZStack { Rectangle() // destination Circle() // source .blendMode(.destinationOut) } .compositingGroup() .frame(width: 200, height: 200) .border(.red) ZStack { Circle() // destination Rectangle() // source .blendMode(.destinationOut) } .compositingGroup() .frame(width: 200, height: 200) .border(.red) } .padding() } } struct FSView5: View { var body: some View { HStack { ZStack { LinearGradient( colors: [.clear, .black], startPoint: .leading, endPoint: .trailing ) // destination Circle() // source .blendMode(.destinationOut) } .compositingGroup() .frame(width: 200, height: 200) .border(.red) ZStack { Circle() // destination LinearGradient( colors: [.clear, .black], startPoint: .leading, endPoint: .trailing ) // source .blendMode(.destinationOut) } .compositingGroup() .frame(width: 200, height: 200) .border(.red) } .padding() } } ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/ReverseMaskingApp.swift ================================================ import SwiftUI @main struct ReverseMaskingApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/Star.swift ================================================ // import SwiftUI struct Star: Shape { @Clamping(0...Int.max) var points: Int = 5 var innerRatio = 0.4 func path(in rect : CGRect) -> Path { let center = CGPoint(x: rect.midX, y: rect.midY) let angle: Double = .pi / Double(points) var path = Path() var startPoint: CGPoint = rect.origin let outerRadius = min(rect.width / 2, rect.height / 2) let innerRadius = outerRadius * innerRatio let maxCorners = 2 * points for corner in 0 ..< maxCorners { let radius = (corner % 2) == 0 ? outerRadius : innerRadius let x = center.x + cos(Double(corner) * angle) * radius let y = center.y + sin(Double(corner) * angle) * radius let point = CGPoint(x: x, y: y) if corner == 0 { startPoint = point path.move(to: point) } else { path.addLine(to: point) } if corner == (maxCorners - 1) { path.addLine(to: startPoint) } } return path } } @propertyWrapper public struct Clamping { var value: Value let range: ClosedRange public init(wrappedValue: Value, _ range: ClosedRange) { precondition(range.contains(wrappedValue)) self.range = range self.value = wrappedValue } public var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/View+reverseMask.swift ================================================ // import SwiftUI extension View { @inlinable public func reverseMask( alignment: Alignment = .center, @ViewBuilder _ mask: () -> Mask ) -> some View { self.mask { Rectangle() .overlay(alignment: alignment) { mask() .blendMode(.destinationOut) } } } } ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 55; objects = { /* Begin PBXBuildFile section */ 6458843C26DB583C00667211 /* ReverseMaskingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458843B26DB583C00667211 /* ReverseMaskingApp.swift */; }; 6458843E26DB583C00667211 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458843D26DB583C00667211 /* ContentView.swift */; }; 6458844B26DB58A100667211 /* FSViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6458844A26DB58A100667211 /* FSViews.swift */; }; 64880B9F26EC82DB00B340D6 /* Star.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64880B9E26EC82DB00B340D6 /* Star.swift */; }; 64880BA126EC876C00B340D6 /* View+reverseMask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64880BA026EC876C00B340D6 /* View+reverseMask.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 6458843826DB583C00667211 /* Reverse-Masking.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Reverse-Masking.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 6458843B26DB583C00667211 /* ReverseMaskingApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReverseMaskingApp.swift; sourceTree = ""; }; 6458843D26DB583C00667211 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 6458844A26DB58A100667211 /* FSViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSViews.swift; sourceTree = ""; }; 64880B9E26EC82DB00B340D6 /* Star.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Star.swift; sourceTree = ""; }; 64880BA026EC876C00B340D6 /* View+reverseMask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+reverseMask.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 6458843526DB583C00667211 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 6458842F26DB583C00667211 = { isa = PBXGroup; children = ( 6458843A26DB583C00667211 /* Reverse-Masking */, 6458843926DB583C00667211 /* Products */, ); sourceTree = ""; }; 6458843926DB583C00667211 /* Products */ = { isa = PBXGroup; children = ( 6458843826DB583C00667211 /* Reverse-Masking.app */, ); name = Products; sourceTree = ""; }; 6458843A26DB583C00667211 /* Reverse-Masking */ = { isa = PBXGroup; children = ( 6458843B26DB583C00667211 /* ReverseMaskingApp.swift */, 6458843D26DB583C00667211 /* ContentView.swift */, 64880BA026EC876C00B340D6 /* View+reverseMask.swift */, 64880B9E26EC82DB00B340D6 /* Star.swift */, 6458844A26DB58A100667211 /* FSViews.swift */, ); path = "Reverse-Masking"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 6458843726DB583C00667211 /* Reverse-Masking */ = { isa = PBXNativeTarget; buildConfigurationList = 6458844626DB583D00667211 /* Build configuration list for PBXNativeTarget "Reverse-Masking" */; buildPhases = ( 6458843426DB583C00667211 /* Sources */, 6458843526DB583C00667211 /* Frameworks */, 6458843626DB583C00667211 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Reverse-Masking"; productName = Masking; productReference = 6458843826DB583C00667211 /* Reverse-Masking.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 6458843026DB583C00667211 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1300; TargetAttributes = { 6458843726DB583C00667211 = { CreatedOnToolsVersion = 13.0; }; }; }; buildConfigurationList = 6458843326DB583C00667211 /* Build configuration list for PBXProject "Reverse-Masking" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 6458842F26DB583C00667211; productRefGroup = 6458843926DB583C00667211 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 6458843726DB583C00667211 /* Reverse-Masking */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 6458843626DB583C00667211 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 6458843426DB583C00667211 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6458844B26DB58A100667211 /* FSViews.swift in Sources */, 6458843E26DB583C00667211 /* ContentView.swift in Sources */, 64880BA126EC876C00B340D6 /* View+reverseMask.swift in Sources */, 6458843C26DB583C00667211 /* ReverseMaskingApp.swift in Sources */, 64880B9F26EC82DB00B340D6 /* Star.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 6458844426DB583D00667211 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 6458844526DB583D00667211 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 6458844726DB583D00667211 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "blog.fivestars.Reverse-Masking"; PRODUCT_NAME = "Reverse-Masking"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 6458844826DB583D00667211 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "blog.fivestars.Reverse-Masking"; PRODUCT_NAME = "Reverse-Masking"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 6458843326DB583C00667211 /* Build configuration list for PBXProject "Reverse-Masking" */ = { isa = XCConfigurationList; buildConfigurations = ( 6458844426DB583D00667211 /* Debug */, 6458844526DB583D00667211 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6458844626DB583D00667211 /* Build configuration list for PBXNativeTarget "Reverse-Masking" */ = { isa = XCConfigurationList; buildConfigurations = ( 6458844726DB583D00667211 /* Debug */, 6458844826DB583D00667211 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 6458843026DB583C00667211 /* Project object */; } ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: SwiftUI-read-a-view-size/README.md ================================================ Code snippet from [How to read a view size in SwiftUI][fs]. [fs]: https://fivestars.blog/articles/swiftui-share-layout-information/ ================================================ FILE: SwiftUI-read-a-view-size/View+readSize.swift ================================================ // Original article here: https://fivestars.blog/articles/swiftui-share-layout-information/ import SwiftUI /* Example: var body: some View { childView .readSize { newSize in print("The new child size is: \(newSize)") } } */ extension View { func readSize(onChange: @escaping (CGSize) -> Void) -> some View { background( GeometryReader { geometryProxy in Color.clear .preference(key: SizePreferenceKey.self, value: geometryProxy.size) } ) .onPreferenceChange(SizePreferenceKey.self, perform: onChange) } } private struct SizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} } ================================================ FILE: Truncable-Text/ContentView.swift ================================================ // import SwiftUI struct ContentView: View { @State var isTruncated: Bool = false @State var forceFullText: Bool = false var body: some View { VStack { if forceFullText { text .fixedSize(horizontal: false, vertical: true) } else { TruncableText( text: text, lineLimit: 3 ) { isTruncated = $0 } } if isTruncated && !forceFullText { Button("show all") { forceFullText = true } } } .padding() } var text: Text { Text("Introducing a new kind of fitness experience. One that dynamically integrates your personal metrics from Apple Watch, along with music from your favorite artists, to inspire like no other workout in the world.") } } struct TruncableText: View { let text: Text let lineLimit: Int? @State private var intrinsicSize: CGSize = .zero @State private var truncatedSize: CGSize = .zero let isTruncatedUpdate: (_ isTruncated: Bool) -> Void var body: some View { text .lineLimit(lineLimit) .readSize { size in truncatedSize = size isTruncatedUpdate(truncatedSize != intrinsicSize) } .background( text .fixedSize(horizontal: false, vertical: true) .hidden() .readSize { size in intrinsicSize = size isTruncatedUpdate(truncatedSize != intrinsicSize) } ) } } public extension View { func readSize(onChange: @escaping (CGSize) -> Void) -> some View { background( GeometryReader { geometryProxy in Color.clear .preference(key: SizePreferenceKey.self, value: geometryProxy.size) } ) .onPreferenceChange(SizePreferenceKey.self, perform: onChange) } } private struct SizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ================================================ FILE: Truncable-Text/README.md ================================================ Code snippet from [How to check if Text is truncated?][fs]. ![][gif] [fs]: https://fivestars.blog/swiftui/trucated-text.html [gif]: truncable.gif ================================================ FILE: Windows/README.md ================================================ Code snippet from [How to manage windows in SwiftUI][fs]. ![][gif] [fs]: https://fivestars.blog/articles/swiftui-windows/ [gif]: HUD.gif ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/FSAppDelegate.swift ================================================ import SwiftUI final class FSAppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions ) -> UISceneConfiguration { let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role) sceneConfig.delegateClass = FSSceneDelegate.self // 👈🏻 return sceneConfig } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/FSSceneDelegate.swift ================================================ import SwiftUI final class FSSceneDelegate: UIResponder, UIWindowSceneDelegate, ObservableObject { var hudState: HudState? { didSet { setupHudWindow() } } var toastWindow: UIWindow? weak var windowScene: UIWindowScene? func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { windowScene = scene as? UIWindowScene } func setupHudWindow() { guard let windowScene = windowScene, let toastState = hudState else { return } let toastViewController = UIHostingController(rootView: HudSceneView().environmentObject(toastState)) toastViewController.view.backgroundColor = .clear let toastWindow = PassThroughWindow(windowScene: windowScene) toastWindow.rootViewController = toastViewController toastWindow.isHidden = false self.toastWindow = toastWindow } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/FSSwiftUILifecycleApp.swift ================================================ import SwiftUI @main struct FSSwiftUILifecycleApp: App { @StateObject var hudState = HudState() @UIApplicationDelegateAdaptor var delegate: FSAppDelegate var body: some Scene { WindowGroup { MainSceneView() .environmentObject(hudState) } } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/HudSceneView.swift ================================================ import SwiftUI struct HudSceneView: View { @EnvironmentObject var hudState: HudState var body: some View { Color.clear .ignoresSafeArea(.all) .hud(isPresented: $hudState.isPresented) { Label(hudState.title, systemImage: hudState.systemImage) } } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/HudState.swift ================================================ import Combine final class HudState: ObservableObject { @Published var isPresented: Bool = false private(set) var title: String = "" private(set) var systemImage: String = "" func show(title: String, systemImage: String) { self.title = title self.systemImage = systemImage isPresented = true } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/MainSceneView.swift ================================================ import SwiftUI struct MainSceneView: View { @EnvironmentObject var sceneDelegate: FSSceneDelegate @EnvironmentObject var hudState: HudState @State var showingSheet = false var body: some View { NavigationView { VStack { Button("Show hud") { hudState.show(title: "Five Stars", systemImage: "star.fill") } Button("Show sheet") { showingSheet = true } } } .font(.largeTitle) .frame(maxWidth: .infinity) .sheet(isPresented: $showingSheet) { Text("Sheet") } .onAppear { sceneDelegate.hudState = hudState } } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/PassThroughWindow.swift ================================================ import UIKit class PassThroughWindow: UIWindow { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let hitView = super.hitTest(point, with: event) else { return nil } return rootViewController?.view == hitView ? nil : hitView } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/View+hud.swift ================================================ import SwiftUI extension View { func hud( isPresented: Binding, @ViewBuilder content: () -> Content ) -> some View { overlay(alignment: .top) { HUD(content: content) .onTapGesture { isPresented.wrappedValue = false } .offset(y: isPresented.wrappedValue ? 0 : -128) .animation(Animation.spring(), value: isPresented.wrappedValue) } } } private struct HUD: View { @ViewBuilder let content: Content var body: some View { content .padding(.horizontal, 12) .padding(16) .background( Capsule() .foregroundColor(Color.white) .shadow(color: Color(.black).opacity(0.16), radius: 12, x: 0, y: 5) ) } } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 55; objects = { /* Begin PBXBuildFile section */ 64A8D21326BFE22800CB377D /* View+hud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D20B26BFE22800CB377D /* View+hud.swift */; }; 64A8D21426BFE22800CB377D /* PassThroughWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D20C26BFE22800CB377D /* PassThroughWindow.swift */; }; 64A8D21526BFE22800CB377D /* FSAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D20D26BFE22800CB377D /* FSAppDelegate.swift */; }; 64A8D21626BFE22800CB377D /* FSSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D20E26BFE22800CB377D /* FSSceneDelegate.swift */; }; 64A8D21726BFE22800CB377D /* FSSwiftUILifecycleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D20F26BFE22800CB377D /* FSSwiftUILifecycleApp.swift */; }; 64A8D21826BFE22800CB377D /* HudSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D21026BFE22800CB377D /* HudSceneView.swift */; }; 64A8D21926BFE22800CB377D /* MainSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D21126BFE22800CB377D /* MainSceneView.swift */; }; 64A8D21A26BFE22800CB377D /* HudState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D21226BFE22800CB377D /* HudState.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64A8D1FA26BFE1DE00CB377D /* FSSwiftUILifecycleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FSSwiftUILifecycleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64A8D20B26BFE22800CB377D /* View+hud.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+hud.swift"; sourceTree = ""; }; 64A8D20C26BFE22800CB377D /* PassThroughWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassThroughWindow.swift; sourceTree = ""; }; 64A8D20D26BFE22800CB377D /* FSAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FSAppDelegate.swift; sourceTree = ""; }; 64A8D20E26BFE22800CB377D /* FSSceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FSSceneDelegate.swift; sourceTree = ""; }; 64A8D20F26BFE22800CB377D /* FSSwiftUILifecycleApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FSSwiftUILifecycleApp.swift; sourceTree = ""; }; 64A8D21026BFE22800CB377D /* HudSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HudSceneView.swift; sourceTree = ""; }; 64A8D21126BFE22800CB377D /* MainSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainSceneView.swift; sourceTree = ""; }; 64A8D21226BFE22800CB377D /* HudState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HudState.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64A8D1F726BFE1DE00CB377D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64A8D1F126BFE1DE00CB377D = { isa = PBXGroup; children = ( 64A8D1FC26BFE1DE00CB377D /* FSSwiftUILifecycleApp */, 64A8D1FB26BFE1DE00CB377D /* Products */, ); sourceTree = ""; }; 64A8D1FB26BFE1DE00CB377D /* Products */ = { isa = PBXGroup; children = ( 64A8D1FA26BFE1DE00CB377D /* FSSwiftUILifecycleApp.app */, ); name = Products; sourceTree = ""; }; 64A8D1FC26BFE1DE00CB377D /* FSSwiftUILifecycleApp */ = { isa = PBXGroup; children = ( 64A8D20F26BFE22800CB377D /* FSSwiftUILifecycleApp.swift */, 64A8D20D26BFE22800CB377D /* FSAppDelegate.swift */, 64A8D20E26BFE22800CB377D /* FSSceneDelegate.swift */, 64A8D21026BFE22800CB377D /* HudSceneView.swift */, 64A8D21226BFE22800CB377D /* HudState.swift */, 64A8D21126BFE22800CB377D /* MainSceneView.swift */, 64A8D20C26BFE22800CB377D /* PassThroughWindow.swift */, 64A8D20B26BFE22800CB377D /* View+hud.swift */, ); path = FSSwiftUILifecycleApp; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64A8D1F926BFE1DE00CB377D /* FSSwiftUILifecycleApp */ = { isa = PBXNativeTarget; buildConfigurationList = 64A8D20826BFE1DF00CB377D /* Build configuration list for PBXNativeTarget "FSSwiftUILifecycleApp" */; buildPhases = ( 64A8D1F626BFE1DE00CB377D /* Sources */, 64A8D1F726BFE1DE00CB377D /* Frameworks */, 64A8D1F826BFE1DE00CB377D /* Resources */, ); buildRules = ( ); dependencies = ( ); name = FSSwiftUILifecycleApp; productName = FSSwiftUILifecycleApp; productReference = 64A8D1FA26BFE1DE00CB377D /* FSSwiftUILifecycleApp.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64A8D1F226BFE1DE00CB377D /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1300; TargetAttributes = { 64A8D1F926BFE1DE00CB377D = { CreatedOnToolsVersion = 13.0; LastSwiftMigration = 1300; }; }; }; buildConfigurationList = 64A8D1F526BFE1DE00CB377D /* Build configuration list for PBXProject "FSSwiftUILifecycleApp" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 64A8D1F126BFE1DE00CB377D; productRefGroup = 64A8D1FB26BFE1DE00CB377D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64A8D1F926BFE1DE00CB377D /* FSSwiftUILifecycleApp */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64A8D1F826BFE1DE00CB377D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64A8D1F626BFE1DE00CB377D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64A8D21726BFE22800CB377D /* FSSwiftUILifecycleApp.swift in Sources */, 64A8D21926BFE22800CB377D /* MainSceneView.swift in Sources */, 64A8D21826BFE22800CB377D /* HudSceneView.swift in Sources */, 64A8D21A26BFE22800CB377D /* HudState.swift in Sources */, 64A8D21426BFE22800CB377D /* PassThroughWindow.swift in Sources */, 64A8D21326BFE22800CB377D /* View+hud.swift in Sources */, 64A8D21626BFE22800CB377D /* FSSceneDelegate.swift in Sources */, 64A8D21526BFE22800CB377D /* FSAppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 64A8D20626BFE1DF00CB377D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 64A8D20726BFE1DF00CB377D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 64A8D20926BFE1DF00CB377D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.FSSwiftUILifecycleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64A8D20A26BFE1DF00CB377D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.FSSwiftUILifecycleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64A8D1F526BFE1DE00CB377D /* Build configuration list for PBXProject "FSSwiftUILifecycleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 64A8D20626BFE1DF00CB377D /* Debug */, 64A8D20726BFE1DF00CB377D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64A8D20826BFE1DF00CB377D /* Build configuration list for PBXNativeTarget "FSSwiftUILifecycleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 64A8D20926BFE1DF00CB377D /* Debug */, 64A8D20A26BFE1DF00CB377D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64A8D1F226BFE1DE00CB377D /* Project object */; } ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/FSAppDelegate.swift ================================================ // import UIKit @main class FSAppDelegate: UIResponder, UIApplicationDelegate { } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/FSSceneDelegate.swift ================================================ import SwiftUI final class FSSceneDelegate: UIResponder, UIWindowSceneDelegate, ObservableObject { lazy var hudState = HudState() var keyWindow: UIWindow? var hudWindow: UIWindow? func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { if let windowScene = scene as? UIWindowScene { setupMainWindow(in: windowScene) setupToastWindow(in: windowScene) } } func setupMainWindow(in scene: UIWindowScene) { let window = UIWindow(windowScene: scene) window.rootViewController = UIHostingController(rootView: MainSceneView().environmentObject(hudState)) self.keyWindow = window window.makeKeyAndVisible() } func setupToastWindow(in scene: UIWindowScene) { let toastViewController = UIHostingController(rootView: HudSceneView().environmentObject(hudState)) toastViewController.view.backgroundColor = .clear let hudWindow = PassThroughWindow(windowScene: scene) hudWindow.rootViewController = toastViewController hudWindow.isHidden = false self.hudWindow = hudWindow } } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/HudSceneView.swift ================================================ import SwiftUI struct HudSceneView: View { @EnvironmentObject var hudState: HudState var body: some View { Color.clear .ignoresSafeArea(.all) .hud(isPresented: $hudState.isPresented) { Label(hudState.title, systemImage: hudState.systemImage) } } } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/HudState.swift ================================================ import Combine final class HudState: ObservableObject { @Published var isPresented: Bool = false private(set) var title: String = "" private(set) var systemImage: String = "" func show(title: String, systemImage: String) { self.title = title self.systemImage = systemImage isPresented = true } } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/Info.plist ================================================ UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).FSSceneDelegate ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/MainSceneView.swift ================================================ import SwiftUI struct MainSceneView: View { @EnvironmentObject var hudState: HudState @State var showingSheet = false var body: some View { NavigationView { VStack { Button("Show hud") { hudState.show(title: "Five Stars", systemImage: "star.fill") } Button("Show sheet") { showingSheet = true } } } .font(.largeTitle) .frame(maxWidth: .infinity) .sheet(isPresented: $showingSheet) { Text("Sheet") } } } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/PassThroughWindow.swift ================================================ import UIKit class PassThroughWindow: UIWindow { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let hitView = super.hitTest(point, with: event) else { return nil } return rootViewController?.view == hitView ? nil : hitView } } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp/View+hud.swift ================================================ import SwiftUI extension View { func hud( isPresented: Binding, @ViewBuilder content: () -> Content ) -> some View { overlay(alignment: .top) { HUD(content: content) .onTapGesture { isPresented.wrappedValue = false } .offset(y: isPresented.wrappedValue ? 0 : -128) .animation(Animation.spring(), value: isPresented.wrappedValue) } } } private struct HUD: View { @ViewBuilder let content: Content var body: some View { content .padding(.horizontal, 12) .padding(16) .background( Capsule() .foregroundColor(Color.white) .shadow(color: Color(.black).opacity(0.16), radius: 12, x: 0, y: 5) ) } } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 55; objects = { /* Begin PBXBuildFile section */ 64A8D25226BFE5C400CB377D /* FSAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D25126BFE5C400CB377D /* FSAppDelegate.swift */; }; 64A8D25E26BFE5C500CB377D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64A8D25C26BFE5C500CB377D /* LaunchScreen.storyboard */; }; 64A8D26C26BFE67500CB377D /* PassThroughWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D26526BFE67500CB377D /* PassThroughWindow.swift */; }; 64A8D26D26BFE67500CB377D /* HudSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D26626BFE67500CB377D /* HudSceneView.swift */; }; 64A8D26E26BFE67500CB377D /* MainSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D26726BFE67500CB377D /* MainSceneView.swift */; }; 64A8D26F26BFE67500CB377D /* HudState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D26826BFE67500CB377D /* HudState.swift */; }; 64A8D27026BFE67500CB377D /* FSSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D26926BFE67500CB377D /* FSSceneDelegate.swift */; }; 64A8D27226BFE67500CB377D /* View+hud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8D26B26BFE67500CB377D /* View+hud.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 64A8D24E26BFE5C400CB377D /* FSUIKitLifecycleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FSUIKitLifecycleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64A8D25126BFE5C400CB377D /* FSAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSAppDelegate.swift; sourceTree = ""; }; 64A8D25D26BFE5C500CB377D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 64A8D25F26BFE5C500CB377D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64A8D26526BFE67500CB377D /* PassThroughWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassThroughWindow.swift; sourceTree = ""; }; 64A8D26626BFE67500CB377D /* HudSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HudSceneView.swift; sourceTree = ""; }; 64A8D26726BFE67500CB377D /* MainSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainSceneView.swift; sourceTree = ""; }; 64A8D26826BFE67500CB377D /* HudState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HudState.swift; sourceTree = ""; }; 64A8D26926BFE67500CB377D /* FSSceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FSSceneDelegate.swift; sourceTree = ""; }; 64A8D26B26BFE67500CB377D /* View+hud.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+hud.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 64A8D24B26BFE5C400CB377D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 64A8D24526BFE5C400CB377D = { isa = PBXGroup; children = ( 64A8D25026BFE5C400CB377D /* FSUIKitLifecycleApp */, 64A8D24F26BFE5C400CB377D /* Products */, ); sourceTree = ""; }; 64A8D24F26BFE5C400CB377D /* Products */ = { isa = PBXGroup; children = ( 64A8D24E26BFE5C400CB377D /* FSUIKitLifecycleApp.app */, ); name = Products; sourceTree = ""; }; 64A8D25026BFE5C400CB377D /* FSUIKitLifecycleApp */ = { isa = PBXGroup; children = ( 64A8D25126BFE5C400CB377D /* FSAppDelegate.swift */, 64A8D26926BFE67500CB377D /* FSSceneDelegate.swift */, 64A8D26626BFE67500CB377D /* HudSceneView.swift */, 64A8D26826BFE67500CB377D /* HudState.swift */, 64A8D26726BFE67500CB377D /* MainSceneView.swift */, 64A8D26526BFE67500CB377D /* PassThroughWindow.swift */, 64A8D26B26BFE67500CB377D /* View+hud.swift */, 64A8D25C26BFE5C500CB377D /* LaunchScreen.storyboard */, 64A8D25F26BFE5C500CB377D /* Info.plist */, ); path = FSUIKitLifecycleApp; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 64A8D24D26BFE5C400CB377D /* FSUIKitLifecycleApp */ = { isa = PBXNativeTarget; buildConfigurationList = 64A8D26226BFE5C500CB377D /* Build configuration list for PBXNativeTarget "FSUIKitLifecycleApp" */; buildPhases = ( 64A8D24A26BFE5C400CB377D /* Sources */, 64A8D24B26BFE5C400CB377D /* Frameworks */, 64A8D24C26BFE5C400CB377D /* Resources */, ); buildRules = ( ); dependencies = ( ); name = FSUIKitLifecycleApp; productName = FSUIKitLifecycleApp; productReference = 64A8D24E26BFE5C400CB377D /* FSUIKitLifecycleApp.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 64A8D24626BFE5C400CB377D /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1300; TargetAttributes = { 64A8D24D26BFE5C400CB377D = { CreatedOnToolsVersion = 13.0; }; }; }; buildConfigurationList = 64A8D24926BFE5C400CB377D /* Build configuration list for PBXProject "FSUIKitLifecycleApp" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 64A8D24526BFE5C400CB377D; productRefGroup = 64A8D24F26BFE5C400CB377D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 64A8D24D26BFE5C400CB377D /* FSUIKitLifecycleApp */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 64A8D24C26BFE5C400CB377D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64A8D25E26BFE5C500CB377D /* LaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 64A8D24A26BFE5C400CB377D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64A8D27226BFE67500CB377D /* View+hud.swift in Sources */, 64A8D25226BFE5C400CB377D /* FSAppDelegate.swift in Sources */, 64A8D26C26BFE67500CB377D /* PassThroughWindow.swift in Sources */, 64A8D26F26BFE67500CB377D /* HudState.swift in Sources */, 64A8D27026BFE67500CB377D /* FSSceneDelegate.swift in Sources */, 64A8D26E26BFE67500CB377D /* MainSceneView.swift in Sources */, 64A8D26D26BFE67500CB377D /* HudSceneView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 64A8D25C26BFE5C500CB377D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 64A8D25D26BFE5C500CB377D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 64A8D26026BFE5C500CB377D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 64A8D26126BFE5C500CB377D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 64A8D26326BFE5C500CB377D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FSUIKitLifecycleApp/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 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 = blog.fivestars.FSUIKitLifecycleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 64A8D26426BFE5C500CB377D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FSUIKitLifecycleApp/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 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 = blog.fivestars.FSUIKitLifecycleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 64A8D24926BFE5C400CB377D /* Build configuration list for PBXProject "FSUIKitLifecycleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 64A8D26026BFE5C500CB377D /* Debug */, 64A8D26126BFE5C500CB377D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 64A8D26226BFE5C500CB377D /* Build configuration list for PBXNativeTarget "FSUIKitLifecycleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 64A8D26326BFE5C500CB377D /* Debug */, 64A8D26426BFE5C500CB377D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 64A8D24626BFE5C400CB377D /* Project object */; } ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Windows/UIKit-life-cycle/FSUIKitLifecycleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning