You are on page 1of 202

VISUAL QUICKSTART REFERENCE

SwiftUI Views
Free Sample Beta 7

Mark Moeykens YOUR COMPREHENSIVE VISUAL REFERENCE GUIDE Big Mountain Studio
Table of Contents
Foreword by Meng To 16

How To Use 17

Conventions 18

Code Formatting 19
Omitting Code 20

SwiftUI 21

Understanding the Syntax 22


The View 23
Property Getters 24
Opaque Types 25
View Containers 26
View Container Initialization 27
@ViewBuilder Parameter Attribute 28

Some Basics 29

My Basic Format 30
Short Introduction to Symbols 31
Layers 32
Short Introduction to Shapes 33

Layout Behavior 34
Some Views Pull In 35
Some Views Push Out 36

Layout Views 37

VStack 38

ii
Table of Contents
VStack - Introduction 39
VStack - Spacing 40
VStack - Alignment 41

HStack 42
HStack - Introduction 43
HStack - Spacing 44
HStack - Alignment 45
HStack - Layout Priority 46

ZStack 47
ZStack - Introduction 48
ZStack - Ignore Safe Area 49
ZStack - Layering & Aligning 50

Spacer 51
Spacer - Introduction 52
Spacer - Evenly Spaced 53
Spacer - Minimum Length 54

GeometryReader 55
GeometryReader - Introduction 56
GeometryReader - Positioning 57
GeometryReader - Getting Size 58
GeometryReader - Getting Coordinates 59
GeometryReader - SafeAreaInsets 60

Control views 61

Button 62
Button - Introduction 63
Button - Text Composition 64
Button - With Backgrounds 65

iii
Table of Contents
Button - With Borders 66
Button - With SF Symbols 67
Button - With Images 68
Button - Floating 69

DatePicker 70
DatePicker - Introduction 71
DatePicker - Titles & Offset 72
DatePicker - Displayed Components 73
DatePicker - In Forms and Lists 74
DatePicker - From a Specific Date or Time 75
DatePicker - To a Specific Date or Time 76
DatePicker - With Minimum and Maximum Date Range 77

Form 78
Form - Introduction 79
Form - Section Headers and Footers 80
Form - List Row Background 81
Form - Background Images 82
Form - Row Inset 83
Form - With Controls 84

List 85
List - Introduction 86
List - Grouped List Style 87
List - Custom Rows 88
List - Delete Rows 89
List - Move Rows 90
List - Row Background 91
List - Row Inset 92
List - Custom Rows 93
List - Headers and Footers 93
List - Removing Separator Lines After Rows 94

iv
Table of Contents
NavigationView 95
NavigationView - Introduction 96
NavigationView - Display Mode 97
NavigationView - NavigationBarItems: Example Views 98
NavigationView - NavigationBarItems 99

Picker 100
Picker - Introduction 101
Picker - Customized 102
Picker - Custom Rows 103
Picker - Binding Rows to Data 104

ScrollView 105
ScrollView - Introduction 106
ScrollView - Scroll Horizontally 107

SecureField 108
SecureField - Introduction 109
SecureField - Customizations 110
SecureField - Customization Layers 111

Segmented Control 112


Segmented Control (Picker) - Introduction 113
Segmented Control (Picker) - No Segment Selected 114
Segmented Control (Picker) - Colors 115

Slider 116
Slider - Introduction 117
Slider - Customization 118
Slider - With Images 119

Stepper 120
Stepper - Introduction 121

v
Table of Contents
Stepper - Range 122
Stepper - Customization 123
Stepper - Colors 124

TabView 125
TabView - Introduction 126
TabView - TabItems 127
TabView - Too Many Tabs 128
TabView - Navigation 129
TabView - Colors 130

Text 131
Text - Introduction 132
Text - Text Styles 133
Text - Weights 134
Text - FontDesign 135
Text - Formatting 136
Text - Allows Tightening 137
Text - Minimum Scale Factor 138
Text - Line Spacing 139
Text - Alignment 140
Text - Truncation Mode 141
Text - Combining Modified Text 142
Text - Baseline Offset 143
Text - Layout Priority 144
Text - Custom Fonts 145
Text - Imported Fonts 146

TextField 147
TextField - Introduction 148
TextField - Title (Placeholder or Hint Text) 149
TextField - Text Size and Fonts 150
TextField - Customizing Colors 151

vi
Table of Contents
TextField - Custom Composition 152

Toggle 153
Toggle - Introduction 154
Toggle - Color Customization 155

Other Views 156

Circular Shapes 157


Circular Shapes - Introduction 158
Circular Shapes - Color 159
Circular Shapes - Stroke (Outline) 160
Circular Shapes - Stroke Border (Inner Stroke) 161
Circular Shapes - Sizing with Padding and Frame 162
Circular Shapes - Size Modifier 163
Circular Shapes - Trim 164
Circular Shapes - With Buttons 165

Color 166
Color - Introduction 167
Color - As Background 168
Color - Light & Dark Modes 169
Color - System Colors 170
Color - Stacking Secondary Colors 171
Color - Color Multiply 172
Color - Opacity/Alpha 173
Color - Opacity Modifier 174
Color - Color Invert 175

Divider 176
Divider - Introduction 177
Divider - Customizing 178

vii
Table of Contents
Group 179
Group - Introduction 180
Group - Shared Modifiers 181

Image 182
Image - Introduction 183
Image - Resizing 184
Image - Clipping 185
Image - Symbols Introduction 186
Image - Symbol Sizing 187
Image - Symbol Weight 188

Path 189
Path - Introduction 190
Path - Triangles 191

Rectangular Shapes 192


Rectangular Shapes - Introduction 193
Rectangular Shapes - Colors 194
Rectangular Shapes - Stroke (Outline) 195
Rectangular Shapes - Stroke Border (Inner Stroke) 196
Rectangular Shapes - Sizing with Padding and Frame 197
Rectangular Shapes - Size Modifier 198
Rectangular Shapes - Trim 199
Rectangular Shapes - With Controls 200

Paints 201

AngularGradient 202
AngularGradient - Introduction 203
AngularGradient - Applied to Circles 204

ImagePaint 205

viii
Table of Contents
ImagePaint - Introduction 206
ImagePaint - With Strokes 207

LinearGradient 208
LinearGradient - Introduction 209
LinearGradient - On Shapes & Controls 210
LinearGradient - Gradient Direction 211
LinearGradient - Custom Direction 212
LinearGradient - Color Location 213

RadialGradient 214
RadialGradient - Introduction 215

Presenting 216

ActionSheet 217
ActionSheet - Introduction 218
ActionSheet - Presenting with Bool 219
ActionSheet - Passing Data Into 220
ActionSheet - Buttons 221

Alert 222
Alert - Introduction 223
Alert - Presenting with Bool 224
Alert - Passing Data Into 225
Alert - Button Options 226

ContextMenu 227
ContextMenu - Introduction 228
ContextMenu - Introduction - Menu Shown 229
ContextMenu - Conditionally Showing 230
ContextMenu - Conditionally Showing - Menu Shown 231

ix
Table of Contents
Sheet (Modals) 232
Sheet (Modals) - Presenting with Bool 233
Sheet (Modals) - Presenting with Identifiable 234

Popover 235
Popover - Presenting with Bool 236
Popover - Presenting on iPad 237
Popover - Colors 238

Custom Popups 239


Custom Popups - Presenting with Bool 240
Custom Popups - The Popup 241

Layout Modifiers 242

Aspect Ratio 243


Aspect Ratio - Introduction 244
Aspect Ratio - Resizing for Devices 245

Background 246
Background - Colors 247
Background - Using Gradients 248
Background - Using Shapes 249

Content Shape 250


Content Shape - Introduction 251

Edges Ignoring Safe Area 252


Edges Ignoring Safe Area - Introduction 253
Edges Ignoring Safe Area - Landscape 254
Edges Ignoring Safe Area - Handling Orientation 255

Fixed Size 256

x
Table of Contents
Fixed Size - Introduction 257
Fixed Size - Horizontal & Vertical 258

Frame 259
Frame - Fixed Sizes 260
Frame - Controls & Shapes 261
Frame - Alignment 262
Frame - Minimum & Maximum Sizes 263

Hidden 264
Hidden - Introduction 265

Offset 266
Offset - Introduction 267

Overlay 268
Overlay - Introduction 269
Overlay - Compared with ZStack 270

Scale To Fill 271


Scale To Fill - Introduction 272

Scale To Fit 273


Scale To Fit - Introduction 274

Effect Modifiers 275

Blend Mode 276


Blend Mode - Setup Code 277
Blend Mode - Introduction 278
Blend Modes: Color - Multiply 279
Blend Modes: Normal - SourceAtop 280

xi
Table of Contents
Blur 281
Blur - Introduction 282
Blur - Underneath Layers 283

Border 284
Border - Introduction 285
Border - Rounded Corners 286
Border - Rounded Corners on Images 287

Brightness 288
Brightness - Introduction 289

Clipped 290
Clipped - Introduction 291

Clip Shape 292


Clip Shape - Introduction 293

Color Invert 294


Color Invert - Introduction 295
Color Invert - With Photos 296

Color Scheme 297


Color Scheme - Introduction 298

Compositing Group 299


Compositing Group - Introduction 300

Contrast 301
Contrast - Introduction 302
Contrast - With Photos 303

Corner Radius 304

xii
Table of Contents
Corner Radius - Introduction 305
Corner Radius - Fully Rounded Sides 306

Drawing Group 307


Drawing Group - Introduction 308

Foreground Color 309


Foreground Color - Introduction 310
Foreground Color - View Hierarchy 311

Grayscale 312
Grayscale - Introduction 313
Grayscale - With Photos 314

Hue Rotation 315


Hue Rotation - Introduction 316
Hue Rotation - More Colors 317
Hue Rotation - With Photos 318

Luminance To Alpha 319


Luminance To Alpha - Introduction 320
Luminance To Alpha - Dark Mode 321

Opacity 322
Opacity - Introduction 323
Opacity - On Layers 324

Rotation Effect 325


Rotation Effect - Introduction 326
Rotation Effect - Anchors 327

Rotation 3D Effect 328


Rotation 3D Effect - X Axis 329

xiii
Table of Contents
Rotation 3D Effect - Y Axis 330
Rotation 3D Effect - Z Axis 331
Rotation 3D Effect - Anchors 332
Rotation 3D Effect - X & Y Axes 333
Rotation 3D Effect - X, Y & Z Axes 334

Saturation 335
Saturation - With Colors 336
Saturation - With Photos 337

Scale Effect 338


Scale Effect - Introduction 339
Scale Effect - Frames & Content 340
Scale Effect - On X & Y Axes 341
Scale Effect - Flip Horizontally or Vertically 342
Scale Effect - Anchors 343

Shadow 344
Shadow - Radius 345
Shadow - Colors 346
Shadow - On Different Views 347
Shadow - Offset 348
Shadow - Order Matters 349
Shadow - Opacity & Shadows 350

Transform Effect 352


Transform Effect - Translation 353
Transform Effect - Compared With Offset 354
Transform Effect - Rotation 355
Transform Effect - Compared with RotationEffect 356
Transform Effect - Scale 357
Transform Effect - Compared With ScaleEffect 358

xiv
Table of Contents
Resources 359

Learn More SwiftUI 360


Chris Ching 361
Kyle Lee 362
Mark Moeykens 363
Max Nelson 364
Meng To 365
Paul Hudson 366
Scott Smith 367
Sean Allen 368

Thank you 369

Affiliate Info 370

xv
Foreword by Meng To
I have been teaching Swift to designers and design to coders for years now. SwiftUI is an

incredible step in the direction of combining these two fields. Using Xcode to create apps

in SwiftUI is feeling more and more like using a design tool.

If you are looking for a reference guide when using SwiftUI to build your apps, then Mark

has you covered with this excellent resource here. He has been sharing his Swift

knowledge for years in an easy to understand manner and this book continues to follow

this tradition.

I wish you the best in your journey to learning SwiftUI. This technology is already powerful

and will only get better with time. I have enjoyed learning SwiftUI and I think you will too.

Meng To
designcode.io
16
HOW TO USE
This is a visual REFERENCE GUIDE. Find a screenshot of

something you want to learn more about or produce in your app

and then read it and look at the code.

Read what is on the screenshots to learn more about the views

and what they can do.

You can also read the book from beginning to end. The choice is

yours.

17
CONVENTIONS

18 Photo by Rod Liberal


SwiftUI
Code Formatting
Throughout this book, you may notice I don’t always follow the same formatting conventions. This is due to limited vertical space.

For example, on one page you may see code formatted like this (pseudo-code):

NewView()
.modifyTheView1()
.modifyTheView2()

And then on another page, you see code formatted like this:

NewView().modifyTheView1().modifyTheView2()

Other times, functions may be on the same line as the closing brace:

NewView {
...
}.modifyTheView2()

Or on the next line:

NewView {
...
}
.modifyTheView2()

In the end, how the code is formatted in your project is up to you. These inconsistencies are strictly due to limited printing space.
www.bigmountainstudio.com 19 Free Sample (Full Version Here)
SwiftUI
Omitting Code
When using SwiftUI, the views (screens) are represented in a struct, inside a body property. This will become apparent when you
add your first SwiftUI file to your project.

In most examples, you will see the struct and body property are missing. Again, this is due to limited vertical spacing. The main
thing to remember is that the relevant code is always shown.

For example, I may take this view here (pseudo-code):

struct MyView {
var body {
NewView()
.modifyTheView1()
.modifyTheView2()
}
}

And show only the relevant code:

NewView()
.modifyTheView1()
.modifyTheView2()

When space is limited, I omit the unnecessary code and show an ellipsis:

struct MyView {
var body {
... // Unnecessary code omitted
NewView()
}
}

www.bigmountainstudio.com 20 Free Sample (Full Version Here)


SWIFTUI

21 Photo by David Mark


UNDERSTANDING THE
SYNTAX
If you have used Swift in the past, the SwiftUI syntax may look a little different. It may not be readily apparent
just how this code can even compile. This chapter is to help you understand how it is all able to work.
22
SwiftUI
The View
struct BasicSyntax: View {
var body: some View {
Text("Hello World!")
}
}

Views in SwiftUI are structs that conform to the View protocol. There is just one
property to implement, the body property.

If “body” is a property then where is the “get” and the “return” syntax?

www.bigmountainstudio.com 23 Free Sample (Full Version Here)


SwiftUI
Property Getters
Properties can have a getter and struct Person {
// Computed read-only property (no set, value is not stored)
setter. But when a property has no setter,
var personType: String {
it’s called a “read-only” property. And get {
when the property does not store a value, return "human"
it is called a “computed” property. This is }
because the value is computed or }
generated every time the property is }
read.
// Change 1 - Remove the return
In this example, personType is a struct Person {
var personType: String {
computed read-only property.
get {
"human"
You can further simplify this property }
}
in two ways: }

1. When the code inside the get is a // Change 2 - Remove the get
single expression (one thing), the getter var personType: String {
will just return it automatically. You can "human"
remove return. }
See “Change 1” in the code example. }

2. When a property is read-only (no // SwiftUI with the get and return keywords
struct BasicSyntax: View {
setter), we can remove the get.
var body: some View {
get {
Just know that these changes are
return Text("Hello World!")
optional. You can, for example, write the }
previous SwiftUI syntax with a get and }
return inside the body property. This }
might look more familiar to you now.

www.bigmountainstudio.com 24 Free Sample (Full Version Here)


SwiftUI
Opaque Types
struct BasicSyntax: View {
var body: some View {
Text("Hello World!")
}
}

Looking at this code again, you notice the some keyword here. Normally, when
defining a type for a property, you wouldn’t see this word.

So what does this word do?

Opaque Types
The keyword some is specifying that an opaque type is being returned. In this case,
the opaque type is View. So why is the type called “opaque”? Well, the English
definition for the word “opaque”, when referring to languages, means “hard or
impossible to understand.” And this is true here because opaque types hide the
value’s type information and implementation details. This will certainly make it
“hard or impossible to understand” but still usable.

When this View (BasicSyntax) is used by iOS to draw the screen, it doesn’t have to
know that, in this case, the type Text is being returned. It is OK with just knowing
that some View is being returned and can use it to draw the screen.

And so you can return anything in that body property as long as it conforms to the
View protocol.

For more information on Opaque Types, I recommend referring to the Swift


Programming Language documentation.

www.bigmountainstudio.com 25 Free Sample (Full Version Here)


SwiftUI
View Containers
struct Example: View {
var body: some View {
VStack {
Text("Hello World!")
Text("This Vertical Stack is using a function builder")
}
}
}

So far, you have learned that body is a computed read-only property and can
only return ONE object that is some View. What if you wanted to show multiple
views though?

In SwiftUI, there is the concept of “containers”. There are views that can contain
other views. Remember, the body property can only return one view. You will get
an error if you try to return more than one view in the body property.

In the example above, the VStack (Vertical Stack) is the one view that is being
returned. And that vertical stack is a container with two more views inside of it.

The VStack is using a “trailing closure” which just means that a code block is
passed into the initializer to be run by the VStack. You have probably seen this
before in Swift, this is not new.

What is new in Swift is the ability to create multiple, new views within the
constructor like this. Before we get into this though, let’s better understand how
this constructor works.

www.bigmountainstudio.com 26 Free Sample (Full Version Here)


SwiftUI
View Container Initialization
In Swift, you usually see parentheses struct Example: View {
var body: some View {
during initialization but with a trailing
VStack {
closure, the parentheses are optional. Text("Hello World!")
Text("This Vertical Stack is using a function builder")
You can add them and the code will still }
work just fine. }
See “Change 1” in the code example. }

This change may start looking more // Change 1 - Add parentheses and parameter name
familiar to you. struct Example: View {
var body: some View {
Now, the question is: VStack(content: {
Text("Hello World!")
Text("This Vertical Stack is using a function builder")
How does the VStack know how to })
accept the multiple views like this? }
}

This is new in Swift. To better


// VStack initializer
understand this, take a look at the
init(alignment: HorizontalAlignment = .center, spacing: CGFloat? =
VStack’s initializer. nil, @ViewBuilder content: () -> Content)

The alignment and spacing parameters


are optional, that is why you don’t see
them in the examples above. But notice
before the content parameter there is
@ViewBuilder syntax.

This is what allows you to declare


multiple views within the content
parameter’s closure.

www.bigmountainstudio.com 27 Free Sample (Full Version Here)


SwiftUI
@ViewBuilder Parameter Attribute
The @ViewBuilder parameter struct ViewBuilderExample: View {
var body: some View {
attribute allows Swift to build multiple VStack {
child views from within a closure. Text("View 1")
Text("View 2")
Text("View 3")
How many child views can I build Text("View 4")
within a closure? Text("View 5")
Text("View 6")
The way this functionality is set up, you Text("View 7")
Text("View 8")
can only initialize a maximum of ten (10) Text("View 9")
views. In the example here, you will get Text("View 10")
Text("View 11") // Will cause an error
an error because of the 11th view.
}
}
What if I need more child views? }

If you need to declare more child views struct ViewBuilderExample: View {


var body: some View {
for your user interface, then you will
VStack {
have to use another view container, ... // Text views 1 - 5
such as another VStack. (You will be Text("View 6")
Text("View 7")
seeing more options for containers in Text("View 8")
this book.) Text("View 9")
VStack { // The VStack is now the 10th view
Text("View 10")
In the second example, I use another
Text("View 11")
VStack to contain text views 10 and 11. }
}
}
}

www.bigmountainstudio.com 28 Free Sample (Full Version Here)


SOME BASICS

If you are completely new to SwiftUI you may wonder what a lot of this code means right at the beginning.
Although this is a reference guide it might be good to cover some basics so you can at least understand many
of the modifiers I use to describe what I am showing you.

This will be very brief because all the modifiers I use are actually described fully in their own sections in this
book. Read the screens and code in this section to help get you started.
29
SwiftUI
My Basic Format
VStack(spacing: 20) { // 20 points of space between each item in the
VStack
Text("Title") // Shows text on the screen
.font(.largeTitle) // Format text to be largest

Text("Subtitle")
.font(.title) // Format text to be second largest
.foregroundColor(.gray) // Change color of text to gray

Text("Short description goes here.")


.padding() // Add space all around this text
.font(.title) // Format text to be second largest
.background(Color.blue) // Add the color blue behind the text
.foregroundColor(Color.white) // Change text color to white

Text("Content of what I am demonstrating goes here.")


.font(.title)

Text("Notice above, the blue background is only behind the width


of the text. I want it to go ALL the way to the sides. So I tell its
frame or boundaries that its width can extend to infinity.")
.padding(.horizontal) // Add extra space to just the left and
right
.font(.title)

Text("Short description goes here.")


.frame(maxWidth: .infinity) // Extend until you can't go
anymore
.padding()
.font(.title)
.background(Color.blue)
.foregroundColor(Color.white)
}

www.bigmountainstudio.com 30 Free Sample (Full Version Here)


SwiftUI
Short Introduction to Symbols
struct SymbolsIntro: View {
var body: some View {
VStack(spacing: 20) {
Text("Images")
.font(.largeTitle)
Text("Using SF Symbols")
.font(.title)
.foregroundColor(.gray)

Text("You will see I use icons or symbols to add clarity


to what I'm demonstrating. These come from Apple's new symbol font
library which you can browse using an app called 'SF Symbols'.")
.frame(maxWidth: .infinity)
.padding()
.font(.title)
.background(Color.blue)
.foregroundColor(Color.white)
.layoutPriority(1) // Sometimes the text frame won't
expand to show all the text so I want to give this view priority to
use the space it needs to layout first.

// Use "systemName" when you want to use "SF Symbols"


Image(systemName: "hand.thumbsup.fill")
.font(.largeTitle) // Make the symbol larger

Image("SF Symbols") // Regular image from Assets.xcassets


}
.edgesIgnoringSafeArea(.bottom) // Ignore the bottom screen
border
}
}

www.bigmountainstudio.com 31 Free Sample (Full Version Here)


SwiftUI
Layers
struct Layers: View {
var body: some View {
VStack(spacing: 40) {
Text("Layers")
.font(.largeTitle)

Text("The Basics")
.font(.title)
.foregroundColor(.gray)

Text("With SwiftUI views, you can add a layers on top


(.overlay) and behind (.background) the view.")
.frame(maxWidth: .infinity)
.padding()
.font(.title)
.background(Color.blue)
.foregroundColor(Color.white)
.layoutPriority(1)

Image("yosemite")
.opacity(0.7) // Make image only 70% solid
.background(Color.red) // Layer behind image
.overlay(Text("Yosemite")) // Layer on top of image

Image("Layers")
}
}
}

www.bigmountainstudio.com 32 Free Sample (Full Version Here)


SwiftUI
Short Introduction to Shapes
VStack(spacing: 20) {
Text("Shapes")
.font(.largeTitle)

Text("Short Introduction")
.font(.title)
.foregroundColor(.gray)

Text("I'll make shapes, give them color and put them behind other
views just for decoration.")
...

Text("This text has a rounded rectangle behind it")


.font(.title)
.foregroundColor(Color.white)
.padding() // Add spacing around text to make the size bigger
.background(
RoundedRectangle(cornerRadius: 20) // Create the shape
.foregroundColor(Color.blue)) // Make shape blue
.padding()
.layoutPriority(2) // You need to play with priority order to
get it right sometimes

Text("But sometimes I'll use color and a corner radius:")

Text("This text has a color with a corner radius")


.font(.title)
.foregroundColor(Color.white)
.padding() // Add spacing around text to make the size bigger
.background(Color.blue) // Use a color as the background layer
.cornerRadius(20) // Rounded corners
.layoutPriority(1)
}

www.bigmountainstudio.com 33 Free Sample (Full Version Here)


LAYOUT BEHAVIOR

In SwiftUI, you may wonder why some views layout differently than others. You can observe two behaviors
when it comes to the size and layout of views:
1. Some views pull in to be as small as possible to fit their content. (I will refer to these as “pull-in” views.)
2. Some views push out to fill all available space. (I will refer to these as “push-out” views.)

Knowing these two behaviors can help you predict how to layout views to create the screen you want.
34
SwiftUI
Some Views Pull In
struct ViewSizes_Pull_In: View {
var body: some View {
VStack(spacing: 20) {
Text("Layout Behavior")
.font(.largeTitle)

Text("Controls that Pull In")


.font(.title)
.foregroundColor(.gray)

Text("Some views minimize their frame size so it is only


as big as the content within it.")
.frame(maxWidth: .infinity)
.padding()
.font(.title)
.background(Color.purple)
.foregroundColor(Color.white)
.layoutPriority(1)

Image(systemName: "arrow.down.to.line.alt")

HStack { // Order views horizontally


Image(systemName: "arrow.right.to.line.alt")
Text("Text views pull in")
.font(.title)
Image(systemName: "arrow.left.to.line.alt")
}

Image(systemName: "arrow.up.to.line.alt")
}
}
}

www.bigmountainstudio.com 35 Free Sample (Full Version Here)


SwiftUI
Some Views Push Out
VStack(spacing: 20) {
Text("Layout Behavior")
.font(.largeTitle)
Text("Views that Push Out")
.font(.title)
.foregroundColor(.gray)
Text("Some views will push out to fill up all available space
within their parent.")
...
Color.purple
// Add five layers on top of the color
.overlay(
Image(systemName: "arrow.up.left")
.padding() // Add spacing around the symbol
.font(.largeTitle) // Make symbol larger
, alignment: .topLeading) // Align within the layer
.overlay(
Image(systemName: "arrow.up.right")
.padding() // Add spacing around the symbol
.font(.largeTitle) // Make symbol larger
, alignment: .topTrailing) // Align within the layer
.overlay(
Image(systemName: "arrow.down.left")
.padding() // Add spacing around the symbol
.font(.largeTitle) // Make symbol larger
, alignment: .bottomLeading) // Align within the layer
.overlay(
Image(systemName: "arrow.down.right")
.padding() // Add spacing around the symbol
.font(.largeTitle) // Make symbol larger
, alignment: .bottomTrailing) // Align within the layer
.overlay(Text("Colors are Push-Out views").font(.title))
}

www.bigmountainstudio.com 36 Free Sample (Full Version Here)


LAYOUT VIEWS

37
VSTACK

VStack stands for “Vertical Stack”. It is a pull-in container view in which you pass in up to ten views and it will

compose them one below the next, going down the screen.
38
Layout Views
VStack - Introduction
VStack(spacing: 20) {
Text("VStack")
.font(.largeTitle)

Text("Introduction")
.font(.title)
.foregroundColor(.gray)

Text("VStacks are views that contain other views")


.frame(maxWidth: .infinity, minHeight: 70)
.padding()
.background(Color.blue)
.font(.title)
.foregroundColor(.white)

Text("The containing views are stacked vertically")

VStack {
Text("VStack inside another VStack")
Divider()
Text("This can be handy. Why?")
Divider()
Text("More than 10 views creates an error")
}
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(.blue))
.padding()
}

www.bigmountainstudio.com 39 Free Sample (Full Version Here)


Layout Views
VStack - Spacing
VStack(spacing: 80) {
Text("VStack")
.font(.largeTitle)

Text("Spacing")
.font(.title)
.foregroundColor(.gray)

Text("The VStack initializer allows you to set the spacing between


all the views inside the VStack")
.frame(maxWidth: .infinity)
.padding().layoutPriority(1)
.background(Color.blue).font(.title)
.foregroundColor(.white)

Image(systemName: "arrow.up.and.down.circle.fill")
.font(.largeTitle)

Text("The spacing here between all of these views is 80")


.font(.title)
}

www.bigmountainstudio.com 40 Free Sample (Full Version Here)


Layout Views
VStack - Alignment
VStack(spacing: 20) {
Text("VStack")
.font(.largeTitle)
Text("Alignment")
.font(.title)
.foregroundColor(.gray)
Text("By default, views in a VStack are center aligned.")
...

VStack(alignment: .leading, spacing: 40) {


Text("Leading Alignment")
.font(.title)
Divider() // Creates a thin line (Push-out view)
Image(systemName: "arrow.left")
}
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(.blue))
.padding()

VStack(alignment: .trailing, spacing: 40) {


Text("Trailing Alignment")
.font(.title)
Divider()
Image(systemName: "arrow.right")
}
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(.blue))
.padding()
}

www.bigmountainstudio.com 41 Free Sample (Full Version Here)


HSTACK

HStack stands for “Horizontal Stack”. It is a pull-in container view in which you pass in up to ten views and it will

compose them side-by-side.


42
Layout Views
HStack - Introduction
VStack(spacing: 20) {
Text("HStack").font(.largeTitle)
Text("Introduction")
.font(.title).foregroundColor(.gray)
Text("HStacks are views that contain other views laid out
horizontally.")
...
HStack {
Text("Leading")
Text("Middle")
Text("Trailing")
}
.padding()
.border(Color.orange) // Create a 2 point border using the color
specified

HStack(spacing: 10) {
Image(systemName: "1.circle")
Image(systemName: "2.circle")
Image(systemName: "3.circle")
}.padding()

HStack(spacing: 20) {
Image(systemName: "a.circle.fill")
Image(systemName: "b.circle.fill")
Image(systemName: "c.circle.fill")
Image(systemName: "d.circle.fill")
Image(systemName: "e.circle.fill")
}
.font(.largeTitle).padding()
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(.orange))
}

www.bigmountainstudio.com 43 Free Sample (Full Version Here)


Layout Views
HStack - Spacing
VStack(spacing: 40) {
Text("HStack")
.font(.largeTitle)

Text("Spacing")
.font(.title)
.foregroundColor(.gray)

Text("The HStack initializer allows you to set the spacing between


all the views inside the HStack")
.frame(maxWidth: .infinity)
.padding().layoutPriority(1)
.background(Color.orange).font(.title)
.foregroundColor(.black)

Text("Default Spacing")
.font(.title)
HStack {
Image(systemName: "1.circle")
Image(systemName: "2.circle")
Image(systemName: "3.circle")
}.font(.largeTitle)

Divider()

Text("Spacing: 100")
.font(.title)
HStack(spacing: 100) {
Image(systemName: "1.circle")
Image(systemName: "2.circle")
Image(systemName: "3.circle")
}.font(.largeTitle)
}

www.bigmountainstudio.com 44 Free Sample (Full Version Here)


Layout Views
HStack - Alignment
Text("By default, views within an HStack are vertically aligned in the
center.")
...
HStack {
Rectangle().foregroundColor(.orange).frame(width: 25)
Text("Leading")
Spacer()
Text("Center")
Spacer()
Text("Trailing")
.padding(.trailing)
}
.border(Color.orange)
HStack(alignment: .top) {
Rectangle().foregroundColor(.orange).frame(width: 25)
Text("Leading")
Spacer()
Text("Top")
Spacer()
Text("Trailing")
.padding(.trailing)
}
.border(Color.orange)
HStack(alignment: .bottom) {
Rectangle().foregroundColor(.orange).frame(width: 25)
Text("Leading")
Spacer()
Text("Bottom")
Spacer()
Text("Trailing")
.padding(.trailing)
}
.border(Color.orange)

www.bigmountainstudio.com 45 Free Sample (Full Version Here)


Layout Views
HStack - Layout Priority

When using a horizontal stack with text views within it, there’s a chance that
text might truncate if you are not allowing them to wrap. In this case, you can
prioritize which one will truncate last with layout priority. The default value is 0.
The higher the number, the higher the priority to have enough space to not be
truncated.

HStack {
Text("SwiftUI")
.font(.largeTitle).lineLimit(1) // Don't let text wrap
Image("SwiftUI")
.resizable()
.frame(width: 80, height: 80)
Text("Brings Balance")
.font(.largeTitle)
.layoutPriority(1) // Truncate last
}
.padding([.horizontal])
Divider()
HStack {
Text("SwiftUI")
.font(.largeTitle)
.layoutPriority(1) // Truncate last
Image("SwiftUI")
.resizable()
.frame(width: 80, height: 80)
Text("Brings Balance")
.font(.largeTitle).lineLimit(1) // Don't let text wrap
}
.padding(.horizontal)

www.bigmountainstudio.com 46 Free Sample (Full Version Here)


ZSTACK

A ZStack is a push-out container view. It is a view that overlays its child views on top of each other. (“Z”

represents the Z-axis which is depth-based in a 3D space.)

You learned earlier about creating layers with the background and overlay modifiers. ZStack is another way to

create layers with views that control their own sizing and spacing.

47
Layout Views
ZStack - Introduction
ZStack {
// LAYER 1: Furthest back
Color.gray // Yes, Color is a view!

// LAYER 2: This VStack is on top.


VStack(spacing: 20) {
Text("ZStack")
.font(.largeTitle)
Text("Introduction")
.font(.title).foregroundColor(.white)
Text("ZStacks are great for setting a background color.")
.frame(maxWidth: .infinity)
.padding()
.background(Color.green).font(.title)

Text("But notice the Color stops at the Safe Areas (white


areas on top and bottom).")
.frame(maxWidth: .infinity)
.padding()
.background(Color.green).font(.title)
}
.font(.title)
}

www.bigmountainstudio.com 48 Free Sample (Full Version Here)


Layout Views
ZStack - Ignore Safe Area
ZStack {
Color.gray

VStack(spacing: 20) {
Text("ZStack")
.font(.largeTitle)

Text("Edges Ignoring Safe Area")


.font(.title)
.foregroundColor(.white)

Text("Ignoring the Safe Areas will extend a view to fill the


whole scene.")
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(Color.green)
.font(.title)
}
}
.edgesIgnoringSafeArea(.all)

www.bigmountainstudio.com 49 Free Sample (Full Version Here)


Layout Views
ZStack - Layering & Aligning
VStack {
Text("ZStack")
.font(.largeTitle)

Text("Layering & Aligning")


.font(.title).foregroundColor(.gray)

Text("ZStacks are great for layering views. For example, putting


text on top of an image. You can align all the subviews within the
ZStack.")
.frame(maxWidth: .infinity, minHeight: 171) // Force the
height. Can increase but not go below 171.
.padding().foregroundColor(.white)
.background(Color.green).font(.title)

ZStack(alignment: .bottomTrailing) {
Image("yosemite_large")

Rectangle()
.foregroundColor(transparentWhite)
.frame(width: 390, height: 50)

Text("Yosemite National Park")


.font(.title)
.padding(8) // Override the default padding with 8 points
}
Text("View Hierarchy")
.font(.title).foregroundColor(.gray)

Image("yosemite_layers")
}

www.bigmountainstudio.com 50 Free Sample (Full Version Here)


SPACER

You may notice that when you add new pull-in views, such as Text views, they appear in the center of the

screen. You can use the Spacer to push these views apart, away from the center of the screen.
51
Layout Views
Spacer - Introduction
VStack {
Text("Spacer")
.font(.largeTitle)

Text("Introduction")
.foregroundColor(.gray)

Text("Spacers push things away either vertically or horizontally")


...

Image(systemName: "arrow.up.circle.fill")

Spacer()

Image(systemName: "arrow.down.circle.fill")

HStack {
Text("Horizontal Spacer")

Image(systemName: "arrow.left.circle.fill")

Spacer()

Image(systemName: "arrow.right.circle.fill")
}
.padding(.horizontal)

Color.yellow
.frame(maxHeight: 50) // Height can decrease but not go higher
than 50
}
.font(.title) // Apply this font to every view within the VStack

www.bigmountainstudio.com 52 Free Sample (Full Version Here)


Layout Views
Spacer - Evenly Spaced
Text("Use Spacer to evenly space views horizontally so
they look good on any device.")
...
Text("After")
...
HStack {
Spacer()

VStack(alignment: .leading) {
Text("Names")
.font(.largeTitle)
.underline()
Text("Chase")
Text("Rodrigo")
Text("Mark")
Text("Evans")
}.layoutPriority(1)

Spacer()

VStack(alignment: .leading) {
Text("Color")
.font(.largeTitle)
.underline()
Text("Red")
Text("Orange")
Text("Green")
Text("Blue")
}.layoutPriority(1)

Spacer()
}

www.bigmountainstudio.com 53 Free Sample (Full Version Here)


Layout Views
Spacer - Minimum Length
VStack(spacing: 10) {
Text("Spacer")
.font(.largeTitle)
Text("Minimum Length")
.font(.title)
.foregroundColor(.gray)
Text("You can set a minimum space to exist between views using the
minLength modifier on the Spacer.")
...
Text("No minLength set (system default is used)")
.bold()
HStack {
Image("yosemite")
Spacer()
Text("This is Yosemite National Park").lineLimit(1)
}.padding()

Text("minLength = 0")
.bold()
HStack {
Image("yosemite")
Spacer(minLength: 0)
Text("This is Yosemite National Park").lineLimit(1)
}.padding()

Text("minLength = 20")
.bold()
HStack {
Image("yosemite")
Spacer(minLength: 20)
Text("This is Yosemite National Park").lineLimit(1)
}.padding()
}

www.bigmountainstudio.com 54 Free Sample (Full Version Here)


GEOMETRYREADER

It is difficult, if not impossible, to get the size of a view. This is where the GeometryReader comes in.

The GeometryReader is similar to a push-out container view in that you can add child views to. It will allow you
to inspect and use properties that can help with positioning other views within it. You can access properties
like height, width and safe area insets which can help you dynamically set the sizes of views within it so they
look good on any size device.
55
Layout Views
GeometryReader - Introduction
struct GeometryReader_Intro : View {
var body: some View {
VStack(spacing: 20) {
Text("GeometryReader")
.font(.largeTitle)

Text("Introduction")
.font(.title)
.foregroundColor(.gray)

Text("GeometryReader is a container view that pushes out


to fill up all available space. You use it to help with positioning
items within it.")
.font(.title)
.padding()
.layoutPriority(1)

GeometryReader {_ in
Text("Views center automatically inside the
GeometryReader")
.font(.title)
}
.foregroundColor(.white)
.background(Color.pink)

}
}
}

www.bigmountainstudio.com 56 Free Sample (Full Version Here)


Layout Views
GeometryReader - Positioning
struct GeometryReader_Positioning: View {
var body: some View {
VStack(spacing: 20) {
Text("GeometryReader").font(.largeTitle)
Text("Positioning").font(.title).foregroundColor(.gray)
Text("Use the geometry reader's variable to help position
child views at different locations within the geometry's view instead
of it being in the center.")
.font(.title)
.layoutPriority(1)
.padding()

GeometryReader { geometry in
Text("Upper Left")
.font(.title)
.position(x: geometry.size.width/5,
y: geometry.size.height/10)

Text("Lower Right")
.font(.title)
.position(x: geometry.size.width - 90,
y: geometry.size.height - 40)
}
.background(Color.pink)
.foregroundColor(.white)

Text("Note: The position modifier uses the view's center


point when setting the X and Y parameters.")
.font(.title)
}
}
}

www.bigmountainstudio.com 57 Free Sample (Full Version Here)


Layout Views
GeometryReader - Getting Size
VStack(spacing: 10) {
Text("GeometryReader")
.font(.largeTitle)

Text("Getting Size")
.foregroundColor(.gray)

Text("Use the geometry reader when you need to get the height
and/or width of a space.")
.padding()

GeometryReader { geometry in
VStack(spacing: 10) {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}
.foregroundColor(.white)
}
.background(Color.pink)

GeometryReader { geometry in
VStack(spacing: 10) {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}
.foregroundColor(.white)
}
.background(Color.pink)
.padding(30)
}
.font(.title)

www.bigmountainstudio.com 58 Free Sample (Full Version Here)


Layout Views
GeometryReader - Getting Coordinates
VStack(spacing: 10) {
Text("GeometryReader").font(.largeTitle)
Text("Getting Coordinates").foregroundColor(.gray)
Text("Getting the coordinates (x, y) of a geometry view is little
different. Take a look at this example:")
.layoutPriority(1)

GeometryReader { geometry in
VStack(spacing: 10) {
Text("X: \(geometry.frame(in:
CoordinateSpace.local).origin.x)")
Text("Y: \(geometry.frame(in:
CoordinateSpace.local).origin.y)")
}
.foregroundColor(.white)
}
.background(Color.pink)

Text("The local coordinate space will always give you zeros.")


Text("You need to look globally to get the coordinates inside the
current view:")
GeometryReader { geometry in
VStack(spacing: 10) {
Text("X: \(geometry.frame(in: .global).origin.x)")
Text("Y: \(geometry.frame(in: .global).origin.y)")
}
.foregroundColor(.white)
}
.background(Color.pink)
.frame(height: 200)
}
.font(.title)
.padding(.horizontal)

www.bigmountainstudio.com 59 Free Sample (Full Version Here)


Layout Views
GeometryReader - SafeAreaInsets

Text("GeometryReader can also tell you the safe area insets it has.")
...

GeometryReader { geometry in
VStack {
Text("geometry.safeAreaInsets.leading: \(geometry.safeAreaInsets.leading)")
Text("geometry.safeAreaInsets.trailing: \(geometry.safeAreaInsets.trailing)")
Text("geometry.safeAreaInsets.top: \(geometry.safeAreaInsets.top)")
Text("geometry.safeAreaInsets.bottom: \(geometry.safeAreaInsets.bottom)")
}
}
.font(.title)
.background(Color.purple)

www.bigmountainstudio.com 60 Free Sample (Full Version Here)


CONTROL VIEWS

61
BUTTON

The Button is a pull-in view with a wide range of composition and customization options to be presented to the

user. The button can be just text, just an image or both combined.
62
Control Views
Button - Introduction
VStack(spacing: 20) {
Text("Button")
.font(.largeTitle)
Text("Introduction")
.font(.title).foregroundColor(.gray)
Text("If you just want to show the default text style in a button
then you can pass in a string as the first parameter")
...

Button("Default Button Style") {


// Your code here
}

Text("You can customize the text shown for a button")


.padding().frame(minWidth: 0, maxWidth: .infinity)
.background(Color.purple).layoutPriority(1)
.foregroundColor(.white).font(.title)
Button(action: {}) {
Text("Headline Font")
.font(.headline)
}
Divider()
Button(action: {}) {
Text("Foreground Color")
.foregroundColor(Color.red)
}
Divider()
Button(action: {}) {
Text("Thin Font Weight")
.fontWeight(.thin)
} For more text customization
}
options, see the chapter on Text.

www.bigmountainstudio.com 63 Free Sample (Full Version Here)


Control Views
Button - Text Composition
Text("Button")
.font(.largeTitle)

Text("Text Composition")
.font(.title)
.foregroundColor(.gray)

Text("You can add more than one text view to a button. By default they
are composed within a VStack.")
.padding().frame(maxWidth: .infinity)
.background(Color.purple).layoutPriority(2)
.foregroundColor(.white).font(.title)

Button(action: {}, label: {


Text("New User")
.font(.title)
Text("(Register Here)")
})

Text("Using an HStack")
.padding().frame(maxWidth: .infinity)
.background(Color.purple).layoutPriority(1)
.foregroundColor(.white).font(.title)

Button(action: {}, label: {


HStack {
Text("Forgot Password?")
Text("Tap to Recover")
.foregroundColor(.orange)
}.font(.title)
})

www.bigmountainstudio.com 64 Free Sample (Full Version Here)


Control Views
Button - With Backgrounds
Text("Button").font(.largeTitle)
Text("With Backgrounds").font(.title).foregroundColor(.gray)
Text("As with most views, we can also customize the background and add
a shadow.")
.padding().frame(maxWidth: .infinity)
.background(Color.purple).layoutPriority(1)
.foregroundColor(.white).font(.title)

Button(action: {}) {
Text("Solid Button")
.padding()
.foregroundColor(Color.white)
.background(Color.purple)
.cornerRadius(8)
}
Button(action: {}) {
Text("Button With Shadow")
.padding(12)
.foregroundColor(Color.white)
.background(Color.purple)
.cornerRadius(8)
}
.shadow(color: Color.purple, radius: 20, y: 5) // See more info in
section on Shadows
Button(action: {}) {
Text("Button With Rounded Ends")
.padding(EdgeInsets(top: 12, leading: 20, bottom: 12,
trailing: 20))
.foregroundColor(Color.white)
.background(Color.purple)
.cornerRadius(.infinity) // Infinity will always give you the
perfect corner no matter the size of the view.
}

www.bigmountainstudio.com 65 Free Sample (Full Version Here)


Control Views
Button - With Borders
Text("Button").font(.largeTitle)
Text("With Borders").font(.title).foregroundColor(.gray)
Text("Applying borders can add a nice effect to your buttons. Here are
some options.")
.padding().frame(maxWidth: .infinity)
.background(Color.purple).layoutPriority(1)
.foregroundColor(.white).font(.title)

Button(action: {}) {
Text("Square Border Button")
.padding()
.border(Color.purple)
}
Button(action: {}) {
Text("Rounded Border Button")
.padding()
.border(Color.purple)
.cornerRadius(10)
}
Text("Look what happened when I tried to add a corner radius to the
border. It is clipping the corners. Here is a different way you can
accomplish this:")
...

Button(action: {}) {
Text("Border Button")
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.purple, lineWidth: 2)
)
}

www.bigmountainstudio.com 66 Free Sample (Full Version Here)


Control Views
Button - With SF Symbols
Button(action: {}) {
HStack{
Text("Button With Symbol")
.padding(.horizontal)
Image(systemName: "gift.fill")
}.padding()
}
.foregroundColor(Color.white)
.background(Color.purple)
.cornerRadius(8)

Button(action: {}) {
HStack{
Image(systemName: "magnifyingglass")
Text("Search")
.padding(.horizontal)
}.padding()
}
.foregroundColor(Color.white)
.background(Color.purple)
.cornerRadius(8)

Button(action: {}) {
Image(systemName: "video.fill")
Text("Record")
.padding(.horizontal)
}
.padding()
.foregroundColor(Color.white)
.background(Color.purple)
.cornerRadius(.infinity)

www.bigmountainstudio.com 67 Free Sample (Full Version Here)


Control Views
Button - With Images
struct Button_WithPhotos: View {
var body: some View {
VStack(spacing: 30) {
Text("Button")
.font(.largeTitle)
Text("With Photos")
.font(.title)
.foregroundColor(.gray)
Text("Buttons work fine with the SF Images. But what if you
wanted to use a photo? Look what happens:")
...

Button(action: {}) {
Image("yosemite")
.cornerRadius(40)
}

Text("Try putting the photo BEHIND the image with the


background modifier.")
.padding(.horizontal)
.layoutPriority(2)
.padding(.bottom, 40)

Button(action: {}) {
Text("")
}
.background(Image("yosemite")
.cornerRadius(40))
}
}
}

www.bigmountainstudio.com 68 Free Sample (Full Version Here)


Control Views
Button - Floating
ZStack {
VStack(spacing: 40) {
Text("Button")
.font(.largeTitle)

Text("Floating")
.font(.title).foregroundColor(.gray)

Text("You can also create floating buttons by using a ZStack


so the button on the top layer, over everything else")
...
Spacer()
}

VStack {
Spacer()
HStack {
Spacer()
Button(action: {}) {
Image(systemName: "plus")
.font(.title)
}
.padding(20)
.foregroundColor(Color.white)
.background(Color.orange)
.cornerRadius(.infinity)
}
.padding(.trailing, 30) // Add 30 points on the trailing side
of the button
}
}
See the section on the Overlay modifier in the Layout Modifiers
chapter for more ways to accomplish the same thing.

www.bigmountainstudio.com 69 Free Sample (Full Version Here)


DATEPICKER

In order to get or set a value for the DatePicker, you need to bind it to a variable. This variable is then passed
into the DatePicker’s initializer. Then, all you need to do is change this bound variable’s value to select the date
or time you want to show in the DatePicker. Or read the bound variable’s value to see which date or time is
currently selected.

This is a pull-in view.


70
Control Views
DatePicker - Introduction
@State private var nextFullMoonDate = Date()
...
VStack(spacing: 20) {
Text("DatePicker").font(.largeTitle)
Text("Introduction")
.font(.title).foregroundColor(.gray)
Text("Bind a date variable to get and set the date in the date
picker")
...

Text("Next Full Moon")


.font(.largeTitle)

HStack {
Spacer()
Image(systemName: "moon.circle")
Spacer()
Circle()
.frame(height: 60.0)
Spacer()
Image(systemName: "moon.circle.fill")
Spacer()
}
.font(.title)
.foregroundColor(Color.yellow)

Text("Date of next full moon")


DatePicker("", selection: $nextFullMoonDate,
displayedComponents: .date)
Spacer()
}

www.bigmountainstudio.com 71 Free Sample (Full Version Here)


Control Views
DatePicker - Titles & Offset
@State private var nextFullMoonDate = Date()
...
VStack {
Text("DatePicker")
.font(.largeTitle)
Text("Titles & Offset")
.font(.title).foregroundColor(.gray)
Text("You can provide an optional title for pickers.")
...

DatePicker("Date of next full moon", selection: $nextFullMoonDate,


displayedComponents: .date)
.padding()

Text("Without a title, you might want to offset the picker.")


.padding().frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
.font(.title).layoutPriority(2)

DatePicker("", selection: $nextFullMoonDate,


displayedComponents: .date)
.offset(x: -50, y: 0)

Text("(The offset modifier moves a view horizontally on the X axis


or vertically on the Y axis.)")
.layoutPriority(1)
.padding(.horizontal)
}
.edgesIgnoringSafeArea(.bottom)

www.bigmountainstudio.com 72 Free Sample (Full Version Here)


Control Views
DatePicker - Displayed Components
@State private var justTime = Date()
@State private var theDateAndTime = Date()

...

VStack(spacing: 9) {
Text("DatePicker")
.font(.largeTitle)
Text("Displayed Components")
.font(.title).foregroundColor(.gray)

Text("Show date and time parts with displayedComponents")


...

DatePicker("", selection: $justTime,


displayedComponents: .hourAndMinute)
.offset(x: -50, y: 0)

Text("Combine date and time with an array")


.padding().frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow).layoutPriority(1)
.font(.title).foregroundColor(.black)

DatePicker("", selection: $theDateAndTime,


displayedComponents: [.date, .hourAndMinute])
.offset(x: -50, y: 0)
}
.edgesIgnoringSafeArea(.bottom)

I’m hoping offset won’t be necessary in


future versions of SwiftUI.

www.bigmountainstudio.com 73 Free Sample (Full Version Here)


Control Views
DatePicker - In Forms and Lists
@State private var dateInForm = Date()

var body: some View {


Form {
Section(header: Text("DatePicker")
.font(.largeTitle).padding())
{
Text("Used In Forms")
.font(.title).foregroundColor(.gray)
.padding()

Text("The date picker looks differently when used in a


form. The first parameter called 'title' is used when in forms and
lists.")
.frame(minWidth: 0, maxWidth: .infinity)
.padding().listRowBackground(Color.yellow)
.font(.title)

DatePicker("DatePicker Collapsed (Default)",


selection: $dateInForm,
displayedComponents: .date)

DatePicker("DatePicker Expanded (Selected)",


selection: $dateInForm,
displayedComponents: .date)
}
}
}

Notice that offset isn’t necessary when pickers are used in forms.

www.bigmountainstudio.com 74 Free Sample (Full Version Here)


Control Views
DatePicker - From a Specific Date or Time
@State private var arrivalDate = Date()
let fromToday = Calendar.current.date(byAdding: .minute,
value: -1, to: Date())!
let mainColor = Color("AccentColorDark")

var body: some View {


VStack(spacing: 20) {
Text("DatePicker").font(.largeTitle)
Text("From Date or Time")
.font(.title).foregroundColor(.gray)
Text("You can set a starting date or time with the date
picker")
.frame(minWidth: 0, maxWidth: .infinity)
.padding().background(mainColor)
.foregroundColor(mainColor.colorInvert() as? Color)
.font(.title)
Image("banff")
.resizable()
.aspectRatio(contentMode: .fit)
Text("Arrival Date")
DatePicker("", selection: $arrivalDate, in: fromToday...,
displayedComponents: .date)
.offset(x: -50, y: 0)
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(mainColor))
.padding(.horizontal)
}
}

The three dots after “fromToday” is called a “range operator” and is used to
express a range of values between a minimum and maximum value. When used
with only one value, it is known as a “one-sided range” that extends in one
direction until the range ends.

www.bigmountainstudio.com 75 Free Sample (Full Version Here)


Control Views
DatePicker - To a Specific Date or Time
@State private var arrivalDate = Date()
let mainColor = Color("LightPinkColor")

var body: some View {


VStack(spacing: 20) {
Text("DatePicker")
.font(.largeTitle)

Text("To Date or Time")


.font(.title).foregroundColor(.gray)

Text("You can set a maximum date or time that you cannot go


past with the date picker")
...

Image("baby")
.resizable()
.aspectRatio(contentMode: .fit)

Text("Date and Time of Birth")

DatePicker("", selection: $arrivalDate, in: ...Date(),


displayedComponents: [.date, .hourAndMinute])
.offset(x: -35, y: 0)
.background(
RoundedRectangle(cornerRadius: 20)
.stroke(mainColor, lineWidth: 2)
)
.padding(.horizontal)
}
}

www.bigmountainstudio.com 76 Free Sample (Full Version Here)


Control Views
DatePicker - With Minimum and Maximum Date Range
@State private var nextFullMoonDate = Date()

var withinNext30Days: ClosedRange<Date> {


let today = Calendar.current.date(byAdding: .minute, value: -1,
to: Date())!
let next30Days = Calendar.current.date(byAdding: .day, value: 30,
to: Date())!
return today...next30Days
}

var body: some View {


VStack(spacing: 30) {
Text("DatePicker").font(.largeTitle)
Text("Min and Max Date Range")
.font(.title).foregroundColor(.gray)
HStack {
// Moon images
}
.font(.title).foregroundColor(Color.yellow)
Text("Select date of next full moon").font(.title)
DatePicker("", selection: $nextFullMoonDate,
in: withinNext30Days,
displayedComponents: .date)
.offset(x: -35, y: 0)
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(.yellow))
.padding(.horizontal)
.shadow(radius: 20, y: 20)
Text("(Valid range only in the next 30 days)")
.padding(.vertical)
Spacer()
}
}

www.bigmountainstudio.com 77 Free Sample (Full Version Here)


FORM

The Form view is a great choice when you want to show settings, options, or get some user input. It is easy to

set up and customize as you will see on the following pages.

This is a push-out view.


78
Control Views
Form - Introduction
Form {
Section {
Text("This is a Form!")
.font(.title)
Text("You can put any content in here")
Text("The cells with grow to fit the content")
Text("Remember, it's just views inside of views")
}

Section {
Text("Limitations")
.font(.title)
Text("There are built-in margins that are difficult to get
around. Take a look at the color below so you can
see where the margins are:")
Color.green
}

Section {
Text("Summary")
.font(.title)
Text("Pretty much what you see here is what you get.")
}
}

www.bigmountainstudio.com 79 Free Sample (Full Version Here)


Control Views
Form - Section Headers and Footers
Form {
Section(header: Text("Section Header Text")) {
Text("You can add any view in a section header")
Text("Notice the default foreground color is gray")
}

Section(header: SectionTextAndImage(name: "People", image:


"person.2.square.stack.fill")) {
Text("Here's an example of a section header with image and
text")
}

Section(header: Text(""), footer: Text("Total:


$5,600.00").bold()) {
Text("Here is an example of a section footer")
}
}

struct SectionTextAndImage: View {


var name: String
var image: String

var body: some View {


HStack {
Image(systemName: image).padding(.trailing)
Text(name)
}
.padding()
.font(.title)
.foregroundColor(Color.red)
}
}

www.bigmountainstudio.com 80 Free Sample (Full Version Here)


Control Views
Form - List Row Background
Form {
Section(header: Text("Form").font(.largeTitle)) {
Text("List Row Background")
.font(.title)
.foregroundColor(.gray)

Text("Forms and Lists allow you to set a background view with


a function called \"listRowBackground(view:)\".")

Text("You can call this modifier function on just one row,


like this.")
.listRowBackground(Color.pink)
}

Section(header: Text("Whole Section")


.font(.title).foregroundColor(.gray)) {
Text("Or you can set a view or color for a whole
section.")

Image(systemName: "smiley.fill")
.frame(minWidth: 0, maxWidth: .infinity, alignment:
.center)
.font(.largeTitle)

Text("Note, even though the color is set on the Section,


the color of the section header is not affected.")
}
.foregroundColor(.white)
.listRowBackground(Color.blue)
}

www.bigmountainstudio.com 81 Free Sample (Full Version Here)


Control Views
Form - Background Images
Form {
Section(header: Text("Form")
.font(.largeTitle).bold()
.foregroundColor(.white)) {
Text("List Row Background")
.font(.title)
.foregroundColor(.gray)
Text("Images work a little differently as you can see
here.")
Text("The image is actually set on a row on the second
section.")
}

Section(header: Text("Images")
.font(.title)
.foregroundColor(.white)) {
Text("An image is set as a background for the row below.
This works fine for rows, but when you use an image on the section
level, it is repeated for all rows.")
Text("The image is set on THIS row, but it extends past
the bounds. It also hides the row below this one and goes under the
previous rows.")
.foregroundColor(.white)
.foregroundColor(.white)
.listRowBackground(Image("water")
.clipped()
.blur(radius: 3))
Text("This row cannot be seen.")
}
}

www.bigmountainstudio.com 82 Free Sample (Full Version Here)


Control Views
Form - Row Inset
Form {
Section(header: Text("Form").font(.largeTitle)) {
Text("List Row Inset")
.font(.title)
.foregroundColor(.gray)
Text("Using this color, you can see where the default margins
are:")
Color.purple
Text("You can use the List Row Inset modifier to adjust the
margins:")
Color.purple
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0,
trailing: 0))
}
Section(header: Text("Row Inset
Uses").font(.title).foregroundColor(.gray)) {
Text("Other possible uses could be for indenting")
Text("Indent Level 1")
.listRowInsets(EdgeInsets(top: 0, leading: 40, bottom: 0,
trailing: 0))
Text("Indent Level 2")
.listRowInsets(EdgeInsets(top: 0, leading: 80, bottom: 0,
trailing: 0))
Text("Or Vertical Alignment")
Text("Top")
.listRowInsets(EdgeInsets(top: -20, leading: 40, bottom:
0, trailing: 0))
Text("Bottom")
.listRowInsets(EdgeInsets(top: 20, leading: 40, bottom: 0,
trailing: 0))
}
}

www.bigmountainstudio.com 83 Free Sample (Full Version Here)


Control Views
Form - With Controls
Form {
Section(header: SectionHeader(name: "Controls in a Form")) {
Text("This will give you an idea of how different controls are
rendered in a Form.")
}

Section {
Button(action: {}) { Text("Button") }
Toggle(isOn: $isOn) { Text("Toggle") }
Stepper(onIncrement: {}, onDecrement: {}) { Text("Stepper") }
TextField("", text: $textFieldData)
.textFieldStyle(RoundedBorderTextFieldStyle())
Image(systemName: "leaf.arrow.circlepath").font(.title)
Circle()
Text("Notice shapes are centered ☝ ")
TextField("", text: $textFieldData)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}

struct SectionHeader: View {


var name: String

var body: some View {


Text(name)
.font(.largeTitle)
.foregroundColor(Color.gray)
}
}

www.bigmountainstudio.com 84 Free Sample (Full Version Here)


LIST

Using a List view is the most efficient way of displaying vertically scrolling data. You can display data in a

ScrollView, as you will see later on, but it will not be as efficient in terms of memory or performance as the List

view.
85
Control Views
List - Introduction
var data = ["This is the simplest List", "Evans", "Lemuel James
Guerrero", "Mark", "Durtschi", "Chase", "Adam", "Rodrigo", "Notice the
automatic wrapping when the content is larger"]

var body: some View {


List(data, id: \.self) { datum in
Text(datum)
}
.font(.largeTitle)
}

.id parameter

You use this parameter to tell the List how it can uniquely identify each row by

which value. The List needs to know this so it can compare rows by this value to

perform different operations like reordering and deleting rows for us.

In this scenario, we are using “self” to say, “Just use the value of the string itself

to uniquely identify each row.”

www.bigmountainstudio.com 86 Free Sample (Full Version Here)


Control Views
List - Grouped List Style
var data = ["Grouped List Style", "This list is using the group list
style", "Evans", "Lemuel James Guerrero", "Mark", "Durtschi", "Chase",
"Adam", "Rodrigo"]

var body: some View {


List(data, id: \.self) { datum in
HStack {
Text(datum)
.font(datum == "Grouped List Style"
? Font.largeTitle : Font.body)

Spacer()

Image(systemName: datum == "Mark"


? "circle.fill" : "circle")
.foregroundColor(Color.green)
}
}
.listStyle(GroupedListStyle())
}

Notice that with the grouped list style that the rows don’t continue past the last
one.

One more thing to note is that inside the List you see an HStack used for the
row. This is optional. By default, the list will implicitly use an HStack for the
row if one is not specified.

www.bigmountainstudio.com 87 Free Sample (Full Version Here)


Control Views
List - Custom Rows
var data = ["Custom Rows!", "Evans", "Lemuel James Guerrero", "Mark",
"Durtschi", "Chase", "Adam", "Rodrigo"]

var body: some View {


List(data, id: \.self) { datum in
CustomRow(content: datum)
}
}

struct CustomRow: View {


var content: String

var body: some View {


HStack {
Image(systemName: "person.circle.fill")
Text(content)
Spacer()
}
.foregroundColor(content == "Custom Rows!" ? Color.green :
Color.primary)
.font(.title)
.padding([.top, .bottom])
}
}

www.bigmountainstudio.com 88 Free Sample (Full Version Here)


Control Views
List - Delete Rows
@State var data = ["Swipe to Delete", "Practice Coding", "Grocery
shopping", "Get tickets", "Clean house", "Do laundry", "Cook dinner",
"Paint room"]

var body: some View {


List {
Section(header: Text("To Do").padding()) {
ForEach(data, id: \.self) { datum in
Text(datum).font(Font.system(size: 24)).padding()
}
.onDelete(perform: delete)
}
}
}

func delete(at offsets: IndexSet) {


if let first = offsets.first {
data.remove(at: first)
}
}

onDelete, onMove, onInsert

These three functions only work on views that implement the

DynamicViewContent protocol. Currently, the only view that conforms to the

DynamicViewContent protocol is the ForEach view. So these functions are only

available on a ForEach view, not a List view.

www.bigmountainstudio.com 89 Free Sample (Full Version Here)


Control Views
List - Move Rows
@State var data = ["Hit the Edit button to reorder", "Practice
Coding", "Grocery shopping", "Get tickets", "Clean house", "Do
laundry", "Cook dinner", "Paint room"]

var body: some View {


NavigationView {
List {
ForEach(data, id: \.self) { datum in
Text(datum).font(Font.system(size: 24)).padding()
}
.onMove(perform: moveRow)
}
.navigationBarTitle(Text("To Do"))
.navigationBarItems(trailing: EditButton())
}
}

func moveRow(from indexes: IndexSet, to destination: Int) {


if let first = indexes.first {
data.insert(data.remove(at: first), at: destination)
}
}

EditButton()

This is a built-in function that returns a view (Button) that will automatically

toggle edit mode on the List. Its text says “Edit” and then when tapped you will

see the move handles appear on the rows and the button text says “Done”.

www.bigmountainstudio.com 90 Free Sample (Full Version Here)


Control Views
List - Row Background
List {
Section(header:
VStack {
Text("To Do").font(.title)
HStack {
TextField("new todo", text: $newToDo)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
self.data.append(Todo(action: self.newToDo))
self.newToDo = ""
}) {
Image(systemName: "plus.circle.fill").font(.title)
}
}
}
.padding(EdgeInsets(top: 50, leading: 16, bottom: 16,
trailing: 16))
) {
ForEach(data) { datum in
Text(datum.action).font(Font.system(size: 24))
.listRowBackground(datum.due == "Today"
? Color.pink : Color.clear)
.padding()
}
}
}.edgesIgnoringSafeArea(.vertical)

Notice the .listRowBackground function is on the view inside the ForEach. You
want to call this function on whatever view will be inside the row, not on the List
itself.

www.bigmountainstudio.com 91 Free Sample (Full Version Here)


Control Views
List - Row Inset
GeometryReader { gr in
List {
Section(header:
VStack {
Text("To Do").font(.title).foregroundColor(.white)
HStack {
TextField("new todo", text: self.$newToDo)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
self.data.append(Todo(action: self.newToDo))
self.newToDo = ""
}) {
Image(systemName: "plus.circle.fill")
.font(.title).foregroundColor(.white)
}
}
}
.padding(EdgeInsets(top: 50, leading: 26,
bottom: 16, trailing: 16))
.background(Color.blue.colorMultiply(.gray))
.frame(width: gr.size.width)
) {
ForEach(self.data) { datum in
Text(datum.action).font(Font.system(size: 24))
.padding()
.listRowInsets(
EdgeInsets(top: 0,
leading: datum.isIndented ? 60 : 20,
bottom: 0, trailing: 0))
}
}
}.edgesIgnoringSafeArea(.vertical)
}

www.bigmountainstudio.com 92 Free Sample (Full Version Here)


Control Views
ListList
- Headers
- Custom
andRows
Footers
List {
Section(header: Header(), footer: Text("7 People on Staff")) {
// ForEach will handle the row creation
ForEach(data, id: \.self) { datum in
NameRow(content: datum)
}
}
}.edgesIgnoringSafeArea(.top)

struct Header: View {


var body: some View {
Text("STAFF")
.font(Font.system(size: 120, design: .serif))
.foregroundColor(Color.red)
.background(Image("mountains"))
.frame(height: 200)
.listRowInsets(EdgeInsets(top: 0, leading: 40, bottom: 0,
trailing: 0))
}
}
In order to set a foregroundColor on
struct NameRow: View {
an image (“Pinetree”), that image has
var content: String
to be set to render as a template
var body: some View {
image.
HStack {
Image("PineTree")
.foregroundColor(Color(red: 0.203,
green: 0.443, blue: 0.158))
Text(content)
Spacer()
}.padding()
}
}

www.bigmountainstudio.com 93 Free Sample (Full Version Here)


Control Views
List - Removing Separator Lines After Rows
var data = ["Evans", "Lemuel James Guerrero", "Mark"]

var body: some View {


List {
Section(header: Header(), footer:
// Use a Footer to remove lines after rows
Color.primary
.colorInvert()
.listRowInsets(EdgeInsets(top: 0, leading: -8, bottom:
0, trailing: -8))
) {
ForEach(data, id: \.self) { datum in
NameRow(content: datum)
}
}
}
.edgesIgnoringSafeArea(.top)
}

You may notice from previous examples that the separator lines will continue
past the last row. Separator lines will not continue past a section footer. So the
trick here is to add an “invisible” footer.

In this example, we add a color view and adjust the insets to push out the view
horizontally. Without the edge insets, you will see some of the default gray
footer color.

Color.primary is black in light mode. Using the colorInvert() modifier will make it
white. Use this strategy so the footer color blends in using dark mode as well.

www.bigmountainstudio.com 94 Free Sample (Full Version Here)


NAVIGATIONVIEW

The NavigationView is a little different in that it will fill the whole screen when used. You will never have to

specify its size. But there are some ways you can customize it which you will see in the following pages.

This is a push-out view.


95
Control Views
NavigationView - Introduction
NavigationView {
ZStack {
Color("Theme3BackgroundColor")

VStack(spacing: 25) {
Group {
Image(systemName: "globe").font(.largeTitle)
Text("NavigationView").font(.title)
}
.foregroundColor(Color("Theme3BackgroundColor"))
.colorInvert()
Text("Introduction")
.font(.title)
.foregroundColor(.gray)

Group {
Text("Having a NavigationView will show nothing unless
you also include a ")
+ Text("navigationBarTitle").bold()
}
...

Spacer()

}.padding(.top, 25)
}
.navigationBarTitle(Text("Navigation Views"))
.edgesIgnoringSafeArea(.bottom)
}

The navigationBarTitle goes INSIDE the NavigationView, not on it. Notice the
default style of the title is large.

www.bigmountainstudio.com 96 Free Sample (Full Version Here)


Control Views
NavigationView - Display Mode
NavigationView {
ZStack {
Color("Theme3BackgroundColor")
VStack(spacing: 25) {
Group {
Image(systemName: "globe")
.font(.largeTitle)
Text("NavigationView")
.font(.title)
}
.foregroundColor(Color("Theme3BackgroundColor"))
.colorInvert()
Text("Display Mode")
.font(.title)
.foregroundColor(.gray)
Text("When you create a navigation bar title, you can
specify if you want it large or small (inline) or just automatic.")
...

Spacer()
}
.padding(.top, 25)
}
.navigationBarTitle(Text("Navigation Views"),
displayMode: .inline)
.edgesIgnoringSafeArea(.bottom)
}

www.bigmountainstudio.com 97 Free Sample (Full Version Here)


Control Views
NavigationView - NavigationBarItems: Example Views
In the next example, I will be showing how struct Notifications: View {
@Binding var dismissFlag: Bool
you can add navigation bar items. Each
navigation bar item will do something var body: some View {
VStack {
different. One will show another view
Text("This is the Notifications screen")
(Notifications) and the other will show an .font(.title)
action sheet.
Spacer()

On this page, you have the code for the Button("Close") {


Notifications view and the action sheet self.dismissFlag = false
}
which will be referenced on the following
}
page. .padding(.top, 40)
}
}

class ActionSheets {
static func getTwoChoiceActionSheet() -> ActionSheet {
ActionSheet(title: Text("Actions"),
buttons: [.default(Text("Action 1")),
.destructive(Text("Action 2"))]
)
}
}

www.bigmountainstudio.com 98 Free Sample (Full Version Here)


Control Views
NavigationView - NavigationBarItems
@State var isShowingNotifications = false
@State var isShowingActions = false

var body: some View {


NavigationView {
VStack(spacing: 25) {
Image(systemName: "globe").font(.largeTitle)
Text("NavigationView").font(.title)
Text("Navigation Bar Items")
.font(.title).foregroundColor(.gray)
Text("You can add navigation bar buttons to the leading or
trailing (or both) sides of a navigation bar")
...
Spacer()
}.padding(.top, 25)
.navigationBarTitle(Text("Navigation Bar Items"),
displayMode: .inline)
.navigationBarItems(
leading: Button(action: {
self.isShowingNotifications.toggle() }) {
Image(systemName: "bell.fill")
}
, trailing: Button("Actions", action: {
self.isShowingActions.toggle() })
).accentColor(.pink)
}
.sheet(isPresented: $isShowingNotifications) {
Notifications(dismissFlag: self.$isShowingNotifications)
}
.actionSheet(isPresented: $isShowingActions, content: {
ActionSheets.getTwoChoiceActionSheet()
})
}

www.bigmountainstudio.com 99 Free Sample (Full Version Here)


PICKER

To get or set a value for the Picker, you need to bind it to a variable. This variable is then passed into the

Picker’s initializer. Then, all you need to do is change this bound variable’s value to select the row you want to
show in the Picker. Or read the bound variable’s value to see which row is currently selected. One thing to note

is that this variable is actually bound to the Picker row’s tag property which you will see in the following pages.

100
Control Views
Picker - Introduction
@State private var favoriteState = 1
@State private var yourName = "Mark"
...
VStack {
Text("Picker")
.font(.largeTitle)
Text("Introduction").font(.title).foregroundColor(.gray)
Text("You associate a variable with the picker rows' tag values")
...

Picker(selection: $favoriteState, label: Text("Favorite State")) {


Text("California").tag(0)
Text("Utah").tag(1)
Text("Vermont").tag(2)
}.padding(.horizontal)

Text("Tag values can be String, Int or Bool.")


...

Picker(selection: $yourName, label: Text("Your name")) {


Text("Paul").tag("Paul")
Text("Chris").tag("Chris")
Text("Mark").tag("Mark")
Text("Scott").tag("Scott")
Text("Meng").tag("Meng")
}.padding(.horizontal)
}

Like the DatePicker, the picker gets indented whether you have text for the label
param or not. So you will have to implement an offset strategy to get around
that. I’m hoping this changes in later versions.

www.bigmountainstudio.com 101 Free Sample (Full Version Here)


Control Views
Picker - Customized
@State private var favoriteState = 1
@State private var youTuberName = "Mark"
...
VStack(spacing: 8) {
Text("Your Favorite State:")
Picker(selection: $favoriteState, label: Text("")) {
Text("California").tag(0)
Text("Utah").tag(1)
Text("Vermont").tag(2)
}
.offset(x: -35)
.foregroundColor(Color.white)
.background(Color.red.colorMultiply(.blue))
.cornerRadius(15)
.padding(.horizontal)
.shadow(radius: 5)
}

VStack(spacing: 8) {
Text("Who do you want to watch today?")
Picker(selection: $youTuberName, label: Text("")) {
Text("Paul").tag("Paul")
Text("Chris").tag("Chris")
Text("Mark").tag("Mark")
Text("Scott").tag("Scott")
Text("Meng").tag("Meng")
}
.offset(x: -35)
.background(RoundedRectangle(cornerRadius: 15)
.stroke(Color.blue, lineWidth: 1))
.padding(.horizontal)
}

www.bigmountainstudio.com 102 Free Sample (Full Version Here)


Control Views
Picker - Custom Rows
VStack(spacing: 20) {
Text("Picker").font(.largeTitle)
Text("Rows with Images").font(.title).foregroundColor(.gray)
Text("Who do you want to watch today?")
.padding()

Picker(selection: $youTuberName, label: Text("")) {


Row(name: "Sean")
Row(name: "Chris")
Row(name: "Mark")
Row(name: "Scott")
Row(name: "Paul")
}
.offset(x: -35)
.foregroundColor(Color.white)
.background(Color.red.colorMultiply(.blue))
.cornerRadius(15)
.shadow(radius: 20)
.padding()
}

struct Row : View {


var name: String
var body: some View {
return HStack {
Image(systemName: "person.fill")
.padding(.trailing)
.foregroundColor(Color.red)
Text(name)
}
.tag(name)
}
}

www.bigmountainstudio.com 103 Free Sample (Full Version Here)


Control Views
Picker - Binding Rows to Data
@State private var youTuberName = "Mark"
var youTubers = ["Sean", "Chris", "Mark", "Scott", "Paul"]
...
VStack(spacing: 20) {
Text("Picker")
.font(.largeTitle)
Text("Binding to Data")
.font(.title).foregroundColor(.gray)
Text("Use a ForEach with your Picker view to populate it with
data.")
...

Text("Who do you want to watch today?")


.font(.title)
.padding(.bottom, 0)

Picker(selection: $youTuberName, label: Text("")) {


ForEach(youTubers, id: \.self) { name in
Row(name: name)
}
}
.offset(x: -50)
}

struct Row : View {


var name: String
var body: some View {
HStack {
Image(systemName: "person.fill")
Text(name)
}.tag(name)
}
}

www.bigmountainstudio.com 104 Free Sample (Full Version Here)


SCROLLVIEW

A ScrollView is like a container for child views. When the child views within the ScrollView go outside the frame,

the user can scroll to bring the child views that are outside the frame into view.

A ScrollView is a push-out view in the scroll direction you specify. You can set the direction of a ScrollView to be

vertical or horizontal.

105
Control Views
ScrollView - Introduction
@State private var names = ["Scott", "Mark", "Chris", "Sean", "Rod",
"Meng", "Natasha", "Chase", "Evans", "Paul", "Durtschi", "Max"]
...
NavigationView {
GeometryReader { gr in
ScrollView {
ForEach(self.names, id: \.self) { name in
NavigationLink(destination: DetailView(name: name)) {
HStack {
Text(name).foregroundColor(.primary)
Image(systemName: "checkmark.seal.fill")
.foregroundColor(.green)
Spacer()
Image(systemName: "chevron.right.circle.fill")
}
.font(.system(size: 24, design: .rounded))
.padding().background(Color.white)
.cornerRadius(8)
.shadow(radius: 1, y: 1)
}
} // Set the width on the ForEach (it's a View)
.frame(width: gr.size.width - 32)
.accentColor(Color.pink)
.padding()
}
.navigationBarTitle(Text("Cool People"))
}
}

A Scrollview with a ForEach view is similar to a List. But be warned, the rows are
not reusable. It is best to limit the number of rows for memory and
performance considerations.

www.bigmountainstudio.com 106 Free Sample (Full Version Here)


Control Views
ScrollView - Scroll Horizontally
struct Scrollview_Horizontal : View {
var items = [Color.green, Color.blue, Color.purple, Color.pink,
Color.yellow, Color.orange]

var body: some View {


GeometryReader { gr in
VStack(spacing: 20) {
Text("ScrollView")
.font(.largeTitle)
Text("Scroll Horizontally")
.font(.title)
.foregroundColor(.gray)
Text("Just set the ScrollView's axis to horizontal and
if the contents go horizontally beyond the frame, scrolling will be
enabled.")
...
ScrollView(Axis.Set.horizontal, showsIndicators: true)
{
HStack {
ForEach(self.items, id: \.self) { item in
RoundedRectangle(cornerRadius: 15)
.fill(item)
.frame(width: gr.size.width - 60,
height: 200)
}
}
}
.padding(.horizontal)
Spacer()
}
}
}
}

www.bigmountainstudio.com 107 Free Sample (Full Version Here)


SECUREFIELD

In order to get or set the text in a SecureField, you need to bind it to a variable. This variable is passed into the

SecureField’s initializer. Then, all you need to do is change this bound variable’s text to change what is in the

SecureField. Or read the bound variable’s value to see what text is currently in the SecureField.

This is a pull-in control.


108
Control Views
SecureField - Introduction
@State private var userName = ""
@State private var password = ""
...
VStack(spacing: 20) {
Image("Logo")
.padding(.bottom, 150)

Text("SecureField")
.font(.largeTitle)

Text("Introduction")
.font(.title)
.foregroundColor(.gray)

Text("SecureFields, like TextFields, need to bind to a local


variable.")
...

TextField("user name", text: $userName)


.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()

SecureField("password", text: $password)


.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()

Spacer()
}

www.bigmountainstudio.com 109 Free Sample (Full Version Here)


Control Views
SecureField - Customizations
@State private var userName = ""
@State private var password = ""

...

Text("Use a ZStack to put a RoundedRectangle behind a SecureField with


a plain textfieldStyle.")
...

ZStack{
RoundedRectangle(cornerRadius: 8)
.foregroundColor(.purple)
TextField("user name", text: $userName)
.foregroundColor(Color.white)
.padding(.horizontal)
}
.frame(height: 40)
.padding(.horizontal)

Text("Or overlay the SecureField on top of another view or shape.")


...

RoundedRectangle(cornerRadius: 8)
.foregroundColor(.purple)
.overlay(
SecureField("password", text: $password)
.foregroundColor(Color.white)
.padding(.horizontal)
)
.frame(height: 40)
.padding(.horizontal)

www.bigmountainstudio.com 110 Free Sample (Full Version Here)


Control Views
SecureField - Customization Layers
@State private var userName = ""
@State private var password = ""
...
VStack(spacing: 20) {
Text("SecureField")
.font(.largeTitle)
Text("Customization Layers")
.font(.title)
.foregroundColor(.gray)
Text("You can also add a background to the SecureField. It's all
the same idea adjust the layers.")
...

SecureField("password", text: $password)


.foregroundColor(Color.white)
.frame(height: 40)
.padding(.horizontal)
.background(
Capsule()
.foregroundColor(.purple)
)
.padding(.horizontal)

Image("SecureFieldLayers")

Text("The highlighted layer in that image is the actual text field


layer of the view.")
.font(.title)
.padding(.horizontal)
}

www.bigmountainstudio.com 111 Free Sample (Full Version Here)


SEGMENTED CONTROL

Segmented controls are now Picker controls with a different picker style set. In order to get or set the selected
segment, you need to bind it to a variable. This variable is passed into the segmented control’s (Picker’s)
initializer. Then, all you need to do is change this bound variable’s value to change the selected segment. Or
read the bound variable’s value to see which segment is currently selected.

This is a pull-in view.


112
Control Views
Segmented Control (Picker) - Introduction
@State private var dayNight = "day"
@State private var tab = 1

...

VStack(spacing: 20) {
Text("Segmented Control (Picker)").font(.largeTitle)
Text("Introduction")
.font(.title).foregroundColor(.gray)
Text("Associate the segmented control with an @State variable that
will control which segment is selected. The state variable will match
each segment's tag value.")
...

Picker("", selection: $dayNight) {


Text("Day").tag("day")
Text("Night").tag("night")
}
.pickerStyle(SegmentedPickerStyle())
.padding()

Text("With Images:")

Picker("", selection: $tab) {


Image(systemName: "sun.min").tag(0)
Image(systemName: "moon").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding()
}

www.bigmountainstudio.com 113 Free Sample (Full Version Here)


Control Views
Segmented Control (Picker) - No Segment Selected
@State private var selection = 0

...

VStack(spacing: 20) {
Text("Segmented Control (Picker)").font(.largeTitle)
Text("No Segment Selected")
.font(.title).foregroundColor(.gray)
Text("This segmented control will have nothing selected because
the default state variable does not match any of the segment tag
values.")
...

Text("How many meals do you eat?")


.foregroundColor(.gray)
.font(.title)

Picker("", selection: $selection) {


Text("One").tag(1)
Text("Two").tag(2)
Text("Three").tag(3)
Text("More").tag(4)
}
.pickerStyle(SegmentedPickerStyle())
.background(RoundedRectangle(cornerRadius: 8)
.stroke(Color.red, lineWidth: selection == 0 ? 1 : 0))
.padding()

Text("The red outline will go away once a selection is made.")


...
}

www.bigmountainstudio.com 114 Free Sample (Full Version Here)


Control Views
Segmented Control (Picker) - Colors
@State private var selection = 2
...
VStack(spacing: 20) {
Text("Segmented Control (Picker)").font(.largeTitle)
Text("Colors")
.font(.title).foregroundColor(.gray)
Text("You can change the color of segmented controls by using the
background modifier.")
...

Text("When you add a color, notice the corners:")


Picker("", selection: $selection) {
Text("One").tag(1)
Text("Two").tag(2)
Text("Three").tag(3)
}
.pickerStyle(SegmentedPickerStyle())
.background(Color.yellow)
.padding(.horizontal)

Text("Adding a corner radius should handle it:")


Picker("", selection: $selection) {
Text("One").tag(1)
Text("Two").tag(2)
Text("Three").tag(3)
}
.pickerStyle(SegmentedPickerStyle())
.background(Color.yellow)
.cornerRadius(8)
.padding(.horizontal)
}

www.bigmountainstudio.com 115 Free Sample (Full Version Here)


SLIDER
When using a Slider view, the default range of values is 0.0 to 1.0. You bind the Slider to a state variable,
usually a number type, like an Int. But it doesn’t have to be a number type. It can be any type that conforms to
the Stridable protocol. (“Stride” means to “take steps in a direction; usually long steps”.) A type that conforms
to Stridable (such as an Int) means it has values that are continuous and can be stepped through and
measured. (“Step through”, “Stride”, I think you see the connection now.)

You use the bound variable to set or get the value the Slider’s thumb (circle) is currently at.

This is a pull-in view.


116
Control Views
Slider - Introduction
@State private var sliderValue = 0.5
@State private var age = 18.0

let ageFormatter: NumberFormatter = {


let numFormatter = NumberFormatter()
numFormatter.numberStyle = .spellOut
return numFormatter
}()
...
Text("Associate the Slider with an @State variable that will control
where the thumb (circle) will be on the slider's track.")
...
Text("Default min value is 0.0 and max value is 1.0")
...
Group {
Text("Value is: ") +
Text("\(sliderValue)").foregroundColor(.green)
}
.font(.title)

Text("You can also set your own min and max value.")
....

Text("What is your age?").font(.title)


Slider(value: $age, in: 1...100, step: 1)
.padding(.horizontal)

Group {
Text("Age is: ") +
Text("\(ageFormatter.string(from: NSNumber(value: age))!)")
.foregroundColor(.pink)
}
.font(.title)

www.bigmountainstudio.com 117 Free Sample (Full Version Here)


Control Views
Slider - Customization
@State private var sliderValue = 0.5
...
Text("At the time of this writing, there isn't a way to color the
thumb. But we can change the background color and apply some other
modifiers.")
...
Slider(value: $sliderValue)
.padding(.horizontal, 10)
.background(Color.orange)
.cornerRadius(.infinity) // Rounded ends
.shadow(color: .gray, radius: 2)
.padding(.horizontal)

Text("Use the accentColor modifier to change the color of the track.")


...

Slider(value: $sliderValue)
.padding(.horizontal)
.accentColor(.orange)

Text("Using shapes and outlines.")


...

Slider(value: $sliderValue)
.padding(10)
.background(Capsule().stroke(Color.orange, lineWidth: 2))
.padding(.horizontal)

Slider(value: $sliderValue)
.padding(10)
.background(Capsule().fill(Color.orange))
.accentColor(.black)
.padding(.horizontal)

www.bigmountainstudio.com 118 Free Sample (Full Version Here)


Control Views
Slider - With Images
Text("Combine the slider with images in an HStack, VStack or both!")
...

HStack {
Image(systemName: "tortoise")
Slider(value: $sliderValue)
Image(systemName: "hare")
}.foregroundColor(.green).padding()

HStack {
Image(systemName: "speaker.fill")
Slider(value: $sliderValue)
Image(systemName: "speaker.3.fill")
}
.foregroundColor(.accentColor)
.padding()

VStack {
Slider(value: $sliderValue)
.accentColor(.orange)
HStack {
Image(systemName: "circle")
Spacer()
Image(systemName: "circle.righthalf.fill")
Spacer()
Image(systemName: "circle.fill")
}
.foregroundColor(.orange)
.padding(.top, 8)
}.padding()

www.bigmountainstudio.com 119 Free Sample (Full Version Here)


STEPPER

When using a Stepper view, you bind it to a state variable, usually a number. But it doesn’t have to be a
number type. It can be any type that conforms to the Stridable protocol. (“Stride” means to “take steps in a
direction; usually long steps”.) A type that conforms to Stridable means it has values that are continuous and
can be stepped through and measured. (“Step through”, “Stride”, I think you see the connection now.)

You use the bound variable to set or get the value it is currently at.

This is a horizontal push-out view. Vertically it is pull-in.


120
Control Views
Stepper - Introduction
@State private var stepperValue = 1
@State private var values = [0, 1]
...
VStack(spacing: 20) {
Text("Stepper")
.font(.largeTitle)
Text("Introduction")
.font(.title).foregroundColor(.gray)
Text("The Stepper can be bound to a variable like this:")
...

Stepper(value: $stepperValue) {
Text("Bound Stepper: \(stepperValue)")
}.padding(.horizontal)
Divider()
Image(systemName: "bolt.fill")
.font(.title).foregroundColor(.yellow)
Text("Or you can run code on the increment and decrement events:")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.blue).foregroundColor(Color.white)
.font(.title)
Stepper(onIncrement: {self.values.append(self.values.count)},
onDecrement: {self.values.removeLast()}) {
Text("onIncrement and onDecrement")
}.padding(.horizontal)
HStack {
ForEach(values, id: \.self) { value in
Image(systemName: "\(value).circle.fill")
}
}.font(.title).foregroundColor(.green)
}

www.bigmountainstudio.com 121 Free Sample (Full Version Here)


Control Views
Stepper - Range
@State private var stars = 0

VStack(spacing: 20) {
Text("Stepper")
.font(.largeTitle)
.padding()
Text("Range of Values")
.font(.title)
.foregroundColor(.gray)
Text("You can set a range for the stepper too. In this example,
the range is between one and five.")
...

Stepper(value: $stars, in: 1...5) {


Text("Rating")
}.padding(.horizontal)

HStack {
ForEach(1...stars, id: \.self) { star in
Image(systemName: "star.fill")
}
}
.font(.title)
.foregroundColor(.yellow)
}

When the Stepper reaches the range limits, the corresponding plus or minus
button will appear as disabled. In this screenshot, notice the plus button is
disabled.

www.bigmountainstudio.com 122 Free Sample (Full Version Here)


Control Views
Stepper - Customization
@State private var contrast = 50
...
Text("A foreground and background color can be set.")
...

Stepper(onIncrement: {}, onDecrement: {}) {


Text("Custom Stepper")
.font(.title)
.padding(.vertical)
}
.padding(.horizontal)
.background(Color.blue)
.foregroundColor(.white)

Text("Notice the minus and plus buttons are not affected. The
platforms determine how this will be shown.")
...

Text("You can add images too.")


.frame(maxWidth: .infinity).padding()
.background(Color.blue).foregroundColor(Color.white)
.font(.title).layoutPriority(1)

Stepper(value: $contrast, in: 0...100) {


// SwiftUI implicitly uses an HStack here
Image(systemName: "circle.lefthalf.fill")
Text(" \(contrast)/100")
}
.font(.title)
.padding(.horizontal)
.foregroundColor(.blue)

www.bigmountainstudio.com 123 Free Sample (Full Version Here)


Control Views
Stepper - Colors
Text("You can add color to the Stepper using the color multiply
modifier. In light mode it is very slight. In dark mode it is more
pronounced.")
...

Stepper(value: $contrast, in: 0...100) {


Text("Default Color")
}
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.gray)")
}.colorMultiply(.gray)
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.red)")
}.colorMultiply(.red)
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.green)")
}.colorMultiply(.green)
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.blue)")
}.colorMultiply(.blue)
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.orange)")
}.colorMultiply(.orange)
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.yellow)")
}.colorMultiply(.yellow)
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.pink)")
}.colorMultiply(.pink)
Stepper(value: $contrast, in: 0...100) {
Text(".colorMultiply(.purple)")
}.colorMultiply(.purple)

www.bigmountainstudio.com 124 Free Sample (Full Version Here)


TABVIEW

The TabView acts like a container for child views within it. These child views are individual screens. It provides

tab buttons (TabItems) that allows the user to switch between these child views.

This is a push-out view.


125
Control Views
TabView - Introduction
TabView {
// First Screen
VStack(spacing: 20) {
Text("TabView")
.font(.largeTitle)
Text("Introduction")
.font(.title)
.foregroundColor(.gray)
Text("The TabView view can hold multiple views, one for each
tab.")
...

Text("At the end of a view that represents a screen, you add


.tabItem to show a button that allows navigation to that view.")
.font(.title)
.padding()
}.tabItem {
Text("Tab 1")
}

// Second Screen
Text("This view represents the Second Screen.")
.tabItem {
Text("Tab 2")
}
}

www.bigmountainstudio.com 126 Free Sample (Full Version Here)


Control Views
TabView - TabItems
struct TabView_Tabs : View {
var body: some View {
TabView {
TabOne().tabItem {
Text("Tab Text")
}
Text("Phone Calls").tabItem {
Image(systemName: "phone")
}
Text("Outgoing Phone Calls").tabItem {
Image(systemName: "phone.arrow.up.right")
Text("Outgoing")
}
}
}
}

// A View within the TabView


struct TabOne: View {
var body: some View {
VStack(spacing: 20) {
Text("TabView")
.font(.largeTitle)
Text("TabItems")
.font(.title).foregroundColor(.gray)
Text("TabItems can accept Text, Image or both. Notice the
order of Text and Image does not matter for the tabItem.")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.red).foregroundColor(Color.white)
.font(.title)
}
}
}

www.bigmountainstudio.com 127 Free Sample (Full Version Here)


Control Views
TabView - Too Many Tabs
TabView {
Text("Call Screen").tabItem {
Image(systemName: "phone")
Text("Call")
}
Text("Outgoing Phone Calls Screen").tabItem {
Image(systemName: "phone.arrow.up.right")
Text("Outgoing")
}
Text("Incoming Phone Calls Screen").tabItem {
Image(systemName: "phone.arrow.down.left")
Text("Incoming")
}
Text("Phone Book Screen").tabItem {
Image(systemName: "book")
Text("Phone Book")
}
Text("History Screen").tabItem {
Image(systemName: "clock")
Text("History")
}
Text("New Phone Number").tabItem {
Image(systemName: "phone.badge.plus")
Text("New")
}
}

When there are too many tabs to fit for the device, the More button is created

where you can find the rest of the tabs listed out.

www.bigmountainstudio.com 128 Free Sample (Full Version Here)


Control Views
TabView - Navigation
@State private var selectedTab = 1 // Set which tab is active
...

TabView(selection: $selectedTab) {
// Tab 1
VStack(spacing: 20) {
Text("TabView").font(.largeTitle)
Text("Navigation")
.font(.title).foregroundColor(.gray)
Text("Add a unique tag value to each screen (view) you want to
programmatically navigate to. You can then bind a variable to the
TabView's selection property and change that variable to navigate.")
...

Button("Go to Tab 3") {


self.selectedTab = 3
}
}.tabItem {
Image(systemName: "star.fill")
}.tag(1)

// Tab 2
Text("Second Screen")
.tabItem {
Image(systemName: "moon.fill")
}.tag(2)

// Tab 3
Text("Third Screen")
.tabItem {
Image(systemName: "sun.min.fill")
}.tag(3)
}

www.bigmountainstudio.com 129 Free Sample (Full Version Here)


Control Views
TabView - Colors
TabView {
// Tab 1
ZStack {
Color.blue.colorMultiply(.red).edgesIgnoringSafeArea(.top)
VStack(spacing: 20) {
Text("TabView")
.font(.largeTitle).foregroundColor(.white)
Text("TabItem Colors")
.font(.title).foregroundColor(.gray)
Text("Set the color of the active tab item by setting the
accent color for the TabView.")
...
}
}.tabItem {
Image(systemName: "star.fill")
}
// Tab 2
Text("Second Screen").tabItem {
Image(systemName: "moon.fill")
}.foregroundColor(Color.red)
// Tab 3
Text("Third Screen").tabItem {
Image(systemName: "sun.min.fill")
}
}
.edgesIgnoringSafeArea(.top)
.accentColor(.yellow)

Notice that I am setting the foreground color of the second tabItem to red. This
will have no effect on the color of the tab item. The background modifier will
not work either.

www.bigmountainstudio.com 130 Free Sample (Full Version Here)


TEXT

The text view will probably be one of your most-used views. It has many, if not the most, modifiers available to

it.

This is a pull-in view.


131
Control Views
Text - Introduction
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)

Text("Wrapping")
.font(.title)
.foregroundColor(.gray)

Image("LineLimit")

Text("The Text view shows read-only text that can be modified in


many ways. It wraps automatically. If you want to limit the text
wrapping, add .lineLimit(<number of lines here>)")
...

Text("Here, I am limiting the text to just one line.")


.lineLimit(1)
.font(.title)
.padding(.horizontal)
}

www.bigmountainstudio.com 132 Free Sample (Full Version Here)


Control Views
Text - Text Styles
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)

Text("Text Styles")
.font(.title)
.foregroundColor(.gray)

Image("Font")

Text("You can add a TextStyle to the Text view by calling


.font(Font.<Text Style>)")
...

Text("Available Text Styles")


.font(.title)
.foregroundColor(.gray)

Group {
Divider()
Text("Font.largeTitle").font(.largeTitle)
Text("Font.title").font(.title)
Text("Font.headline").font(.headline)
Text("Font.subheadline").font(.subheadline)
Text("Font.body").font(.body)
Text("Font.callout").font(.callout)
Text("Font.caption").font(.caption)
Text("Font.footnote").font(.footnote)
}
}

www.bigmountainstudio.com 133 Free Sample (Full Version Here)


Control Views
Text - Weights
VStack(spacing: 10) {
Text("Text Weights").font(.largeTitle)
Image("FontWeight")
Text("Here are a variety of weights you can apply to the Text
view.")
.font(.title)
.layoutPriority(1)

Group {
Text("Ultralight").fontWeight(.ultraLight)
Text("Thin").fontWeight(.thin)
Text("Light").fontWeight(.light)
Text("Regular").fontWeight(.regular)
Text("Medium").fontWeight(.medium)
Text("Semibold").fontWeight(.semibold)
Text("Bold").fontWeight(.bold)
Text("Heavy").fontWeight(.heavy)
Text("Black").fontWeight(.black)
}

Text("These weights can be combined with Text Styles")


.font(.title)
.layoutPriority(1)

Group {
Text("Ultralight - Title")
.fontWeight(.ultraLight).font(.title)
Text("Thin - Body").fontWeight(.thin).font(.body)
Text("Light - Large Title")
.fontWeight(.light).font(.largeTitle)
Text("Bold - Callout").fontWeight(.bold).font(.callout)
}
}

www.bigmountainstudio.com 134 Free Sample (Full Version Here)


Control Views
Text - FontDesign
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)

Text("Font Design")
.font(.title)
.foregroundColor(.gray)

Text("There are 4 font designs now in iOS. Use Font.system to set


the font design you want.")
...

Text("Default font design")


.font(Font.system(size: 36, design: Font.Design.default))

// You can remove the "Font.Design" of the enum


Text("Here is monospaced")
.font(Font.system(size: 36, design: .monospaced))

Text("And there is rounded")


.font(Font.system(size: 36, design: .rounded))

Text("Finally, we have serif!")


.font(Font.system(size: 36, design: .serif))
}

www.bigmountainstudio.com 135 Free Sample (Full Version Here)


Control Views
Text - Formatting
VStack(alignment: .leading) {
HStack {
Image("Bold")
Text("Bold").bold()
}
HStack {
Image("Italic")
Text("Italic").italic()
}
HStack {
Image("Strikethrough")
Text("Strikethrough").strikethrough()
}
HStack {
Image("Strikethrough")
Text("Strikethrough with color")
.strikethrough(true, color: .orange)
}
HStack {
Image("TextColor")
Text("TextColor").foregroundColor(.red)
}
HStack {
Image("Underline")
Text("Underline").underline()
}
HStack {
Image("Underline")
Text("Underline with color").underline(true, color: .purple)
}
}

www.bigmountainstudio.com 136 Free Sample (Full Version Here)


Control Views
Text - Allows Tightening
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)
Text("Allows Tightening")
.font(.title)
.foregroundColor(.gray)

Image("AllowsTightening")

Text("You might want to tighten up some text that might be too


long.")
...

Text("In the example below, the green has .allowTightening(true)")


Group {
Text("Allows tightening to allow text to fit in one line.")
.foregroundColor(.red)
.allowsTightening(false)
.padding(.horizontal)
Text("Allows tightening to allow text to fit in one line.")
.foregroundColor(.green)
.allowsTightening(true)
.padding(.horizontal)
}.padding(.horizontal)
}

Allows Tightening can be helpful when you see the last word getting truncated.
Applying it may not even fully work depending on just how much space can be
tightened. With the default font, I notice I can get a couple of characters worth
of space to tighten up.

www.bigmountainstudio.com 137 Free Sample (Full Version Here)


Control Views
Text - Minimum Scale Factor
Text(".allowsTightening(true) is being used here:")
...

Text("Sometimes you want to shrink text if long")


.allowsTightening(true)
.font(.title)
.lineLimit(1)

Text("Allows tightening is failing here because the text is too long.


In this case, you can apply a minimum scale factor and specify by how
much you want to allow the text to shrink.")
...

Image("MinimumScaleFactor")
Text(".minimumScaleFactor(0.5) is being used here:")
...

Text("Sometimes you want to shrink text if long")


.font(.title)
.lineLimit(1)
.minimumScaleFactor(0.5)

Text(".minimumScaleFactor takes a fraction from 0 to 1. For example,


0.3 is 30% of the original size of the font that it can shrink. If the
font size is 100, then it can shrink to 30.")

www.bigmountainstudio.com 138 Free Sample (Full Version Here)


Control Views
Text - Line Spacing
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)

Text("Line Spacing")
.font(.title)
.foregroundColor(.gray)

Image("LineSpacing")

Text("With NO Line Spacing")


...

Text("SwiftUI offers a Line Spacing modifier for situations where


you want to increase the space between the lines of text on the
screen.")

Text("With Line Spacing of 10")


...

Text("SwiftUI offers a Line Spacing modifier for situations where


you want to increase the space between the lines of text on the
screen.")
.lineSpacing(10.0)
}

www.bigmountainstudio.com 139 Free Sample (Full Version Here)


Control Views
Text - Alignment
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)

Text("Multiline Text Alignment")


.font(.title)
.foregroundColor(.gray)

Image("MultilineTextAlignment")

Divider()

Text("By default, text will be centered within the screen. But


when it wraps to multiple lines, it will be leading aligned by
default. Use multilineTextAlignment modifier to change this!")
.padding(.horizontal)
.layoutPriority(1)

Text(".multilineTextAlignment(.center)")
...

Text("Have I told you lately how awesome I think you are?")


.multilineTextAlignment(.center)
.padding(.horizontal)

Text(".multilineTextAlignment(.trailing)")
...

Text("You are SUPER awesome for improving your skills by using


this book!")
.multilineTextAlignment(.trailing)
.padding(.horizontal)
}

www.bigmountainstudio.com 140 Free Sample (Full Version Here)


Control Views
Text - Truncation Mode
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)

Text("Truncation Mode")
.font(.title).foregroundColor(.gray)

Image("TruncationMode")

Text("Text gets truncated when it is too long to fit within a


certain space. You can control where text is truncated.")
.padding(.horizontal)

Text("Default: .truncationMode(.tail)")
...
Text("This will be the best day of your life!")
.font(.title)
.padding(.horizontal)

Text(".truncationMode(.middle)")
...
Text("This will be the best day of your life!")
.font(.title)
.truncationMode(.middle)
.padding(.horizontal)

Text(".truncationMode(.head)")
...
Text("This will be the best day of your life!")
.font(.title)
.truncationMode(.head)
.padding(.horizontal)
}

www.bigmountainstudio.com 141 Free Sample (Full Version Here)


Control Views
Text - Combining Modified Text
Group {
Text("You can ")
+ Text("format").bold()
+ Text (" different parts of your text by using the plus (+)
symbol.")
}
...

Group {
Text("Here is another ")
+ Text("example").foregroundColor(.red).underline()
+ Text (" of how you might accomplish this. ")
+ Text("Notice").foregroundColor(.purple).bold()
+ Text (" the use of the Group view to add padding and line
limit to all the text ")
+ Text("as a whole.").bold().italic()
}
.padding(.horizontal)
.layoutPriority(1)

Group {
Text("You can also ").font(.title).fontWeight(.light)
+ Text("combine")
+ Text(" different font weights ").fontWeight(.black)
+ Text("and different text
styles!").font(.title).fontWeight(.ultraLight)
}
.padding(.horizontal)

Although you see I’m wrapping my Text views in a Group, it is not required. I
only do this so I can apply common modifiers to everything within the Group.

www.bigmountainstudio.com 142 Free Sample (Full Version Here)


Control Views
Text - Baseline Offset
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)
Text("Baseline Offset")
.font(.title).foregroundColor(.gray)
Image("BaselineOffset")

Text("By default, your combined text will be on the same baseline,


like this:")
...

Text("100").underline()
+ Text(" SWIFTUI ").font(.largeTitle).fontWeight(.light)
.foregroundColor(.blue).underline()
+ Text ("VIEWS").underline()

Text("But you can offset each text view to create a cooler effect,
like this:")
...

Text("100").bold()
+ Text(" SWIFTUI ")
.font(Font.system(size: 60))
.fontWeight(.ultraLight)
.foregroundColor(.blue)
.baselineOffset(-12) // Negative numbers make it go down
+ Text ("VIEWS").bold()
}

www.bigmountainstudio.com 143 Free Sample (Full Version Here)


Control Views
Text - Layout Priority
Text("Text")
.font(.largeTitle)
Text("Layout Priority")
.font(.title)
.foregroundColor(.gray)

Text("Layout priority controls which view will get truncated last. The
higher the priority, the last it is in line to get truncated.")
...
.layoutPriority(1)

Text("This text gets truncated first because it has no priority.")


.font(.title)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.pink)

Text("The text view above got truncated because its layout priority is
zero (the default). This text view and the one on top has a priority
of 1.")
...
.layoutPriority(1)

www.bigmountainstudio.com 144 Free Sample (Full Version Here)


Control Views
Text - Custom Fonts
Text("Use a font that already exists on the system. If the font name
doesn't exist, it goes back to the default font.")
...
Text("This font doesn't exist")
.font(Font.custom("No Such Font", size: 26))

Text("Existing fonts:")
...

Text("Avenir Next")
.font(Font.custom("Avenir Next", size: 26))

Text("Gill Sans")
.font(Font.custom("Gill Sans", size: 26))

Text("Helvetica Neue")
.font(Font.custom("Helvetica Neue", size: 26))

Text("Adust the weight")


...

Text("Avenir Next Regular")


.font(Font.custom("Avenir Next Regular", size: 26))

Text("Or change with the weight modifier:")


.foregroundColor(.red)

Text("Avenir Next Regular Weight")


.font(Font.custom("Avenir Next", size: 26).weight(.regular))

www.bigmountainstudio.com 145 Free Sample (Full Version Here)


Control Views
Text - Imported Fonts
struct Text_CustomFont: View {
var body: some View {
VStack(spacing: 20) {
Text("Text")
.font(.largeTitle)

Text("Imported Fonts")
.font(.title)
.foregroundColor(.gray)

Text("Use the Font.custom() function to set imported fonts


you added to your project.")
...

Text("Hello, World!")
.font(Font.custom("Nightcall", size: 60))
.padding(.top)
}
}
}

In order for this to work, you have to add the font file to your project and be
sure to have the font file target your project. Then you need to add the font file
name to the Info.plist under the “Fonts provided by application” key:

www.bigmountainstudio.com 146 Free Sample (Full Version Here)


TEXTFIELD

In order to get or set the text in a TextField, you need to bind it to a variable. This variable is passed into the

TextField’s initializer. Then, all you need to do is change this bound variable’s text to change what is in the

TextField. Or read the bound variable’s value to see what text is currently in the TextField.

This is a push-out horizontally view.


147
Control Views
TextField - Introduction
VStack(spacing: 20) {
Group {
Text("TextField").font(.largeTitle)
Text("Introduction").font(.title).foregroundColor(.gray)
Text("It is required to bind text fields to a variable when
using them so you can get/set the text.")
...
}
Text("TextFieldStyle")
.font(.title)
.foregroundColor(.gray)

Text("By default, TextFields have a plain TextFieldStyle that has


no visual content to be seen.")
...
Image(systemName: "arrow.down.circle")
.font(.title)

TextField("", text: $textFieldData)

Image(systemName: "arrow.up.circle")
.font(.title)

Text("Use .textFieldStyle(RoundedBorderTextFieldStyle()) to show a


border.")
...
}

www.bigmountainstudio.com 148 Free Sample (Full Version Here)


Control Views
TextField - Title (Placeholder or Hint Text)
@State private var textFieldData = ""
@State private var username = ""
@State private var password = ""
...
VStack(spacing: 20) {
Text("TextField")
.font(.largeTitle)

Text("Title Text (Placeholder or Hint)")


.font(.title)
.foregroundColor(.gray)

Text("You can supply title text (placeholder/hint text) through


the first parameter to let the user know the purpose of the text
field.")
...

Group {
TextField("Here is title text", text: $textFieldData)
.textFieldStyle(RoundedBorderTextFieldStyle())

TextField("User name", text: $username)


.textFieldStyle(RoundedBorderTextFieldStyle())

TextField("Password", text: $password)


.textFieldStyle(RoundedBorderTextFieldStyle())

}
.padding(.horizontal)
}

www.bigmountainstudio.com 149 Free Sample (Full Version Here)


Control Views
TextField - Text Size and Fonts
VStack(spacing: 20) {
Text("TextField")
.font(.largeTitle)
Text("With Text Modifiers")
.font(.title)
.foregroundColor(.gray)

Image("Font")

Text("To change the size of the font used within the TextField,
you just need to use the font modifier.")
...

Group {
TextField("first name", text: $textFieldData)
.font(.title)
.textFieldStyle(RoundedBorderTextFieldStyle())

TextField("first name", text: $textFieldData)


.font(Font.system(size: 36, design: .monospaced))
.textFieldStyle(RoundedBorderTextFieldStyle())

TextField("first name", text: $textFieldData)


.font(Font.system(size: 20, design: Font.Design.serif))
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(.horizontal)

Text("Notice this also changes the placeholder or hint text in the


text field.")
...
}

www.bigmountainstudio.com 150 Free Sample (Full Version Here)


Control Views
TextField - Customizing Colors
VStack(spacing: 20) {
Text("TextField")
.font(.largeTitle)
Text("Customizing")
.font(.title)
.foregroundColor(.gray)
Text("One way to customize TextFields is to add a shape behind one
that has no TextFieldStyle set.")
...
TextField("Placeholder Text", text: $textFieldNoText)
.padding(10)
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color(hue: 0.126, saturation: 0.47,
brightness: 0.993)))
.padding()

TextField("Placeholder Text", text: $withOutline)


.padding(10)
.overlay(
// Add the outline
RoundedRectangle(cornerRadius: 8)
.stroke(Color.orange, lineWidth: 2)
)
.padding()

Text("Change text color using the foregroundColor property.")


...

TextField("first name", text: $textFieldWithText)


.textFieldStyle(RoundedBorderTextFieldStyle())
.foregroundColor(.orange)
.padding(.horizontal)
}

www.bigmountainstudio.com 151 Free Sample (Full Version Here)


Control Views
TextField - Custom Composition
@State private var textFieldData = ""
...
VStack {
HStack {
Image(systemName: "magnifyingglass").foregroundColor(.gray)
TextField("first name", text: $textFieldData)
Image(systemName: "slider.horizontal.3")
}
Divider()
}
.padding()

HStack {
Image(systemName: "envelope")
.foregroundColor(.gray).font(.headline)
TextField("email address", text: $textFieldData)
}
.padding()
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray,
lineWidth: 1))
.padding()

HStack {
TextField("country", text: $textFieldData)
Button(action: {}) {
Image(systemName: "chevron.right").padding(.horizontal)
}
.accentColor(.orange)
}
.padding()
.overlay(Capsule().stroke(Color.gray, lineWidth: 1))
.padding()

www.bigmountainstudio.com 152 Free Sample (Full Version Here)


TOGGLE

The Toggle is a switch that can either be on or off. Much like other controls, you need to bind it to a variable.

This variable is passed into the Toggle’s initializer. Then, all you need to do is change this bound variable’s

value to change the Toggle’s state on or off. Or read the bound variable’s value to see what state the Toggle is

currently in.

This is a push-out horizontally view.


153
Control Views
Toggle - Introduction
@State private var isToggleOn = true
...
Text("The Toggle fills the width of its parent view.")
...
Toggle("Night Mode", isOn: $isToggleOn)
.padding()

Text("Combine images with text")


...
Toggle(isOn: $isToggleOn) {
Text("Night Mode")
Image(systemName: "moon")
}
.padding()

Text("Or you can have nothing")


...
VStack {
Text("Turn Alarm On?")
.foregroundColor(.white)
Toggle("", isOn: $isToggleOn)
.frame(width: 25)
}
.padding(25)
.background(Color.pink)
.cornerRadius(20)
.shadow(color: .gray, radius: 10)

www.bigmountainstudio.com 154 Free Sample (Full Version Here)


Control Views
Toggle - Color Customization

Toggle(isOn: $isToggleOn) {
Text("1 Normal (Starting Point)")
}

Toggle(isOn: $isToggleOn) {
Text("2 Hue Rotation (45 Degrees)")
}.hueRotation(Angle.degrees(45))

Toggle(isOn: $isToggleOn) {
Text("3 Hue Rotation (90 Degrees)")
}.hueRotation(Angle.degrees(90))

Toggle(isOn: $isToggleOn) {
Text("4 Hue Rotation (135 Degrees)")
}.hueRotation(Angle.degrees(135))

Toggle(isOn: $isToggleOn) {
Text("5 Hue Rotation (180 Degrees)")
}.hueRotation(Angle.degrees(180))

Toggle(isOn: $isToggleOn) {
Text("6 Hue Rotation (225 Degrees)")
}.hueRotation(Angle.degrees(225))

Toggle(isOn: $isToggleOn) {
Text("7 Hue Rotation (270 Degrees)")
}.hueRotation(Angle.degrees(270))

Toggle(isOn: $isToggleOn) {
Text("8 Hue Rotation (315 Degrees)")
}.hueRotation(Angle.degrees(315))

www.bigmountainstudio.com 155 Free Sample (Full Version Here)


OTHER VIEWS

156
CIRCULAR SHAPES

Circular shapes include the Circle, Capsule and Ellipse. I grouped them together because they all operate the

same way and behave the same way with modifiers. The only difference is their shapes. One important thing

to note with all shapes is that they are drawn and centered WITHIN their own views. If you use a background

modifier to apply a color to a shape, you will see the bounds of the view the shapes are drawn within.

These are push-out views.


157
Other Views
Circular Shapes - Introduction
VStack(spacing: 20) {
Text("Circular Shapes")
.font(.largeTitle)

Text("Introduction")
.font(.title)
.foregroundColor(.gray)

Text("There are three circular shapes available in SwiftUI:")


...

Text("Circle")

Circle()
.padding()

Text("Capsule")

Capsule()
.padding()

Text("Ellipse")

Ellipse()
.padding()

}
.font(.title)

www.bigmountainstudio.com 158 Free Sample (Full Version Here)


Other Views
Circular Shapes - Color
VStack(spacing: 5) {
Text("Circular Shapes").font(.largeTitle)
Text("Colors").font(.title).foregroundColor(.gray)
Text("Circular shapes (and other shapes) can have shape styles
applied to them. Color is considered a shape style along with
different gradient patterns. You 'fill' a shape with color.")
...

Text("Using .fill(Color.green)")
Circle()
.fill(Color.green)
.frame(height: 100)
.padding()

Text("You can also use .foregroundColor(Color.red)")


Circle()
.foregroundColor(Color.red)
.frame(height: 100)
.padding()

Text("Using .background(Color.orange)")
Circle()
.background(Color.orange)
.frame(height: 100)
.padding()
}

Note, when applying a background color, it is the view containing the shape that
is affected. A shape has no background of its own.

www.bigmountainstudio.com 159 Free Sample (Full Version Here)


Other Views
Circular Shapes - Stroke (Outline)
VStack(spacing: 10) {
Text("Circular Shapes").font(.largeTitle)
Text("Strokes (Outlines)").font(.title).foregroundColor(.gray)
Text("Use strokes to create outlines around shapes. Using
.stroke(Color.red):")
...

Circle()
.stroke(Color.red)
.padding()
Text(".stroke(Color.purple, lineWidth: 20)")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.orange).foregroundColor(Color.white)
Capsule()
.stroke(Color.purple, lineWidth: 20)
.padding()

Text(".stroke(Color.blue, style: StrokeStyle(lineWidth: 10, dash:


[15, 10]))")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.orange).foregroundColor(Color.white)

Ellipse()
/* dash parameter
The 15 represents the length of the dash
The 10 represents the length in between dashes
*/
.stroke(Color.blue, style: StrokeStyle(lineWidth: 10, dash:
[15, 10]))
.padding()
}

www.bigmountainstudio.com 160 Free Sample (Full Version Here)


Other Views
Circular Shapes - Stroke Border (Inner Stroke)
VStack(spacing: 10) {
Text("Circular Shapes")
.font(.largeTitle)

Text("Stroke Border (Inner Stroke)")


.font(.title).foregroundColor(.gray)

Text("A stroke modifier grows outward from the center of the


shape's outline and can cause overlapping issues.")
...

ZStack {
Capsule()
.stroke(Color.purple, lineWidth: 50)
Capsule()
.stroke() // Black outline
}

Text("A strokeBorder modifier will grow inward from the shape's


outline.")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.pink).foregroundColor(Color.white)

ZStack {
Capsule()
.strokeBorder(Color.purple, lineWidth: 50)
Capsule()
.stroke() // Black outline
}
}

www.bigmountainstudio.com 161 Free Sample (Full Version Here)


Other Views
Circular Shapes - Sizing with Padding and Frame
Text("Using padding")

ZStack {
Circle()
.fill(Color.red)
.padding()
Circle()
.fill(Color.white)
.padding(40)
Circle()
.fill(Color.red)
.padding(60)
Circle()
.fill(Color.white)
.padding(80)
}.frame(width: 200, height: 200)

Text("Using frames")

ZStack(alignment: .bottom) {
Circle()
.fill(Color.secondary)
.frame(height: 250) Note, using multiple secondary
Circle() colors on top of each other (using
.fill(Color.secondary)
.frame(height: 200) frames example) will lighten or
Circle() darken each one depending on if
.fill(Color.secondary)
your app is in dark mode or light
.frame(height: 150)
Circle() mode.
.fill(Color.secondary)
.frame(height: 100)
}

www.bigmountainstudio.com 162 Free Sample (Full Version Here)


Other Views
Circular Shapes - Size Modifier
VStack(spacing: 10) {
Text("Circular Shapes")
.font(.largeTitle)

Text("Size Modifier")
.font(.title)
.foregroundColor(.gray)

Text("You can also use the shape's size modifier to define a


height and width. Take warning, though, using size no longer aligns
shapes to the center.")
...

Group {
Text("Circle using .size(width: 40, height: 40)")
Circle()
.size(width: 40, height: 40)
.background(Color.blue)

Text("Ellipse using .size(width: 40, height: 80)")


Ellipse()
.size(width: 40, height: 80)
.background(Color.blue)

Text("Capsule using .size(width: 80, height: 40)")


Capsule()
.size(width: 80, height: 40)
.background(Color.blue)
}
.padding()
}

www.bigmountainstudio.com 163 Free Sample (Full Version Here)


Other Views
Circular Shapes - Trim
@State private var circleProgress: CGFloat = 1.0
private var circlePercentage: Int { Int(circleProgress * 100.0) }
...
VStack(spacing: 40) {
Text("Circular Shapes").font(.largeTitle)
Text("Trim Function").font(.title).foregroundColor(.gray)
Text("Trim your shape to only show a percentage of it")
...

Circle()
.trim(from: 0, to: circleProgress)
.stroke(Color.purple,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.overlay(
Text("\(circlePercentage)%")
.font(.largeTitle)
.foregroundColor(.gray))
.padding(40)

VStack {
Text("Progress")
HStack {
Text("0%")
Slider(value: $circleProgress)
Text("100%")
}
}.padding()
}

www.bigmountainstudio.com 164 Free Sample (Full Version Here)


Other Views
Circular Shapes - With Buttons
VStack(spacing: 20) {
Text("Circular Shapes").font(.largeTitle)
Text("Using with Buttons").font(.title).foregroundColor(.gray)
Text("You can apply circular shapes to other views using the
.background modifier")
...

Button(action: {}) {
Text("Use the Capsule shape")
.bold().padding()
}
.background(Capsule().strokeBorder(Color.green, lineWidth: 1))
.accentColor(.green)

Button(action: {}) {
Text("Use the Capsule shape")
.foregroundColor(.white)
.bold().padding()
}
.background(Capsule().foregroundColor(.green))
.accentColor(.green)

Text("Circle and Button combination:")


Button(action: {}) {
Image(systemName: "circle.grid.hex.fill")
.font(.largeTitle)
.padding()
}
.background(Circle()
.foregroundColor(.green))
.accentColor(.white)
}

www.bigmountainstudio.com 165 Free Sample (Full Version Here)


COLOR

The one important thing to know about the Color struct is that it too is a view, just like any other view. It can

occupy its own space and have modifiers applied to it.

This is a push-out view.


166
Other Views
Color - Introduction
VStack(spacing: 40) {
Text("Color")
.font(.largeTitle)
Text("Colors are Views")
.font(.title)
.foregroundColor(.gray)

Divider()

Text("You can treat colors as views with their own frames and
modifiers")
...

HStack(spacing: 40) {
Color.pink
.frame(width: 88, height: 88)

Color.blue
.frame(width: 88, height: 88)
.shadow(radius: 20)

Color.purple
.frame(width: 88, height: 88)
.cornerRadius(20)
}
}

www.bigmountainstudio.com 167 Free Sample (Full Version Here)


Other Views
Color - As Background
ZStack {
Color("ColorBackground")
.edgesIgnoringSafeArea(.vertical)

// Your main view content here


VStack(spacing: 20) {
Text("Color")
.font(.largeTitle)
Text("Using As Background")
.font(.title)
.foregroundColor(.gray)
Divider()
Text("With a ZStack, you can set a Color view as the
background color.")
...

Text("(Background in Dark Mode)")


.padding(.top, 150)
}
}

www.bigmountainstudio.com 168 Free Sample (Full Version Here)


Other Views
Color - Light & Dark Modes
Text("The system colors will actually change in light and dark mode to
provide optimal contrast. If you supply a named color, you should try
to adopt this practice.")
...

List {
Color.pink
Color.red
Color.purple
Color.blue
Color.green
Color.yellow
Color.orange
Text("Custom Colors")
Color("AccentColorDark")
Color("AccentColorLight")
}

www.bigmountainstudio.com 169 Free Sample (Full Version Here)


Other Views
Color - System Colors
VStack(spacing: 20) {
Text("Color").font(.largeTitle)
Text("Other System Colors")
.font(.title)
.foregroundColor(.gray)

Text("These colors show differently depending on Light or Dark


mode.")
...

Text("Light Mode").font(.title)
Text("Primary Color")
.foregroundColor(Color.primary)
Text("Secondary Color")
.foregroundColor(Color.secondary)
Text("Accent Color")
.foregroundColor(Color.accentColor)

Group {
Text("Dark Mode").font(.title)
Text("Primary Color")
.foregroundColor(Color.primary)
Text("Secondary Color")
.foregroundColor(Color.secondary)
Text("Accent Color")
.foregroundColor(Color.accentColor)
}
}

www.bigmountainstudio.com 170 Free Sample (Full Version Here)


Other Views
Color - Stacking Secondary Colors
VStack(spacing: 40) {
ZStack(alignment: .bottom) {
Color.secondary

VStack {
Text("Color").font(.largeTitle)
ZStack {
Color.secondary
Text("Layering the Secondary Color")
.font(.title)
.foregroundColor(.primary).colorInvert()
}.frame(height: 50)
}
}.frame(height: 150)

Divider()
Text("When you stack the Secondary color on top of each other, it
gets slightly darker or lighter, depending if in light or dark mode.")
...

ZStack {
Color.secondary
Color.secondary.padding()
Color.secondary.padding(40)
Color.secondary.padding(60)
Color.secondary.padding(80)
}
.frame(height: 200)
.padding(.top, 50)
}

www.bigmountainstudio.com 171 Free Sample (Full Version Here)


Other Views
Color - Color Multiply
VStack(spacing: 20) {
Text("Color")
.font(.largeTitle)
Text("Color Multiply")
.font(.title)
.foregroundColor(.gray)

Divider()

Text("You can combine colors to form new colors with Color


Multiply")
...

HStack {
Color.pink
.frame(width: 88, height: 88)

Image(systemName: "plus")
.font(.title)

Color.blue
.frame(width: 88, height: 88)

Image(systemName: "equal")
.font(.title)

Color.pink.colorMultiply(Color.blue)
.frame(width: 88, height: 88)
}
}

www.bigmountainstudio.com 172 Free Sample (Full Version Here)


Other Views
Color - Opacity/Alpha
ZStack {
Color.purple

VStack(spacing: 20) {
Text("Color")
.font(.largeTitle)
Text("Opacity")
.font(.title)
.colorInvert()
Text("There are a couple ways to adjust color opacity. You can
use the Inspector and create a custom color to adjust the opactity
there. This black background's opacity is at 25%.")
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.background(Color(hue: 0.79, saturation: 0.0, brightness:
0.0, opacity: 0.25))
.font(.title).layoutPriority(1)
}
}
.edgesIgnoringSafeArea(.vertical)

To set the opacity you can go into the Inspector

and select “Custom” for a color. (You might have to

select it twice for the color picker to show.) You can

use the slider to adjust the opacity or manually

enter a value. Although, here it is referred to as

“Alpha”, not opacity, the effect is the same.

www.bigmountainstudio.com 173 Free Sample (Full Version Here)


Other Views
Color - Opacity Modifier
ZStack {
Color.purple

VStack(spacing: 20) {
Text("Color")
.font(.largeTitle)

Text("Opacity Modifier")
.font(.title)
.foregroundColor(.white)

Image("Opacity")

Text("You can also use the opacity modifier. (Note: The


opacity modifier can apply to any view, not just Color views.)")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.black.opacity(0.25))
.font(.title).layoutPriority(1)

Text("The opacity modifier can also be applied to a custom


color.")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color("Theme1Background").opacity(0.6))
.font(.title).layoutPriority(1)
}
}
.edgesIgnoringSafeArea(.vertical)

www.bigmountainstudio.com 174 Free Sample (Full Version Here)


Other Views
Color - Color Invert
...
Text("Using the colorInvert modifier you can increase your
choices from the system color pallet.")
...
HStack {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.gray)
.overlay(Text("Gray").foregroundColor(.white))
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.gray)
.colorInvert()
}
HStack {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
.overlay(Text("Red").foregroundColor(.white))
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
.colorInvert()
}
HStack {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.green)
.overlay(Text("Green").foregroundColor(.white))
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.green)
.colorInvert()
}
...

www.bigmountainstudio.com 175 Free Sample (Full Version Here)


DIVIDER

Dividers are views that operate like drawn lines. You cannot make them thicker but you can adjust how long

they are and their color.

The divider is a push-out view, either vertically or horizontally, depending on which kind of stack it is used in.
176
Other Views
Divider - Introduction
VStack(spacing: 20) {
Text("Divider")
.font(.largeTitle)
Text("Introduction")
.font(.title)
.foregroundColor(.gray)

Text("Dividers are easy to implement")


...

Text("Horizontal Divider")

Divider()
Image(systemName: "arrow.left.and.right")
.font(.system(size: 60))
Divider()

Text("It is horizontal because it is in a VStack. It will be


vertical if in an HStack")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.green).foregroundColor(Color.white)
.font(.title)

HStack {
Text("Horizontal Divider")
Divider()
Image(systemName: "arrow.up.and.down")
.font(.system(size: 60))
Divider()
}
}

www.bigmountainstudio.com 177 Free Sample (Full Version Here)


Other Views
Divider - Customizing
VStack(spacing: 20) {
Text("Divider").font(.largeTitle)
Text("Customization").font(.title).foregroundColor(.gray)

Text("You can change the colors of Dividers using the background


modifier")
...

Text("Red")
Divider().background(Color.red)
Text("Blue")
Divider().background(Color.blue)

Text("You can also change the size (but not the thickness) of a
Divider")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.green).foregroundColor(Color.white)
.font(.title)

HStack {
Divider().frame(height: 40)
Text("When vertical, you change the height")
Divider().frame(height: 40)
}

Group {
Divider().frame(width: 300)
Text("When horizontal, you change the width")
Divider().frame(width: 300)
}
}

www.bigmountainstudio.com 178 Free Sample (Full Version Here)


GROUP

A Group has no default visual representation on the screen. Instead, it is used as a container, similar to the
VStack or HStack. There are different reasons why you might want to group up your views which you will learn
about in this section.

This is a pull-in view.


179
Other Views
Group - Introduction
struct Group_Intro: View {
var body: some View {
VStack(spacing: 20) {
Text("Group")
.font(.largeTitle)

Text("Introduction")
.font(.title)
.foregroundColor(.gray)

Text("A Group view can be used to compose a view with more


than ten (10) child views.")
...

Text("View 4")
Text("View 5")
Text("View 6")
Text("View 7")
Text("View 8")
Text("View 9")
Group { // 10th View
Text("Child View 1 (Inside Group)")
Text("Child View 2 (Inside Group)")
}
}
}
}

As mentioned in the “Understanding the Syntax” chapter, a container cannot


have more than ten views. A Group view can be used to continue adding more
views to the screen.

www.bigmountainstudio.com 180 Free Sample (Full Version Here)


Other Views
Group - Shared Modifiers
Group {
Image(systemName: "leaf.arrow.circlepath")
.font(.largeTitle)
.padding()
Text("Please Recycle")
}
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)

Text("Notice the difference between these two")


.font(.system(size: 22))

VStack {
Image(systemName: "leaf.arrow.circlepath")
.font(.largeTitle)
.padding()
Text("Please Recycle")
}
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)

As you can see, when using a Group view, the modifiers are applied
INDIVIDUALLY to each view inside. This is different than when using other
containers, like the VStack. These modifiers are applied to the VStack as one
view.

www.bigmountainstudio.com 181 Free Sample (Full Version Here)


IMAGE

The image view can be used to show your own image in the asset catalog whether bitmap or PDF vector. It can

also be used to show SF (San Francisco) Symbols by using the systemName initializer.

The image is a pull-in view unless you apply the resizable modifier to it. Then it is a push-out view.
182
Other Views
Image - Introduction
VStack(spacing: 20) {
Text("Image")
.font(.largeTitle)

Text("From Asset Catalog")


.font(.title)
.foregroundColor(.gray)

Text("By default, your images retain their original size.")


...

Image("SwiftUI.red.small")

Text("You can change the size by adding the resizable modifier.")


...

Image("SwiftUI.red.small")
.resizable()

Text("By default, resizable will allow the image to expand to fill


all available space.")
...
}
.edgesIgnoringSafeArea(.bottom)

www.bigmountainstudio.com 183 Free Sample (Full Version Here)


Other Views
Image - Resizing
VStack(spacing: 10) {
Text("Image")
.font(.largeTitle)

Text("Resizing")
.font(.title)
.foregroundColor(.gray)

Text("With the resizable modifier, you can then adjust the frame
to resize")
...

Image("SwiftUI")
.resizable()
.frame(width: 100.0, height: 100.0)

Text("No Scaling")

Image("SwiftUI")
.resizable()
.frame(width: 100.0, height: 150.0)
.background(Color.primary)

Text("With Scaling")

Image("SwiftUI")
.resizable().scaledToFit()
.frame(width: 400.0, height: 200.0)
.background(Color.primary)
}

www.bigmountainstudio.com 184 Free Sample (Full Version Here)


Other Views
Image - Clipping
VStack(spacing: 10) {
Text("Image")
.font(.largeTitle)

Text("Clipping (Masking)")
.font(.title)
.foregroundColor(.gray)

Text("Use the clipShape modifier and provide shape to mask your


image")
...

Text("Circle")
Image("valley")
.clipShape(Circle())

Text("Rounded Rectangle")
Image("valley")
.clipShape(RoundedRectangle(cornerRadius: 10))
.shadow(radius: 10, x: 1, y: 1)

Text("Circle with Offset")


Image("valley")
.clipShape(Capsule().offset(x: -80, y: 0))
}

www.bigmountainstudio.com 185 Free Sample (Full Version Here)


Other Views
Image - Symbols Introduction
VStack(spacing: 40) {
Text("Image").font(.largeTitle)
Text("From Symbols").font(.title).foregroundColor(.gray)
Text("You can also use the Image view to load 'symbols' using the
'systemName' parameter in the Image initializer.")
...

Text("Here are some examples of symbols:")

HStack(spacing: 40) {
Image(systemName: "cloud.sun.fill")
Image(systemName: "cloud.sun.rain.fill")
Image(systemName: "cloud.sun.bolt.fill")
}

Text("Tip: Use the app, SF Symbols, to browse over 1500 symbols


available.")

Text("Set color with the foregroundColor modifier.")


...

HStack(spacing: 40) {
Image(systemName: "cloud.sun.fill")
.foregroundColor(Color.red)
Image(systemName: "cloud.sun.rain.fill")
.foregroundColor(Color.orange)
Image(systemName: "cloud.sun.bolt.fill")
.foregroundColor(Color.yellow)
}
}

www.bigmountainstudio.com 186 Free Sample (Full Version Here)


Other Views
Image - Symbol Sizing
Text("You can scale images using imageScale modifier.")
...

HStack(spacing: 40) {
Image(systemName: "thermometer.sun").imageScale(.large)
Image(systemName: "thermometer.sun").imageScale(.medium)
Image(systemName: "thermometer.sun").imageScale(.small)
}

Text("You can also change size with font text styles")


...

HStack(spacing: 40) {
Image(systemName: "thermometer.sun").font(.largeTitle)
Image(systemName: "thermometer.sun").font(.title)
Image(systemName: "thermometer.sun").font(.body)
Image(systemName: "thermometer.sun").font(.caption)
}

Text("Font point size will also work")


...

HStack(spacing: 40) {
Image(systemName: "thermometer.sun").font(.system(size: 60))
Image(systemName: "thermometer.sun").font(.system(size: 50))
Image(systemName: "thermometer.sun").font(.system(size: 40))
Image(systemName: "thermometer.sun").font(.system(size: 30))
}

www.bigmountainstudio.com 187 Free Sample (Full Version Here)


Other Views
Image - Symbol Weight
Text("Just as fonts have weight, symbols have weight too. You change
the weight the same way you set the font's weight.")
...

HStack(spacing: 40) {
Image(systemName: "moon.stars")
.font(Font.largeTitle.weight(.ultraLight))
Image(systemName: "moon.stars")
.font(Font.largeTitle.weight(.regular))
Image(systemName: "moon.stars")
.font(Font.largeTitle.weight(.semibold))
Image(systemName: "moon.stars")
.font(Font.largeTitle.weight(.bold))
Image(systemName: "moon.stars")
.font(Font.largeTitle.weight(.black))
}

Text("Use a custom font point size with a font weight.")


...

HStack(spacing: 40) {
Image(systemName: "moon.zzz")
.font(Font.system(size: 60, weight: .ultraLight))
Image(systemName: "moon.zzz")
.font(Font.system(size: 60, weight: .light))
Image(systemName: "moon.zzz")
.font(Font.system(size: 60, weight: .regular))
Image(systemName: "moon.zzz")
.font(Font.system(size: 60, weight: .bold))
}

www.bigmountainstudio.com 188 Free Sample (Full Version Here)


PATH

You use a path to draw lines or to describe a two-dimensional shape. The lines or shape you describe will not

be visible until you modify it with some strokes or fills.


189
Other Views
Path - Introduction
Text("You can't see this path because no modifier is added:")
Path { path in
// Set the starting Point:
path.move(to: CGPoint(x: 10, y: 10))
path.addLine(to: CGPoint(x: 300, y: 10))
}
Text("Let's draw a path and modify it with a red stroke:")
Path { path in
path.move(to: CGPoint(x: 10, y: 10))
path.addLine(to: CGPoint(x: 200, y: 10))
}
.strokedPath(StrokeStyle(lineWidth: 5))
.foregroundColor(.red)
Text("Let's create our own divider:")
MyDivider(lineWidth: 5, color: .purple)
...

struct MyDivider: View {


var lineWidth: CGFloat = 1
var color: Color = Color.secondary
var body: some View {
GeometryReader { geometry in
Path { path in
path.move(to: CGPoint(x: 10, y: 10))
path.addLine(to: CGPoint(x: geometry.size.width - 10,
y: 10))
}
.strokedPath(StrokeStyle(lineWidth: self.lineWidth,
lineCap: CGLineCap.round))
.foregroundColor(self.color)
}
}
}

www.bigmountainstudio.com 190 Free Sample (Full Version Here)


Other Views
Path - Triangles
VStack(spacing: 40) {
Text("Path").font(.largeTitle)
Text("Triangles").font(.title).foregroundColor(.gray)
Text("You can already create circular and rectangular shapes, so
I'm not going to cover those. Let's make triangles instead")
...

GeometryReader { geometry in
Path { path in
let middle = geometry.size.width / 2
let width: Length = 200
// Start in the center
path.move(to: CGPoint(x: middle, y: 10))
path.addLine(to: CGPoint(x: middle + (width / 2), y: 170))
path.addLine(to: CGPoint(x: middle-(width / 2), y: 170))
path.addLine(to: CGPoint(x: middle, y: 10))
}
.strokedPath(StrokeStyle(lineWidth: 1))
.foregroundColor(Color.blue)
}
}

www.bigmountainstudio.com 191 Free Sample (Full Version Here)


RECTANGULAR SHAPES

Rectangular shapes include the Rectangle and the RoundedRectangle. I have grouped them together

because they all operate the same way and behave the same with modifiers. The only difference is the shape

of the views they produce. Unlike Paths, you can see rectangular shapes because they default to the fill color

black.

These are push-out views.


192
Other Views
Rectangular Shapes - Introduction
VStack(spacing: 20) {
Text("Rectangular Shapes")
.font(.largeTitle)

Text("Introduction")
.foregroundColor(.gray)

Text("There are two rectangular shapes available in SwiftUI")


...

Text("Rectangle")

Rectangle()
.padding()

Text("Rounded Rectangle")

RoundedRectangle(cornerRadius: 20)
.padding()

Text("(Notice the default colors are black)")


.font(.body)
.padding()
}
.font(.title)

www.bigmountainstudio.com 193 Free Sample (Full Version Here)


Other Views
Rectangular Shapes - Colors
VStack(spacing: 20) {
Text("Rectangular Shapes")
.font(.largeTitle)

Text("Colors").font(.title).foregroundColor(.gray)

Text("Rectangular shapes (and other shapes) can have shape styles


applied to them. Color is considered a shape style along with
different gradient patterns. You 'fill' a shape with color.")
...

Text("Using .fill(Color.pink)")

RoundedRectangle(cornerRadius: 20)
.fill(Color.pink)
.padding()

Text("You can also use .foregroundColor(Color.green)")

Rectangle()
.foregroundColor(Color.green)
.padding()

Text("Using .background(Color.blue)")

RoundedRectangle(cornerRadius: .infinity)
.background(Color.blue)
.padding()
}

www.bigmountainstudio.com 194 Free Sample (Full Version Here)


Other Views
Rectangular Shapes - Stroke (Outline)
VStack(spacing: 10) {
Text("Rectangular Shapes").font(.largeTitle)
Text("Stroke (Outline)").font(.title).foregroundColor(.gray)
Text("Use stokes to create outlines around shapes. Using
.stroke(Color.purple):")
...
Rectangle()
.stroke(Color.purple)
.padding()

Text(".stroke(Color.blue, lineWidth: 20)")


...

RoundedRectangle(cornerRadius: 25)
.stroke(Color.blue, lineWidth: 20)
.padding()

Text(".stroke(Color.purple, style: StrokeStyle(lineWidth: 10,


dash: [15, 10]))")
...

RoundedRectangle(cornerRadius: 25)
/* dash parameter
The 15 represents the length of the dash
The 25 represents the length in between dashes
*/
.stroke(Color.purple, style: StrokeStyle(lineWidth: 10,
lineCap: CGLineCap.round, dash: [15, 25]))
.padding()
}

www.bigmountainstudio.com 195 Free Sample (Full Version Here)


Other Views
Rectangular Shapes - Stroke Border (Inner Stroke)
VStack(spacing: 20) {
Text("Rectangular Shapes").font(.largeTitle)
Text("Stroke Border (Inner Stroke)")
.font(.title).foregroundColor(.gray)
Text("A stroke modifier grows outward from the center of the
shape's outline and can cause overlapping issues.")
...

ZStack {
RoundedRectangle(cornerRadius: 40)
.stroke(Color.orange, lineWidth: 40)
RoundedRectangle(cornerRadius: 40)
.stroke() // Outline
}

Text("A strokeBorder modifier will grow inward from the shape's


outline.")
...

ZStack {
RoundedRectangle(cornerRadius: 40)
.strokeBorder(Color.orange, lineWidth: 40)
RoundedRectangle(cornerRadius: 40)
.stroke() // Outline
}

Text("Notice how .strokeBorder will start to square off inside


when the lineWidth increases")
...
}

www.bigmountainstudio.com 196 Free Sample (Full Version Here)


Other Views
Rectangular Shapes - Sizing with Padding and Frame
Text("Using padding")

ZStack {
Rectangle()
.fill(Color.black)
.padding()
Rectangle()
.fill(Color.white)
.padding(40)
Rectangle()
.fill(Color.black)
.padding(60)
Rectangle()
.fill(Color.white)
.padding(80)
}.frame(width: 150, height: 150)

Text("Using frames")

ZStack(alignment: .bottomLeading) {
RoundedRectangle(cornerRadius: 10)
.fill(Color.secondary)
.frame(width: 320, height: 250)
RoundedRectangle(cornerRadius: 10)
.fill(Color.secondary)
.frame(width: 270, height: 200)
RoundedRectangle(cornerRadius: 10)
.fill(Color.secondary)
.frame(width: 220, height: 150)
RoundedRectangle(cornerRadius: 10)
.fill(Color.secondary)
.frame(width: 170, height: 100)
}

www.bigmountainstudio.com 197 Free Sample (Full Version Here)


Other Views
Rectangular Shapes - Size Modifier
VStack(spacing: 10) {
Text("Rectangular Shapes")
.font(.largeTitle)

Text("Size Modifier")
.font(.title)
.foregroundColor(.gray)

Text("You can also use the shape's size modifier to define a


height and width. Take warning, though, using size no longer aligns
shapes to the center.")
.frame(minWidth: 0, maxWidth: .infinity).padding()
.background(Color.green).foregroundColor(Color.white)
.font(.title)

Group {
Text("Rectangle using .size(width: 80, height: 80)")
Rectangle()
.size(width: 80, height: 80)
.background(Color.orange)

Text("RoundedRectangle using .size(width: 200, height: 180)")

RoundedRectangle(cornerRadius: 20)
.size(width: 200, height: 180)
.background(Color.orange)
}
.padding()
}

www.bigmountainstudio.com 198 Free Sample (Full Version Here)


Other Views
Rectangular Shapes - Trim
@State private var circleProgress: CGFloat = 1.0
private var circlePercentage: Int { Int(circleProgress * 100.0) }
...
var body: some View {
VStack(spacing: 30) {
Text("Rectangular Shapes").font(.largeTitle)
Text("Trim Function").font(.title).foregroundColor(.gray)
Text("Trim your shape to only show a percentage of it")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 70)
.padding().font(.title)
.background(Color.red).foregroundColor(Color.white)

Rectangle()
.trim(from: 0, to: circleProgress)
.stroke(Color.red, style:
StrokeStyle(lineWidth: 40, lineCap: CGLineCap.round))
.frame(height: 300)
.overlay(Text("\(circlePercentage)%")
.font(.largeTitle).foregroundColor(.gray))
.padding(40)

VStack {
Text("Progress")
HStack {
Text("0%")
Slider(value: $circleProgress)
Text("100%")
}
}.padding()
}
}

www.bigmountainstudio.com 199 Free Sample (Full Version Here)


Other Views
Rectangular Shapes - With Controls
VStack(spacing: 20) {
Text("Rectangular Shapes").font(.largeTitle)
Text("Using with Controls").font(.title).foregroundColor(.gray)
Text("You can apply rectangular shapes to other views using the
.background modifier")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.title).padding().layoutPriority(1)
.background(Color.blue).foregroundColor(Color.white)

Button(action: {}) {
Text("RoundedRectange and Button")
.bold().padding()
}
.background(RoundedRectangle(cornerRadius: 10)
.strokeBorder(Color.blue, lineWidth: 1))
.accentColor(.blue)

Button(action: {}) {
Text("RoundedRectange and Button")
.foregroundColor(.white)
.bold().padding()
}
.background(RoundedRectangle(cornerRadius:
10).foregroundColor(.blue))
.accentColor(.green)

TextField("Placeholder Text", text: $textField)


.foregroundColor(.white)
.padding(10)
.background(RoundedRectangle(cornerRadius:
10).foregroundColor(.blue))
.padding()
}

www.bigmountainstudio.com 200 Free Sample (Full Version Here)


THANK YOU
I hope you have enjoyed this book as your visual quickstart reference guide. A lot of time and work went into this to make it as
easy as possible for you to use.

If you find anything wrong or have suggestions for improvement, please let me know.

Found a way to create a cool UI? I’d be super interested to see it!

If you would like to write a positive review on how this book has helped you, I would love to hear that too! Also, indicate if your
review is ok to publish. I may put your review on social media, my website or in an email to others.

Email: mark@bigmountainstudio.com

Direct Message me on Twitter: @bigmtnstudio

More to Come
I’m constantly working on the full version of the book so sign up to get notified on my website www.bigmountainstudio.com. I’ll
be sending out a newsletter when the book is ready or any updates have been made.

369
AFFILIATE INFO
I have an affiliate program that anyone can sign up for, whether you bought the book or not. If you mention you like my book

with your affiliate link and someone buys a book, you get:

20% !
If five people buy the book then you made your book is basically free. Beyond that, you have got yourself some extra spending

money.

I love it, sign me up!


Just go to www.bigmountainstudio.com/affiliate and sign up. You will need a PayPal account to get paid.

370

You might also like