Slide 1

Slide 1 text

Peter Friese, Developer Relations Engineer, Google Why every SwiftUI developer should care about the Environment @peterfriese.dev SwiftHeroes, 2025

Slide 2

Slide 2 text

Peter Friese, Developer Relations Engineer, Google Why every SwiftUI developer should care about the Environment @peterfriese.dev SwiftHeroes, 2025 Environment

Slide 3

Slide 3 text

Hey, I’m Peter

Slide 4

Slide 4 text

Hey, I’m Peter

Slide 5

Slide 5 text

Environment

Slide 6

Slide 6 text

/ɪnˈvaɪ.r ə .m ə nt/

Slide 7

Slide 7 text

/ɪnˈvaɪ.r ə .m ə nt/ The air, water, and land in or on which people, animals, and plants live.

Slide 8

Slide 8 text

/ɪnˈvaɪ.r ə .m ə nt/ The conditions that you live or work in and the way that they influence how you feel or how effectively you can work. The conditions that you live or work in and the way that they influence how you feel or how effectively you can work.

Slide 9

Slide 9 text

Why?

Slide 10

Slide 10 text

Why? SwiftUI Environment

Slide 11

Slide 11 text

SwiftUI Environment 1/ React to external conditions (e.g. color scheme) 2/ Define our own custom conditions 3/ State management 4/ Building better views

Slide 12

Slide 12 text

1/ React to external conditions (e.g. color scheme)

Slide 13

Slide 13 text

Environment values

Slide 14

Slide 14 text

Environment values

Slide 15

Slide 15 text

Environment values 101 - reading struct SomeView: View { @Environment(\.colorScheme) var colorScheme } Read value using key path

Slide 16

Slide 16 text

Environment values 101 - reading struct SomeView: View { @Environment(\.colorScheme) var colorScheme } Use wrapped value in your view body This works because @Environment conforms to DynamicProperty var body: some View { VStack { } } Text("Hello World - dark mode treatment") .padding() .background(colorScheme == .dark ? Color.white : Color.red) .foregroundColor(colorScheme == .dark ? .red : .white) .cornerRadius(10)

Slide 17

Slide 17 text

Environment values 101 - reading struct SomeView: View { @Environment(\.colorScheme) var colorScheme } var body: some View { VStack { } } Text("Hello World - dark mode treatment") .padding() .background(colorScheme == .dark ? Color.white : Color.red) .foregroundColor(colorScheme == .dark ? .red : .white) .cornerRadius(10) Text("Hello World - no dark mode treatment") .padding() .background(Color(.systemRed)) .cornerRadius(10)

Slide 18

Slide 18 text

Environment values 101 - writing struct SomeView: View { @Environment(\.colorScheme) var colorScheme } var body: some View { VStack { } } Button("Toggle case") { isUppercase.toggle() } .environment(\.textCase, isUppercase ? .uppercase : nil) @State private var isUppercase: Bool = false Update environment values

Slide 19

Slide 19 text

Environment values 101 - writing struct SomeView: View { @Environment(\.colorScheme) var colorScheme } var body: some View { VStack { } } Button("Toggle lights") { isDarkModeActive.toggle() } .environment(\.colorScheme, isDarkModeActive ? .light : .dark) @State private var isDarkModeActive: Bool = false This doesn’t work as expected for colorScheme…

Slide 20

Slide 20 text

Environment values 101 - writing struct SomeView: View { @Environment(\.colorScheme) var colorScheme } var body: some View { VStack { } } Button("Toggle lights") { isDarkModeActive.toggle() } .preferredColorScheme(isDarkModeActive ? .light : .dark) @State private var isDarkModeActive: Bool = false Use this instead

Slide 21

Slide 21 text

Environment values layoutDirection / isEnabled / isLuminanceReduced / redactionReasons / controlSize / backgroundMaterial / materialActiveAppearance / symbolRenderingMode / truncationMode / multilineTextAlignment / lineSpacing / allowsTightening / minimumScaleFactor / textCase / lineLimit / backgroundProminence / backgroundStyle / appearsActive / dynamicTypeSize / sizeCategory / headerProminence / allowedDynamicRange / symbolVariants / displayScale / contentTransitionAddsDrawingGroup / backgroundProminence / font / legibilityWeight / imageScale / pixelLength / locale / calendar / timeZone / horizontalSizeClass / verticalSizeClass / accessibilityEnabled / accessibilityDifferentiateWithoutColor / accessibilityReduceTransparency / accessibilityReduceMotion / accessibilityInvertColors / accessibilityShowButtonShapes / accessibilityDimFlashingLights / accessibilityPlayAnimatedImages / colorScheme / colorSchemeContrast

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Environment values

Slide 24

Slide 24 text

Environment values 101 - custom keys extension EnvironmentValues { @Entry var isHighlighted: Bool = false } Use the @Entry macro to define the environment key

Slide 25

Slide 25 text

Environment values 101 - custom keys extension EnvironmentValues { @Entry var isHighlighted: Bool = false } Set the default value

Slide 26

Slide 26 text

Environment values 101 - custom keys extension EnvironmentValues { @Entry var isHighlighted: Bool = false } Access the value in the environment struct HighlightedText: View { let text: String @Environment(\.isHighlighted) private var isHighlighted var body: some View { } }

Slide 27

Slide 27 text

Environment values 101 - custom keys extension EnvironmentValues { @Entry var isHighlighted: Bool = false } struct HighlightedText: View { let text: String @Environment(\.isHighlighted) private var isHighlighted var body: some View { } } Text(text) .background(isHighlighted ? Color.yellow : Color.clear) Use the value in the body

Slide 28

Slide 28 text

Environment values 101 - custom keys struct HighlightedText: View { let text: String @Environment(\.isHighlighted) private var isHighlighted var body: some View { } } Text(text) .background(isHighlighted ? Color.yellow : Color.clear) struct ContentView: View { var body: some View { VStack(spacing: 20) { HighlightedText(text: "This text is not highlighted") HighlightedText(text: "This text is highlighted") .environment(\.isHighlighted, true) } } } Sample usage

Slide 29

Slide 29 text

Environment values 101 - custom keys extension EnvironmentValues { @Entry var isHighlighted: Bool = false } struct ContentView: View { var body: some View { VStack(spacing: 20) { HighlightedText(text: "This text is not highlighted") HighlightedText(text: "This text is highlighted") .environment(\.isHighlighted, true) } } } extension View { func highlighted(_ isOn: Bool = true) -> some View { } } Move this up here

Slide 30

Slide 30 text

Environment values 101 - custom keys extension EnvironmentValues { @Entry var isHighlighted: Bool = false } struct ContentView: View { var body: some View { VStack(spacing: 20) { HighlightedText(text: "This text is not highlighted") HighlightedText(text: "This text is highlighted") } } } extension View { func highlighted(_ isOn: Bool = true) -> some View { } } environment(\.isHighlighted, isOn) .highlighted()

Slide 31

Slide 31 text

State handling

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

KitchenStore - activeOrders: [Order] - foodItems: [String] DiningAreaStore - tables: [Table]

Slide 34

Slide 34 text

KitchenStore - activeOrders: [Order] - foodItems: [String] DiningAreaStore - tables: [Table] struct MainRestaurantView: View { var kitchenStore: KitchenStore } struct DiningAreaView: View { var diningAreaStore: DiningAreaStore var kitchenStore: KitchenStore } struct OrderView: View { var kitchenStore: KitchenStore }

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Re-implementing Text

Slide 37

Slide 37 text

Re-implementing Text 1: Using the Initialiser

Slide 38

Slide 38 text

struct MyText: View { var text: String var body: some View { } } #Preview { MyText( text: "Hello world" ) }

Slide 39

Slide 39 text

struct MyText: View { var text: String var font: Font? var body: some View { } } #Preview { MyText( text: "Hello world", font: .title ) }

Slide 40

Slide 40 text

struct MyText: View { var text: String var font: Font? var foregroundColor: Color? var body: some View { } } #Preview { MyText( text: "Hello world", font: .title, foregroundColor: .swift, ) }

Slide 41

Slide 41 text

struct MyText: View { var text: String var font: Font? var foregroundColor: Color? var fontWeight: Font.Weight? var body: some View { } } #Preview { MyText( text: "Hello world", font: .title, foregroundColor: .swift, fontWeight: .bold ) }

Slide 42

Slide 42 text

struct MyText: View { var text: String var font: Font? var foregroundColor: Color? var fontWeight: Font.Weight? var textCase: Text.Case? var body: some View { } } #Preview { MyText( text: "Hello world", font: .title, foregroundColor: .swift, fontWeight: .bold, textCase: .uppercase ) }

Slide 43

Slide 43 text

struct MyText: View { var text: String var font: Font? var foregroundColor: Color? var fontWeight: Font.Weight? var textCase: Text.Case? var italic: Bool? var body: some View { } } #Preview { MyText( text: "Hello world", font: .title, foregroundColor: .swift, fontWeight: .bold, textCase: .uppercase, italic: true ) }

Slide 44

Slide 44 text

struct MyText: View { var text: String var font: Font? var foregroundColor: Color? var fontWeight: Font.Weight? var textCase: Text.Case? var italic: Bool? var body: some View { } } #Preview { MyText( text: "Hello world", font: .title, foregroundColor: .swift, fontWeight: .bold, textCase: .uppercase, italic: true ) } Fixed order Adding new parameters: breaking change Incredibly difficult to type at call site Default values: band aid

Slide 45

Slide 45 text

Re-implementing Text 2: Decorators

Slide 46

Slide 46 text

struct MyText: View { var text: String var body: some View { } }

Slide 47

Slide 47 text

struct MyText: View { var text: String var body: some View { } } struct FontDecorator: View { var content: Content var font: Font? var body: some View { } } struct ForegroundColorDecorator: View { var content: Content var color: Color? var body: some View {

Slide 48

Slide 48 text

struct FontDecorator: View { var content: Content var font: Font? var body: some View { } } struct ForegroundColorDecorator: View { var content: Content var color: Color? var body: some View { } }

Slide 49

Slide 49 text

#Preview { ItalicDecorator( content: TextCaseDecorator( content: MultilineTextAlignmentDecorator( content: LineLimitDecorator( content: FontWeightDecorator( content: ForegroundColorDecorator( content: FontDecorator( content: MyText(text: "Hello world"), font: .title ), color: .swift ), weight: .bold ), lineLimit: 2 ), alignment: .center ), textCase: .uppercase ), italic: true ) } content: content: content: content: content: content: content: #Preview { ItalicDecorator( font: .title ), color: .swift ), weight: .bold ), lineLimit: 2 ), alignment: .center ), textCase: .uppercase ), italic: true ) } TextCaseDecorator( MultilineTextAlignmentDecorator( LineLimitDecorator( FontWeightDecorator( ForegroundColorDecorator( FontDecorator( MyText(text: "Hello world"),

Slide 50

Slide 50 text

#Preview { ItalicDecorator( font: .title ), color: .swift ), weight: .bold ), lineLimit: 2 ), alignment: .center ), textCase: .uppercase ), italic: true ) } TextCaseDecorator( MultilineTextAlignmentDecorator( LineLimitDecorator( FontWeightDecorator( ForegroundColorDecorator( FontDecorator( MyText(text: "Hello world"),

Slide 51

Slide 51 text

#Preview { ItalicDecorator( font: .title ), color: .swift ), weight: .bold ), lineLimit: 2 ), alignment: .center ), textCase: .uppercase ), italic: true ) } TextCaseDecorator( MultilineTextAlignmentDecorator( LineLimitDecorator( FontWeightDecorator( ForegroundColorDecorator( FontDecorator( MyText(text: "Hello world"), Easy to add new decorators No breaking changes Order significant Even harder to read Changes incredibly difficult

Slide 52

Slide 52 text

Re-implementing Text 3: Decorator functions

Slide 53

Slide 53 text

Attempt 1: Using @State for View configuration

Slide 54

Slide 54 text

struct TextStyle { var font: Font? = nil var foregroundColor: Color? = nil } struct MyText: View { var text: String @State private var style = TextStyle() var body: some View { } func myFont(_ font: Font?) -> MyText { style.font = font return self } func myForegroundColor(_ color: Color?) -> MyText { style.foregroundColor = color return self } } Use a private state to hold the view configuration Use decorator functions to update the view configuration

Slide 55

Slide 55 text

#Preview { MyText(text: "Hello world") .myFont(.title) .myForegroundColor(.swift) .myFontWeight(.bold) .myLineLimit(2) .myTextCase(.uppercase) .myMultilineTextAlignment(.center) .myItalic() } 🤔

Slide 56

Slide 56 text

struct TextStyle { var font: Font? = nil var foregroundColor: Color? = nil } struct MyText: View { var text: String @State private var style = TextStyle() var body: some View { } func myFont(_ font: Font?) -> MyText { style.font = font return self } func myForegroundColor(_ color: Color?) -> MyText { style.foregroundColor = color return self } } These run during view initialization - state handling hasn’t been fully set up yet!

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

dump(view) ▿ SwiftUIEnvironmentDemos.MyText - text: "Hello world" ▿ _style: SwiftUI.State ▿ _value: SwiftUIEnvironmentDemos.TextStyle - font: nil - foregroundColor: nil - fontWeight: nil - lineLimit: nil - textCase: nil - multilineTextAlignment: SwiftUI.TextAlignment.leading - minimumScaleFactor: 1.0 - truncationMode: SwiftUI.Text.TruncationMode.tail - italic: false - _location: nil

Slide 59

Slide 59 text

Attempt 2: Making copies, lots of copies

Slide 60

Slide 60 text

struct TextStyle { var font: Font? = nil var foregroundColor: Color? = nil } struct MyText: View { var text: String var style = TextStyle() var body: some View { } func myFont(_ font: Font?) -> MyText { var newStyle = style newStyle.font = font return MyText(text: text, style: newStyle) } func myForegroundColor(_ color: Color?) -> MyText { var newStyle = style newStyle.foregroundColor = color return MyText(text: text, style: newStyle) Remove the @State Change the view configuration Return a copy of the view

Slide 61

Slide 61 text

#Preview { MyText(text: "Hello world") .myFont(.title) .myForegroundColor(.swift) .myFontWeight(.bold) .myLineLimit(2) .myTextCase(.uppercase) .myMultilineTextAlignment(.center) .myItalic() } Initialiser will be called for each decorator function

Slide 62

Slide 62 text

dump(view) ▿ SwiftUIEnvironmentDemos.MyText - text: "Hello world" ▿ style: SwiftUIEnvironmentDemos.TextStyle ▿ font: Optional(SwiftUI.Font(provider: SwiftUI.(unknown context at $1d4c016c0).FontBox)) ▿ some: SwiftUI.Font ▿ provider: SwiftUI.(unknown context at $1d4c016c0).FontBox #0 - super: SwiftUI.AnyFontBox ▿ base: SwiftUI.Font.(unknown context at $1d4c14e0c).TextStyleProvider - style: SwiftUI.Font.TextStyle.title - design: nil - weight: nil ▿ foregroundColor: Optional(UIExtendedSRGBColorSpace 0.94 0.303056 0.2162 1) ▿ some: UIExtendedSRGBColorSpace 0.94 0.303056 0.2162 1 ▿ provider: SwiftUI.(unknown context at $1d4c18ab0).ColorBox< __ C.UIColor> #1 - super: SwiftUI.AnyColorBox - super: SwiftUI.AnyShapeStyleBox - base: UIExtendedSRGBColorSpace 0.94 0.303056 0.2162 1 #2 - super: UIColor - super: NSObject ▿ fontWeight: Optional(SwiftUI.Font.Weight(value: 0.4)) ▿ some: SwiftUI.Font.Weight - value: 0.4 ▿ lineLimit: Optional(2) - some: 2 ▿ textCase: Optional(SwiftUI.Text.Case.uppercase) - some: SwiftUI.Text.Case.uppercase - multilineTextAlignment: SwiftUI.TextAlignment.center - minimumScaleFactor: 1.0 - truncationMode: SwiftUI.Text.TruncationMode.tail - italic: true

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

Attempt 3: Using the environment

Slide 65

Slide 65 text

struct TextStyle { var font: Font? = nil var foregroundColor: Color? = nil } extension EnvironmentValues { @Entry var textStyle: TextStyle = TextStyle() } struct MyText: View { var text: String @Environment(\.textStyle) var style var body: some View { } } Define a custom environment key Read the environment value

Slide 66

Slide 66 text

extension View { func myFont(_ font: Font?) -> some View { style.font = font } func myForegroundColor(_ color: Color?) -> some View { style.foregroundColor = color } struct MyText: View { var text: String @Environment(\.textStyle) var style var body: some View { } } Set the style in the decorator function How can we access the environment value here?

Slide 67

Slide 67 text

extension View { func myFont(_ font: Font?) -> some View { style.font = font } func myForegroundColor(_ color: Color?) -> some View { style.foregroundColor = color } struct MyText: View { var text: String @Environment(\.textStyle) var style var body: some View { } } self.transformEnvironment(\.textStyle) { style in } transformEnvironment(\.textStyle) { style in } This gives us convenient access to the environment

Slide 68

Slide 68 text

Advanced Use Cases Action Handlers

Slide 69

Slide 69 text

SwiftUI Styling Action Handlers

Slide 70

Slide 70 text

Configuring Features SwiftUI Styling Action Handlers

Slide 71

Slide 71 text

Exposing inner state Configuring Features SwiftUI Styling Action Handlers

Slide 72

Slide 72 text

Action Handlers Configuring Features SwiftUI Styling Exposing inner state

Slide 73

Slide 73 text

Action Handlers

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

struct ActionHandlerDemo: View { @State private var showingPopover = false var body: some View { Avatar(image: Image("peter"), title: "Peter Friese", subtitle: "Google", size: 100 ) .onEditProfile { showingPopover.toggle() } .popover(isPresented: $showingPopover, attachmentAnchor: .point(.top), arrowEdge: .bottom) { Text("@peterfriese.dev") .font(.body) .padding() .presentationCompactAdaptation(.popover) } } }

Slide 76

Slide 76 text

struct Avatar: View { let image: Image let title: String let subtitle: String var size: CGFloat = 44 var body: some View { VStack { image .resizable() .aspectRatio(contentMode: .fill) .frame(width: size, height: size) .clipShape(Circle()) Text(title) .font(.headline) Text(subtitle) .font(.subheadline) .foregroundStyle(.secondary) } } }

Slide 77

Slide 77 text

struct Avatar: View { let image: Image let title: String let subtitle: String var size: CGFloat = 44 var body: some View { VStack { image .resizable() .aspectRatio(contentMode: .fill) .frame(width: size, height: size) .clipShape(Circle()) Text(title) .font(.headline) Text(subtitle) .font(.subheadline) extension EnvironmentValues { @Entry var onEditProfile: () - > Void = {} }

Slide 78

Slide 78 text

struct Avatar: View { let image: Image let title: String let subtitle: String var size: CGFloat = 44 var body: some View { VStack { image .resizable() .aspectRatio(contentMode: .fill) .frame(width: size, height: size) .clipShape(Circle()) Text(title) .font(.headline) extension EnvironmentValues { @Entry var onEditProfile: () - > Void = {} } @Environment(\.onEditProfile) private var onEditProfile

Slide 79

Slide 79 text

struct Avatar: View { let image: Image let title: String let subtitle: String var size: CGFloat = 44 var body: some View { VStack { image .resizable() extension EnvironmentValues { @Entry var onEditProfile: () - > Void = {} } @Environment(\.onEditProfile) private var onEditProfile extension View { func onEditProfile(_ action: @escaping () -> Void) -> some View { environment(\.onEditProfile, action) } } Convenience view modifier

Slide 80

Slide 80 text

struct ActionHandlerDemo: View { @State private var showingPopover = false var body: some View { Avatar(image: Image("peter"), title: "Peter Friese", subtitle: "Google", size: 100 ) .onEditProfile { showingPopover.toggle() } .popover(isPresented: $showingPopover, attachmentAnchor: .point(.top), arrowEdge: .bottom) { Text("@peterfriese.dev") .font(.body) .padding() .presentationCompactAdaptation(.popover) } } }

Slide 81

Slide 81 text

Learn how to build a reusable Avatar view Building Reusable SwiftUI Components https://bit.ly/swiftui-components-tutorial

Slide 82

Slide 82 text

Action Handlers Configuring Features SwiftUI Styling Exposing inner state

Slide 83

Slide 83 text

SwiftUI Styling Configuring Features Exposing inner state Action Handlers

Slide 84

Slide 84 text

SwiftUI Styling

Slide 85

Slide 85 text

Let ’ s build a button, shall we? Button(action: { print("Button tapped!") }) { Text("Home-made button") .font(.headline) .fontWeight(.semibold) .foregroundStyle(Color.white) .padding(.horizontal, 8) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.blue) ) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color.blue.opacity(0.8), lineWidth: 1) ) } .shadow(color: .black.opacity(0.1), radius: 4, y: 2)

Slide 86

Slide 86 text

Let ’ s build a button, shall we? Button("Bordered Prominent") { print("Button tapped!") } .buttonStyle(.borderedProminent)

Slide 87

Slide 87 text

What does it take to build your own style?

Slide 88

Slide 88 text

Content Warning The following slides contain scenes that may be traumatizing to some audiences 􀋯

Slide 89

Slide 89 text

🤌

Slide 90

Slide 90 text

.toggleStyle()

Slide 91

Slide 91 text

Toggle("Cappuccino after 12", isOn: $cappuccinoAfter12) .toggleStyle(.reminder) I know - it’s a horrible crime, but let’s focus here for a moment, Ok?

Slide 92

Slide 92 text

struct ReminderToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) - > some View { HStack { Image( systemName: configuration.isOn ? "largecircle.fill.circle" : "circle" ) .resizable() .frame(width: 24, height: 24) .foregroundColor(configuration.isOn ? .accentColor : .gray) .onTapGesture { configuration.isOn.toggle() } configuration.label } } }

Slide 93

Slide 93 text

struct ReminderToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) - > some View { HStack { Image( systemName: configuration.isOn ? "largecircle.fill.circle" : "circle" ) .resizable() .frame(width: 24, height: 24) .foregroundColor(configuration.isOn ? .accentColor : .gray) .onTapGesture { configuration.isOn.toggle() } configuration.label } } }

Slide 94

Slide 94 text

struct ReminderToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) - > some View { HStack { Image( systemName: configuration.isOn ? "largecircle.fill.circle" : "circle" ) .resizable() .frame(width: 24, height: 24) .foregroundColor(configuration.isOn ? .accentColor : .gray) .onTapGesture { configuration.isOn.toggle() } configuration.label } } }

Slide 95

Slide 95 text

struct ReminderToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) - > some View { HStack { Image( systemName: configuration.isOn ? "largecircle.fill.circle" : "circle" ) .resizable() .frame(width: 24, height: 24) .foregroundColor(configuration.isOn ? .accentColor : .gray) .onTapGesture { configuration.isOn.toggle() } configuration.label } } }

Slide 96

Slide 96 text

struct ReminderToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) - > some View { HStack { Image( systemName: configuration.isOn ? "largecircle.fill.circle" : "circle" ) .resizable() .frame(width: 24, height: 24) .foregroundColor(configuration.isOn ? .accentColor : .gray) .onTapGesture { configuration.isOn.toggle() } configuration.label } } }

Slide 97

Slide 97 text

struct ReminderToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) - > some View { HStack { Image( systemName: configuration.isOn ? "largecircle.fill.circle" : "circle" ) .resizable() .frame(width: 24, height: 24) .foregroundColor(configuration.isOn ? .accentColor : .gray) .onTapGesture { configuration.isOn.toggle() } configuration.label } } } extension ToggleStyle where Self = = ReminderToggleStyle { static var reminder: ReminderToggleStyle { ReminderToggleStyle() } }

Slide 98

Slide 98 text

.toggleStyle(.reminder)

Slide 99

Slide 99 text

Content Warning The following slides contain scenes that may be extremely traumatizing to some audiences 􀋯

Slide 100

Slide 100 text

VStack(alignment: .leading) { Toggle("Pizza with Pineapple", isOn: $pizzaWithPineapple) Toggle("Cappuccino after 12", isOn: $cappuccinoAfter12) Toggle("Cook Pasta al Dente", isOn: $alDente) Toggle("Break Spaghetti", isOn: $breakSpaghetti) Toggle("Oil in Pasta Water", isOn: $oilInPasta) if hasItalianFoodCrime { Image("nogodno") .resizable() .scaledToFit() .frame(maxWidth: .infinity) .padding(.top, 8) } } .toggleStyle(.reminder) I know - it’s a horrible crime, but let’s focus here for a moment, Ok?

Slide 101

Slide 101 text

SwiftUI Styling Configuring Features Exposing inner state Action Handlers

Slide 102

Slide 102 text

Exposing inner state Action Handlers Configuring Features SwiftUI Styling

Slide 103

Slide 103 text

Configuring Features

Slide 104

Slide 104 text

Exposing inner state Action Handlers Configuring Features SwiftUI Styling

Slide 105

Slide 105 text

Configuring Features Action Handlers SwiftUI Styling Exposing inner state

Slide 106

Slide 106 text

Exposing inner state

Slide 107

Slide 107 text

Resources Source Code YouTube series https://bit.ly/3n99fis https://bit.ly/building-swiftui-components Composable Styles (Moving Parts) https://bit.ly/47w7wJY

Slide 108

Slide 108 text

Resources Creating a Styleable Toggle Slides for this talk https://bit.ly/3siupAk https://speakerdeck.com/peterfriese

Slide 109

Slide 109 text

FREE all-in-one bundle! Content bundle https://bit.ly/swiftui-content-bundle