Showing preview only (318K chars total). Download the full file or copy to clipboard to get everything.
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<Content: View>: 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<Content: View>: 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<Content: View>: 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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 = "<group>"; };
64B79F04253EBD9000168B88 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
64B79F0B253EBD9100168B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
64B79F15253EBDE600168B88 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
64B79F18253EBDF100168B88 /* SocialSignInView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialSignInView.swift; sourceTree = "<group>"; };
64B79F19253EBDF100168B88 /* ExperimentalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExperimentalView.swift; sourceTree = "<group>"; };
64B79F1A253EBDF100168B88 /* View+ReadSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+ReadSize.swift"; sourceTree = "<group>"; };
64B79F1B253EBDF100168B88 /* AdaptiveView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveView.swift; sourceTree = "<group>"; };
64B79F1C253EBDF100168B88 /* AdaptiveExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveExampleView.swift; sourceTree = "<group>"; };
64B79F1D253EBDF100168B88 /* SignInButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignInButton.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
64B79F00253EBD9000168B88 /* Products */ = {
isa = PBXGroup;
children = (
64B79EFF253EBD9000168B88 /* AdaptiveViews.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Adaptive-Views/AdaptiveViews/AdaptiveViews.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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<SourceView: View, DestinationView: View>: 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<MainView: View>: 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<TopContent: View>: View {
var placeholder: LocalizedStringKey = "Placeholder"
@Binding var text: String
var appearance: Appearance = .default
var topContent: TopContent
init(
placeholder: LocalizedStringKey = "",
text: Binding<String>,
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<String>,
appearance: Appearance = .default
) {
self.placeholder = placeholder
self._text = text
self.appearance = appearance
self.topContent = EmptyView()
}
}
extension FSTextField {
init<TopTrailingContent: View>(
title: LocalizedStringKey,
placeholder: LocalizedStringKey = "",
text: Binding<String>,
appearance: Appearance = .default,
@ViewBuilder topTrailingContent: () -> TopTrailingContent
) where TopContent == HStack<TupleView<(Text, Spacer, TopTrailingContent)>> {
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<String>,
appearance: Appearance = .default
) where TopContent == HStack<TupleView<(Text, Spacer, EmptyView)>> {
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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 = "<group>"; };
647F0AAF2535871A009ED3F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
647F0AB92535874F009ED3F5 /* FSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FSTextField.swift; sourceTree = "<group>"; };
647F0ABA2535874F009ED3F5 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContentView.swift; path = ../../../../../Desktop/playground/Playground/Playground/ContentView.swift; sourceTree = "<group>"; };
647F0ABB2535874F009ED3F5 /* _FSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _FSTextField.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
647F0AA425358718009ED3F5 /* Products */ = {
isa = PBXGroup;
children = (
647F0AA325358718009ED3F5 /* ComposingSwiftUIViews.app */,
);
name = Products;
sourceTree = "<group>";
};
647F0AA525358718009ED3F5 /* ComposingSwiftUIViews */ = {
isa = PBXGroup;
children = (
647F0AA625358718009ED3F5 /* ComposingSwiftUIViewsApp.swift */,
647F0ABA2535874F009ED3F5 /* ContentView.swift */,
647F0AB92535874F009ED3F5 /* FSTextField.swift */,
647F0ABB2535874F009ED3F5 /* _FSTextField.swift */,
647F0AAF2535871A009ED3F5 /* Info.plist */,
);
path = ComposingSwiftUIViews;
sourceTree = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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<Data: RandomAccessCollection, Content: View>: 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<Data: RandomAccessCollection, Content: View>: 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 = "<group>"; };
64DF03E624DFBD0000909495 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
64DF03E824DFBD0200909495 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
64DF03ED24DFBD0200909495 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
64DF03F724DFBD7F00909495 /* _ReadjustingStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _ReadjustingStackView.swift; sourceTree = "<group>"; };
64DF03F824DFBD7F00909495 /* ReadjustingStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadjustingStackView.swift; sourceTree = "<group>"; };
64DF03FB24E03FDE00909495 /* SizeReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeReader.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
64DF03E224DFBD0000909495 /* Products */ = {
isa = PBXGroup;
children = (
64DF03E124DFBD0000909495 /* Flexible.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Content-Friendly-Layouts/Flexible.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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<Content: View>: 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: View>(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<S: CardStyle>(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<S: CardStyle>(_ style: S) -> some View {
environment(\.cardStyle, AnyCardStyle(style: style))
}
}
// MARK: 8. Update view to take advantage of environment style
struct Card<Content: View>: 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<S: StringProtocol>(_ 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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 = "<group>"; };
64DF69F0254D7EF300099987 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
64DF69F2254D7EF400099987 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
64DF69F7254D7EF400099987 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
64DF69FF254D7F1F00099987 /* FSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSButton.swift; sourceTree = "<group>"; };
64DF6A07254D7F9300099987 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
64DF6A0A254D7F9400099987 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
/* 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 = "<group>";
};
64DF69EC254D7EF300099987 /* Products */ = {
isa = PBXGroup;
children = (
64DF69EB254D7EF300099987 /* DisplayingText.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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 = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Displaying-Text-SwiftUI/DisplayingText/DisplayingText.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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..<model.alignments.count) {
Text(alignmentName[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
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: Flexible-SwiftUI/Flexible/FlexibleApp.swift
================================================
//
import SwiftUI
@main
struct FlexibleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
================================================
FILE: Flexible-SwiftUI/Flexible/FlexibleView.swift
================================================
//
import SwiftUI
/// Facade of our view, its main responsibility is to get the available width
/// and pass it down to the real implementation, `_FlexibleView`.
struct FlexibleView<Data: Collection, Content: View>: 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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<Data: Collection, Content: View>: 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 = "<group>"; };
64DF03E624DFBD0000909495 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
64DF03E824DFBD0200909495 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
64DF03ED24DFBD0200909495 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
64DF03F724DFBD7F00909495 /* _FlexibleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _FlexibleView.swift; sourceTree = "<group>"; };
64DF03F824DFBD7F00909495 /* FlexibleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlexibleView.swift; sourceTree = "<group>"; };
64DF03FB24E03FDE00909495 /* SizeReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeReader.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
64DF03E224DFBD0000909495 /* Products */ = {
isa = PBXGroup;
children = (
64DF03E124DFBD0000909495 /* Flexible.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Flexible-SwiftUI/Flexible.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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<V: Hashable, S: StringProtocol>(
_ label: S,
tag: V,
selection: Binding<V?>,
content: @escaping () -> Content) {
let boolBinding: Binding<Bool> = 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<Data, RowContent>: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View {
private let recursiveView: RecursiveView<Data, RowContent>
public init(data: Data, children: KeyPath<Data.Element, Data?>, rowContent: @escaping (Data.Element) -> RowContent) {
self.recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent)
}
public var body: some View {
List {
recursiveView
}
}
}
private struct RecursiveView<Data, RowContent>: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View {
let data: Data
let children: KeyPath<Data.Element, Data?>
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<Data, RowContent>: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View {
private let recursiveView: RecursiveView<Data, RowContent>
public init(data: Data, children: KeyPath<Data.Element, Data?>, rowContent: @escaping (Data.Element) -> RowContent) {
self.recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent)
}
public var body: some View {
List {
recursiveView
}
}
}
private struct RecursiveView<Data, RowContent>: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View {
let data: Data
let children: KeyPath<Data.Element, Data?>
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<Label, Content>: 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<Data, RowContent>: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View {
private let recursiveView: RecursiveView<Data, RowContent>
public init(data: Data, children: KeyPath<Data.Element, Data?>, rowContent: @escaping (Data.Element) -> RowContent) {
self.recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent)
}
public var body: some View {
List {
recursiveView
}
}
}
private struct RecursiveView<Data, RowContent>: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View {
let data: Data
let children: KeyPath<Data.Element, Data?>
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<Label, Content>: 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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<V: Identifiable, Destination2: View>(
item: Binding<V?>,
destination: @escaping (V) -> Destination2
) {
let value: V? = item.wrappedValue
let isActive: Binding<Bool> = 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<V: Identifiable, Destination: View>(
item: Binding<V?>,
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 = "<group>"; };
64B446EB25727738003B2E60 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
64B446F22572773A003B2E60 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
64B446FC2572778F003B2E60 /* NavigationLink+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Identifiable.swift"; sourceTree = "<group>"; };
64B446FF257277C3003B2E60 /* View+Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Navigation.swift"; sourceTree = "<group>"; };
/* 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 = "<group>";
};
64B446E725727737003B2E60 /* Products */ = {
isa = PBXGroup;
children = (
64B446E625727737003B2E60 /* IdentifiableNavigation.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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: View>(_ 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<OverlayContent: View>: 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<Content: View>: 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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<Content: View>: 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 = "<group>"; };
64998CFA2522334B00DA960A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
64998D012522334C00DA960A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
64998D0B2522338000DA960A /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = "<group>"; };
64998D0E252233A600DA960A /* LazyVStackMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyVStackMock.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
64998CF62522334B00DA960A /* Products */ = {
isa = PBXGroup;
children = (
64998CF52522334B00DA960A /* LazyStacks.app */,
);
name = Products;
sourceTree = "<group>";
};
64998CF72522334B00DA960A /* LazyStacks */ = {
isa = PBXGroup;
children = (
64998CFA2522334B00DA960A /* ContentView.swift */,
64998D0B2522338000DA960A /* Label.swift */,
64998CF82522334B00DA960A /* LazyStacksApp.swift */,
64998D0E252233A600DA960A /* LazyVStackMock.swift */,
64998D012522334C00DA960A /* Info.plist */,
);
path = LazyStacks;
sourceTree = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Stack-vs-Grid/LazyStacks/LazyStacks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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..<ellipsesNumber {
let ellipseOrigin = CGPoint(x: (rect.width - rectWidth) * Double(index) / Double(ellipsesNumber - 1), y: rect.origin.y)
path.addEllipse(in: CGRect(origin: ellipseOrigin, size: rectSize))
}
return path
}
}
struct OverlappingRectangles: Shape {
@Clamping(1...Int.max) var rectanglesNumber: 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(rectanglesNumber)) * (1 + Double(rectanglesNumber - 1) * overlapping)
let rectSize = CGSize(width: rectWidth, height: rect.height)
var path = Path()
for index in 0..<rectanglesNumber {
let ellipseOrigin = CGPoint(x: (rect.width - rectWidth) * Double(index) / Double(rectanglesNumber - 1), y: rect.origin.y)
path.addRect(CGRect(origin: ellipseOrigin, size: rectSize))
}
return path
}
}
================================================
FILE: SwiftUI-Clipping/Clipping/Clipping/propertyWrapper+Clamping.swift
================================================
@propertyWrapper
public struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
public init(wrap
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
Condensed preview — 161 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (319K chars).
[
{
"path": ".gitignore",
"chars": 2899,
"preview": "\n# Created by https://www.toptal.com/developers/gitignore/api/xcode,swift,swiftpackagemanager,swiftpm\n# Edit at https://"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/AdaptiveExampleView.swift",
"chars": 1358,
"preview": "//\n\nimport SwiftUI\n\nstruct AdaptiveExampleView: View {\n var body: some View {\n AdaptiveView {\n RoundedRectangle"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/AdaptiveView.swift",
"chars": 1719,
"preview": "//\n\nimport SwiftUI\n\n// MARK: Size class\n\n// Run this on a plus size device in landscape or on an iPad to see the regular"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/AdaptiveViewsApp.swift",
"chars": 150,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct AdaptiveViewsApp: App {\n var body: some Scene {\n WindowGroup {\n Co"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/AccentColor.colorset/Contents.json",
"chars": 123,
"preview": "{\n \"colors\" : [\n {\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1591,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"scale\" : \"2x\",\n \"size\" : \"20x20\"\n },\n {\n \"idiom\""
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/apple.imageset/Contents.json",
"chars": 224,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"Apple.pdf\",\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author\" "
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/google.imageset/Contents.json",
"chars": 225,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"Google.pdf\",\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author\""
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/Assets.xcassets/twitter.imageset/Contents.json",
"chars": 226,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"Twitter.pdf\",\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/ContentView.swift",
"chars": 416,
"preview": "//\n\nimport SwiftUI\n\nstruct ContentView: View {\n var body: some View {\n NavigationView {\n List {\n Navigat"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/ExperimentalView.swift",
"chars": 1090,
"preview": "//\n\nimport SwiftUI\n\nstruct ExperimentalView: View {\n @State var currentWidth: CGFloat = 0\n @State var padding: CGFloat"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/Info.plist",
"chars": 1580,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/SignInButton.swift",
"chars": 1590,
"preview": "//\n\nimport SwiftUI\n\nextension Color {\n static let appleTint = Color.black\n static let googleTint = Color(red: 222 / 25"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/SocialSignInView.swift",
"chars": 955,
"preview": "//\n\nimport SwiftUI\n\nstruct SocialSignInView: View {\n @State private var availableWidth: CGFloat = 0\n\n private var butt"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews/View+ReadSize.swift",
"chars": 512,
"preview": "//\n\nimport SwiftUI\n\nextension View {\n func readSize(onChange: @escaping (CGSize) -> Void) -> some View {\n background"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews.xcodeproj/project.pbxproj",
"chars": 14185,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Adaptive-Views/AdaptiveViews/AdaptiveViews.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Adaptive-Views/README.md",
"chars": 145,
"preview": "Final project from [Adaptive SwiftUI views][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/swiftui/adaptive-swiftui-views."
},
{
"path": "App-State-In-SwiftUI/AppState.swift",
"chars": 1293,
"preview": "// Gist from https://fivestars.blog/swiftui/app-state.html\n\nimport SwiftUI\n\n@main\nstruct FiveStarsApp: App {\n @StateObj"
},
{
"path": "App-State-In-SwiftUI/README.md",
"chars": 104,
"preview": "Code snippet from [App-wide state in SwiftUI][fs].\n\n[fs]: https://fivestars.blog/swiftui/app-state.html\n"
},
{
"path": "Blending/README.md",
"chars": 106,
"preview": "Code snippet from [SwiftUI blending][fs].\n\n[fs]: https://www.fivestars.blog/articles/swiftui-blend-modes/\n"
},
{
"path": "Blending/content.swift",
"chars": 3277,
"preview": "import SwiftUI\n\nstruct ContentView: View {\n let edge: Double = 600\n let blendModes: [BlendMode] = [\n .colorDodge, ."
},
{
"path": "Button-Styles/README.md",
"chars": 139,
"preview": "Code snippet from [Meet the new Button styling][fs].\n\n![][gif]\n\n[fs]: https://www.fivestars.blog/articles/button-styles-"
},
{
"path": "Button-Styles/content.swift",
"chars": 5229,
"preview": "import SwiftUI\n\n/// Usage:\n/// Button(...) { \n/// ...\n/// }\n/// .buttonStyle(.fiveStars)\n\nextension ButtonStyle where "
},
{
"path": "Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/ComposingSwiftUIViewsApp.swift",
"chars": 140,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct ComposingSwiftUIViewsApp: App {\n var body: some Scene {\n WindowGroup {\n Conten"
},
{
"path": "Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/FSTextField.swift",
"chars": 2746,
"preview": "//\n\nimport SwiftUI\n\nstruct FSTextField<TopContent: View>: View {\n var placeholder: LocalizedStringKey = \"Placeholder\"\n "
},
{
"path": "Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/Info.plist",
"chars": 1580,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews/_FSTextField.swift",
"chars": 740,
"preview": "//\n\nimport SwiftUI\n\nstruct _FSTextField: View {\n var placeholder: LocalizedStringKey = \"\"\n @Binding var text: String\n "
},
{
"path": "Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews.xcodeproj/project.pbxproj",
"chars": 12110,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Composing-SwiftUI-Views/ComposingSwiftUIViews/ComposingSwiftUIViews.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Composing-SwiftUI-Views/README.md",
"chars": 156,
"preview": "Final project from [Composing SwiftUI views][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/swiftui/design-system-composin"
},
{
"path": "Content-Friendly-Layouts/Flexible/Assets.xcassets/AccentColor.colorset/Contents.json",
"chars": 123,
"preview": "{\n \"colors\" : [\n {\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }"
},
{
"path": "Content-Friendly-Layouts/Flexible/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1591,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"scale\" : \"2x\",\n \"size\" : \"20x20\"\n },\n {\n \"idiom\""
},
{
"path": "Content-Friendly-Layouts/Flexible/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Content-Friendly-Layouts/Flexible/ContentView.swift",
"chars": 2372,
"preview": "//\n\nimport SwiftUI\n\nclass ContentViewModel: ObservableObject {\n\n @Published var originalItems = [\n \"Here’s\", \"to\", \""
},
{
"path": "Content-Friendly-Layouts/Flexible/FlexibleApp.swift",
"chars": 145,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct FlexibleApp: App {\n var body: some Scene {\n WindowGroup {\n Content"
},
{
"path": "Content-Friendly-Layouts/Flexible/Info.plist",
"chars": 1469,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Content-Friendly-Layouts/Flexible/ReadjustingStackView.swift",
"chars": 731,
"preview": "//\n\nimport SwiftUI\n\n/// Facade of our view, its main responsibility is to get the available width\n/// and pass it down t"
},
{
"path": "Content-Friendly-Layouts/Flexible/SizeReader.swift",
"chars": 512,
"preview": "//\n\nimport SwiftUI\n\nextension View {\n func readSize(onChange: @escaping (CGSize) -> Void) -> some View {\n background"
},
{
"path": "Content-Friendly-Layouts/Flexible/_ReadjustingStackView.swift",
"chars": 1122,
"preview": "//\n\nimport SwiftUI\n\n/// This view is responsible to lay the given elements in either axis.\nstruct _ReadjustingStackView<"
},
{
"path": "Content-Friendly-Layouts/Flexible.xcodeproj/project.pbxproj",
"chars": 12706,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Content-Friendly-Layouts/Flexible.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Content-Friendly-Layouts/Flexible.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Content-Friendly-Layouts/README.md",
"chars": 161,
"preview": "Proof of concept from [Build content-friendly layouts][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/ios/content-friendly"
},
{
"path": "Custom-SwiftUI-Styles/ContentView.swift",
"chars": 5869,
"preview": "// Gist from http://fivestars.blog/swiftui/custom-view-styles.html\n\nimport SwiftUI\n\n// This gist shows you how to build "
},
{
"path": "Custom-SwiftUI-Styles/README.md",
"chars": 136,
"preview": "Code snippet from [Custom view styles][fs].\n\n![][image]\n\n[fs]: http://fivestars.blog/swiftui/custom-view-styles.html\n[im"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Assets.xcassets/AccentColor.colorset/Contents.json",
"chars": 123,
"preview": "{\n \"colors\" : [\n {\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1591,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"scale\" : \"2x\",\n \"size\" : \"20x20\"\n },\n {\n \"idiom\""
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/ContentView.swift",
"chars": 871,
"preview": "//\n\nimport SwiftUI\n\nstruct ContentView: View {\n var backendString: String = \"some backend string\"\n\n var body: some Vie"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/DisplayingTextApp.swift",
"chars": 133,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct DisplayingTextApp: App {\n var body: some Scene {\n WindowGroup {\n ContentView()"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/FSButton.swift",
"chars": 1096,
"preview": "//\n\nimport SwiftUI\n\nstruct FSButton: View {\n let title: Text\n let action: () -> Void\n\n init(_ title: Text, action: @e"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/Info.plist",
"chars": 1580,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/en.lproj/Localizable.strings",
"chars": 39,
"preview": "\"my_localized_title\" = \"Button title\";\n"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText/th.lproj/Localizable.strings",
"chars": 35,
"preview": "\"my_localized_title\" = \"ชื่อปุ่ม\";\n"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText.xcodeproj/project.pbxproj",
"chars": 12728,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Displaying-Text-SwiftUI/DisplayingText/DisplayingText.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Displaying-Text-SwiftUI/README.md",
"chars": 157,
"preview": "Final project from [Displaying text in SwiftUI][fs].\n\n![][buttons]\n\n[fs]: https://fivestars.blog/swiftui/displaying-text"
},
{
"path": "Flexible-SwiftUI/Flexible/Assets.xcassets/AccentColor.colorset/Contents.json",
"chars": 123,
"preview": "{\n \"colors\" : [\n {\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }"
},
{
"path": "Flexible-SwiftUI/Flexible/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1591,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"scale\" : \"2x\",\n \"size\" : \"20x20\"\n },\n {\n \"idiom\""
},
{
"path": "Flexible-SwiftUI/Flexible/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Flexible-SwiftUI/Flexible/ContentView.swift",
"chars": 3054,
"preview": "//\n\nimport SwiftUI\n\nclass ContentViewModel: ObservableObject {\n\n @Published var originalItems = [\n \"Here’s\", \"to\", \""
},
{
"path": "Flexible-SwiftUI/Flexible/FlexibleApp.swift",
"chars": 145,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct FlexibleApp: App {\n var body: some Scene {\n WindowGroup {\n Content"
},
{
"path": "Flexible-SwiftUI/Flexible/FlexibleView.swift",
"chars": 984,
"preview": "//\n\nimport SwiftUI\n\n/// Facade of our view, its main responsibility is to get the available width\n/// and pass it down t"
},
{
"path": "Flexible-SwiftUI/Flexible/Info.plist",
"chars": 1469,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Flexible-SwiftUI/Flexible/SizeReader.swift",
"chars": 512,
"preview": "//\n\nimport SwiftUI\n\nextension View {\n func readSize(onChange: @escaping (CGSize) -> Void) -> some View {\n background"
},
{
"path": "Flexible-SwiftUI/Flexible/_FlexibleView.swift",
"chars": 1446,
"preview": "//\n\nimport SwiftUI\n\n/// This view is responsible to lay down the given elements and wrap them into\n/// multiple rows if "
},
{
"path": "Flexible-SwiftUI/Flexible.xcodeproj/project.pbxproj",
"chars": 12610,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Flexible-SwiftUI/Flexible.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Flexible-SwiftUI/Flexible.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Flexible-SwiftUI/README.md",
"chars": 320,
"preview": "Proof of concept from [Flexible SwiftUI][fs].\n\n![][gif]\n\n## Credits\n\nThis article and code is inspired by [Mauricio Meir"
},
{
"path": "Hashable-Bindings/ContentView.swift",
"chars": 1184,
"preview": "import SwiftUI\n\nenum ContentViewGroup: Hashable {\n case a\n case b\n case c\n}\n\nstruct ContentView: View {\n @State var "
},
{
"path": "Hashable-Bindings/README.md",
"chars": 142,
"preview": "Code snippet from [Hashable SwiftUI bindings][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/swiftui/hashable-bindings.htm"
},
{
"path": "Hierarchy-List/ContentView-1.swift",
"chars": 2078,
"preview": "// Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html\n\nimport SwiftUI\n\nstruct FileItem: "
},
{
"path": "Hierarchy-List/ContentView-2.swift",
"chars": 2377,
"preview": "// Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html\n\nimport SwiftUI\n\nstruct FileItem: "
},
{
"path": "Hierarchy-List/ContentView-xcode-11.swift",
"chars": 2591,
"preview": "// Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html\n\nimport SwiftUI\n\nstruct FileItem: "
},
{
"path": "Hierarchy-List/README.md",
"chars": 137,
"preview": "Code snippet from [SwiftUI Hierarchy Lists][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/articles/swiftui-hierarchy-list"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/ContentView.swift",
"chars": 1259,
"preview": "//\n\nimport SwiftUI\n\nenum ContentViewNavigation: Identifiable {\n case one\n case two(number: Int)\n case three(text: Str"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/IdentifiableNavigationApp.swift",
"chars": 141,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct IdentifiableNavigationApp: App {\n var body: some Scene {\n WindowGroup {\n Conte"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/Info.plist",
"chars": 1580,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/NavigationLink+Identifiable.swift",
"chars": 854,
"preview": "//\n\nimport SwiftUI\n\n/// The extension from the article broke in Xcode 12.5 and no longer work\n/// properly.\n///\n/// This"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation/View+Navigation.swift",
"chars": 255,
"preview": "//\n\nimport SwiftUI\n\nextension View {\n func navigation<V: Identifiable, Destination: View>(\n item: Binding<V?>,\n d"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation.xcodeproj/project.pbxproj",
"chars": 12115,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Identifiable-Navigation/IdentifiableNavigation/IdentifiableNavigation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Identifiable-Navigation/README.md",
"chars": 235,
"preview": "Working proof of concept of an improved SwiftUI navigation API, refer to [The future of SwiftUI navigation (?)][fs] for "
},
{
"path": "LICENSE",
"chars": 1489,
"preview": "Copyright (c) 2020, Federico Zanetello\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or "
},
{
"path": "README.md",
"chars": 69,
"preview": "Code Samples from [fivestars.blog][fs].\n\n[fs]: http://fivestars.blog/"
},
{
"path": "SafeAreaInset/README.md",
"chars": 166,
"preview": "Code snippet from [Backport SwiftUI safe area insets to iOS 13 and 14][fs].\n\n![][gif]\n\n[fs]: https://www.fivestars.blog/"
},
{
"path": "SafeAreaInset/content.swift",
"chars": 2914,
"preview": "// Gist from https://www.fivestars.blog/articles/safe-area-insets-2/\n\nimport SwiftUI\n\nstruct ContentView: View {\n var b"
},
{
"path": "ScrollView-Offset/README.md",
"chars": 148,
"preview": "Final code snippet from [SwiftUI ScrollView offset][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/swiftui/scrollview-offs"
},
{
"path": "ScrollView-Offset/ScrollViewOffset.swift",
"chars": 969,
"preview": "//\n\nimport SwiftUI\n\nstruct ScrollViewOffset<Content: View>: View {\n let onOffsetChange: (CGFloat) -> Void\n let content"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks/ContentView.swift",
"chars": 969,
"preview": "//\n\nimport SwiftUI\n\nstruct ContentView: View {\n var body: some View {\n HStack {\n ScrollView {\n LazyVStac"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks/Info.plist",
"chars": 1580,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks/Label.swift",
"chars": 283,
"preview": "//\n\nimport SwiftUI\n\nextension Label where Title == Text, Icon == Image {\n init(_ title: LocalizedStringKey, colorfulSys"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks/LazyStacksApp.swift",
"chars": 147,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct LazyStacksApp: App {\n var body: some Scene {\n WindowGroup {\n Conte"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks/LazyVStackMock.swift",
"chars": 720,
"preview": "//\n\nimport SwiftUI\n\npublic struct LazyVStackMock<Content: View>: View {\n let alignment: HorizontalAlignment\n let spaci"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks.xcodeproj/project.pbxproj",
"chars": 11675,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Stack-vs-Grid/LazyStacks/LazyStacks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Stack-vs-Grid/README.md",
"chars": 138,
"preview": "Final code snippet from [Lazy stacks secrets][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/swiftui/lazy-stack-grid.html\n"
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping/ClippingApp.swift",
"chars": 123,
"preview": "import SwiftUI\n\n@main\nstruct ClippingApp: App {\n var body: some Scene {\n WindowGroup {\n ContentView()\n }\n }"
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping/ContentView.swift",
"chars": 913,
"preview": "import SwiftUI\n\nstruct ContentView: View {\n var body: some View {\n NavigationView {\n List {\n Section(\"In"
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping/FSViews.swift",
"chars": 3919,
"preview": "import SwiftUI\n\nstruct FSView0: View {\n var body: some View {\n Text(\"Five stars\")\n .background(Color.yellow)\n "
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping/Shapes.swift",
"chars": 2985,
"preview": "import SwiftUI\n\nstruct Star: Shape {\n @Clamping(0...Int.max) var points: Int = 5\n var innerRatio = 0.4\n\n func path(in"
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping/propertyWrapper+Clamping.swift",
"chars": 418,
"preview": "@propertyWrapper\npublic struct Clamping<Value: Comparable> {\n var value: Value\n let range: ClosedRange<Value>\n\n publi"
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping.xcodeproj/project.pbxproj",
"chars": 13231,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "SwiftUI-Clipping/Clipping/Clipping.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "SwiftUI-Clipping/README.md",
"chars": 137,
"preview": "Companion app of [View clipping in SwiftUI][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/articles/swiftui-clipping/\n[gif"
},
{
"path": "SwiftUI-HUD/README.md",
"chars": 128,
"preview": "Code snippets from [Custom HUD in SwiftUI][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/swiftui/swiftui-hud.html\n[gif]: "
},
{
"path": "SwiftUI-HUD/global.swift",
"chars": 1805,
"preview": "//\n\nimport SwiftUI\n\n@main\nstruct FiveStarsApp: App {\n @StateObject var hudState = HUDState()\n\n var body: some Scene {\n"
},
{
"path": "SwiftUI-HUD/local.swift",
"chars": 1316,
"preview": "//\n\nimport SwiftUI\n\nstruct ContentView: View {\n @State private var showingHUD = false\n\n var body: some View {\n Navi"
},
{
"path": "SwiftUI-Masking/Masking/Masking/ContentView.swift",
"chars": 661,
"preview": "import SwiftUI\n\nstruct ContentView: View {\n var body: some View {\n NavigationView {\n List {\n Section(\"Ma"
},
{
"path": "SwiftUI-Masking/Masking/Masking/FSViews.swift",
"chars": 3989,
"preview": "import SwiftUI\n\nstruct FSView1: View {\n private let alignments: [Alignment] = [\n .center, .leading, .trailing, .top,"
},
{
"path": "SwiftUI-Masking/Masking/Masking/MaskingApp.swift",
"chars": 122,
"preview": "import SwiftUI\n\n@main\nstruct MaskingApp: App {\n var body: some Scene {\n WindowGroup {\n ContentView()\n }\n }\n"
},
{
"path": "SwiftUI-Masking/Masking/Masking.xcodeproj/project.pbxproj",
"chars": 12286,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "SwiftUI-Masking/Masking/Masking.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "SwiftUI-Masking/Masking/Masking.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "SwiftUI-Masking/README.md",
"chars": 104,
"preview": "Companion app of [View masking in SwiftUI][fs].\n\n[fs]: https://fivestars.blog/articles/swiftui-masking/\n"
},
{
"path": "SwiftUI-Reverse-Mask/README.md",
"chars": 123,
"preview": "Companion app of [How to apply a reverse mask in SwiftUI][fs].\n\n[fs]: https://fivestars.blog/articles/reverse-masks-how-"
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/ContentView.swift",
"chars": 583,
"preview": "import SwiftUI\n\nstruct ContentView: View {\n var body: some View {\n NavigationView {\n List {\n Section(\"Re"
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/FSViews.swift",
"chars": 2635,
"preview": "import SwiftUI\n\nstruct FSView1: View {\n var body: some View {\n HStack {\n Color.yellow\n .frame(width: 200"
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/ReverseMaskingApp.swift",
"chars": 129,
"preview": "import SwiftUI\n\n@main\nstruct ReverseMaskingApp: App {\n var body: some Scene {\n WindowGroup {\n ContentView()\n "
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/Star.swift",
"chars": 1382,
"preview": "//\n\nimport SwiftUI\n\nstruct Star: Shape {\n @Clamping(0...Int.max) var points: Int = 5\n var innerRatio = 0.4\n\n func pat"
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking/View+reverseMask.swift",
"chars": 334,
"preview": "//\n\nimport SwiftUI\n\nextension View {\n @inlinable\n public func reverseMask<Mask: View>(\n alignment: Alignment = .cen"
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking.xcodeproj/project.pbxproj",
"chars": 13330,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 232,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:/Users/zntfdr/W"
},
{
"path": "SwiftUI-Reverse-Mask/Reverse-Masking/Reverse-Masking.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "SwiftUI-read-a-view-size/README.md",
"chars": 132,
"preview": "Code snippet from [How to read a view size in SwiftUI][fs].\n\n[fs]: https://fivestars.blog/articles/swiftui-share-layout-"
},
{
"path": "SwiftUI-read-a-view-size/View+readSize.swift",
"chars": 737,
"preview": "// Original article here: https://fivestars.blog/articles/swiftui-share-layout-information/\n\nimport SwiftUI\n\n/*\n\nExample"
},
{
"path": "Truncable-Text/ContentView.swift",
"chars": 2116,
"preview": "//\n\nimport SwiftUI\n\nstruct ContentView: View {\n @State var isTruncated: Bool = false\n @State var forceFullText: Bool ="
},
{
"path": "Truncable-Text/README.md",
"chars": 148,
"preview": "Code snippet from [How to check if Text is truncated?][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/swiftui/trucated-tex"
},
{
"path": "Windows/README.md",
"chars": 139,
"preview": "Code snippet from [How to manage windows in SwiftUI][fs].\n\n![][gif]\n\n[fs]: https://fivestars.blog/articles/swiftui-windo"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/FSAppDelegate.swift",
"chars": 455,
"preview": "import SwiftUI\n\nfinal class FSAppDelegate: NSObject, UIApplicationDelegate {\n func application(\n _ application: UIAp"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/FSSceneDelegate.swift",
"chars": 910,
"preview": "import SwiftUI\n\nfinal class FSSceneDelegate: UIResponder, UIWindowSceneDelegate, ObservableObject {\n var hudState: HudS"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/FSSwiftUILifecycleApp.swift",
"chars": 276,
"preview": "import SwiftUI\n\n@main\nstruct FSSwiftUILifecycleApp: App {\n @StateObject var hudState = HudState()\n @UIApplicationDeleg"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/HudSceneView.swift",
"chars": 287,
"preview": "import SwiftUI\n\nstruct HudSceneView: View {\n @EnvironmentObject var hudState: HudState\n\n var body: some View {\n Col"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/HudState.swift",
"chars": 320,
"preview": "import Combine\n\nfinal class HudState: ObservableObject {\n @Published var isPresented: Bool = false\n private(set) var t"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/MainSceneView.swift",
"chars": 626,
"preview": "import SwiftUI\n\nstruct MainSceneView: View {\n @EnvironmentObject var sceneDelegate: FSSceneDelegate\n @EnvironmentObjec"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/PassThroughWindow.swift",
"chars": 274,
"preview": "import UIKit\n\nclass PassThroughWindow: UIWindow {\n override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIV"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp/View+hud.swift",
"chars": 765,
"preview": "import SwiftUI\n\nextension View {\n func hud<Content: View>(\n isPresented: Binding<Bool>,\n @ViewBuilder content: ()"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp.xcodeproj/project.pbxproj",
"chars": 15132,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Windows/SwiftUI-life-cycle/FSSwiftUILifecycleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/Base.lproj/LaunchScreen.storyboard",
"chars": 1665,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/FSAppDelegate.swift",
"chars": 85,
"preview": "//\n\nimport UIKit\n\n@main\nclass FSAppDelegate: UIResponder, UIApplicationDelegate {\n}\n\n"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/FSSceneDelegate.swift",
"chars": 1129,
"preview": "import SwiftUI\n\nfinal class FSSceneDelegate: UIResponder, UIWindowSceneDelegate, ObservableObject {\n lazy var hudState "
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/HudSceneView.swift",
"chars": 287,
"preview": "import SwiftUI\n\nstruct HudSceneView: View {\n @EnvironmentObject var hudState: HudState\n\n var body: some View {\n Col"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/HudState.swift",
"chars": 320,
"preview": "import Combine\n\nfinal class HudState: ObservableObject {\n @Published var isPresented: Bool = false\n private(set) var t"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/Info.plist",
"chars": 641,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/MainSceneView.swift",
"chars": 508,
"preview": "import SwiftUI\n\nstruct MainSceneView: View {\n @EnvironmentObject var hudState: HudState\n @State var showingSheet = fal"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/PassThroughWindow.swift",
"chars": 274,
"preview": "import UIKit\n\nclass PassThroughWindow: UIWindow {\n override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIV"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp/View+hud.swift",
"chars": 765,
"preview": "import SwiftUI\n\nextension View {\n func hud<Content: View>(\n isPresented: Binding<Bool>,\n @ViewBuilder content: ()"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp.xcodeproj/project.pbxproj",
"chars": 15576,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Windows/UIKit-life-cycle/FSUIKitLifecycleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
}
]
About this extraction
This page contains the full source code of the FiveStarsBlog/CodeSamples GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 161 files (272.9 KB), approximately 88.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.