SwiftUI Views FREE Sample
SwiftUI Views FREE Sample
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
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
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
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
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
Background 246
Background - Colors 247
Background - Using Gradients 248
Background - Using Shapes 249
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
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
Contrast 301
Contrast - Introduction 302
Contrast - With Photos 303
xii
Table of Contents
Corner Radius - Introduction 305
Corner Radius - Fully Rounded Sides 306
Grayscale 312
Grayscale - Introduction 313
Grayscale - With Photos 314
Opacity 322
Opacity - Introduction 323
Opacity - On Layers 324
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
Shadow 344
Shadow - Radius 345
Shadow - Colors 346
Shadow - On Different Views 347
Shadow - Offset 348
Shadow - Order Matters 349
Shadow - Opacity & Shadows 350
xiv
Table of Contents
Resources 359
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
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
You can also read the book from beginning to end. The choice is
yours.
17
CONVENTIONS
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()
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.
struct MyView {
var body {
NewView()
.modifyTheView1()
.modifyTheView2()
}
}
NewView()
.modifyTheView1()
.modifyTheView2()
When space is limited, I omit the unnecessary code and show an ellipsis:
struct MyView {
var body {
... // Unnecessary code omitted
NewView()
}
}
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?
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.
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.
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.
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.
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? }
}
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("The Basics")
.font(.title)
.foregroundColor(.gray)
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")
}
}
}
Text("Short Introduction")
.font(.title)
.foregroundColor(.gray)
Text("I'll make shapes, give them color and put them behind other
views just for decoration.")
...
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)
Image(systemName: "arrow.down.to.line.alt")
Image(systemName: "arrow.up.to.line.alt")
}
}
}
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)
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()
}
Text("Spacing")
.font(.title)
.foregroundColor(.gray)
Image(systemName: "arrow.up.and.down.circle.fill")
.font(.largeTitle)
HStack stands for “Horizontal Stack”. It is a pull-in container view in which you pass in up to ten views and it will
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))
}
Text("Spacing")
.font(.title)
.foregroundColor(.gray)
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)
}
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)
A ZStack is a push-out container view. It is a view that overlays its child views on top of each other. (“Z”
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!
VStack(spacing: 20) {
Text("ZStack")
.font(.largeTitle)
ZStack(alignment: .bottomTrailing) {
Image("yosemite_large")
Rectangle()
.foregroundColor(transparentWhite)
.frame(width: 390, height: 50)
Image("yosemite_layers")
}
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)
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
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()
}
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()
}
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)
GeometryReader {_ in
Text("Views center automatically inside the
GeometryReader")
.font(.title)
}
.foregroundColor(.white)
.background(Color.pink)
}
}
}
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("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)
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("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)
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")
...
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)
Text("Using an HStack")
.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.
}
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)
)
}
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)
Button(action: {}) {
Image("yosemite")
.cornerRadius(40)
}
Button(action: {}) {
Text("")
}
.background(Image("yosemite")
.cornerRadius(40))
}
}
}
Text("Floating")
.font(.title).foregroundColor(.gray)
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.
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.
HStack {
Spacer()
Image(systemName: "moon.circle")
Spacer()
Circle()
.frame(height: 60.0)
Spacer()
Image(systemName: "moon.circle.fill")
Spacer()
}
.font(.title)
.foregroundColor(Color.yellow)
...
VStack(spacing: 9) {
Text("DatePicker")
.font(.largeTitle)
Text("Displayed Components")
.font(.title).foregroundColor(.gray)
Notice that offset isn’t necessary when pickers are used in forms.
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.
Image("baby")
.resizable()
.aspectRatio(contentMode: .fit)
The Form view is a great choice when you want to show settings, options, or get some user input. It is easy to
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.")
}
}
Image(systemName: "smiley.fill")
.frame(minWidth: 0, maxWidth: .infinity, alignment:
.center)
.font(.largeTitle)
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.")
}
}
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())
}
}
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"]
.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
Spacer()
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.
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”.
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.
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.
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.
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.
Spacer()
}
.padding(.top, 25)
}
.navigationBarTitle(Text("Navigation Views"),
displayMode: .inline)
.edgesIgnoringSafeArea(.bottom)
}
class ActionSheets {
static func getTwoChoiceActionSheet() -> ActionSheet {
ActionSheet(title: Text("Actions"),
buttons: [.default(Text("Action 1")),
.destructive(Text("Action 2"))]
)
}
}
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")
...
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.
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)
}
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.
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.
Text("SecureField")
.font(.largeTitle)
Text("Introduction")
.font(.title)
.foregroundColor(.gray)
Spacer()
}
...
ZStack{
RoundedRectangle(cornerRadius: 8)
.foregroundColor(.purple)
TextField("user name", text: $userName)
.foregroundColor(Color.white)
.padding(.horizontal)
}
.frame(height: 40)
.padding(.horizontal)
RoundedRectangle(cornerRadius: 8)
.foregroundColor(.purple)
.overlay(
SecureField("password", text: $password)
.foregroundColor(Color.white)
.padding(.horizontal)
)
.frame(height: 40)
.padding(.horizontal)
Image("SecureFieldLayers")
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.
...
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.")
...
Text("With Images:")
...
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.")
...
You use the bound variable to set or get the value the Slider’s thumb (circle) is currently at.
Text("You can also set your own min and max value.")
....
Group {
Text("Age is: ") +
Text("\(ageFormatter.string(from: NSNumber(value: age))!)")
.foregroundColor(.pink)
}
.font(.title)
Slider(value: $sliderValue)
.padding(.horizontal)
.accentColor(.orange)
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)
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()
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.
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)
}
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.")
...
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.
Text("Notice the minus and plus buttons are not affected. The
platforms determine how this will be shown.")
...
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.
// Second Screen
Text("This view represents the Second Screen.")
.tabItem {
Text("Tab 2")
}
}
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.
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.")
...
// 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)
}
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.
The text view will probably be one of your most-used views. It has many, if not the most, modifiers available to
it.
Text("Wrapping")
.font(.title)
.foregroundColor(.gray)
Image("LineLimit")
Text("Text Styles")
.font(.title)
.foregroundColor(.gray)
Image("Font")
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)
}
}
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)
}
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)
}
}
Text("Font Design")
.font(.title)
.foregroundColor(.gray)
Image("AllowsTightening")
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.
Image("MinimumScaleFactor")
Text(".minimumScaleFactor(0.5) is being used here:")
...
Text("Line Spacing")
.font(.title)
.foregroundColor(.gray)
Image("LineSpacing")
Image("MultilineTextAlignment")
Divider()
Text(".multilineTextAlignment(.center)")
...
Text(".multilineTextAlignment(.trailing)")
...
Text("Truncation Mode")
.font(.title).foregroundColor(.gray)
Image("TruncationMode")
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)
}
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.
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()
}
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("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)
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("Imported Fonts")
.font(.title)
.foregroundColor(.gray)
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:
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.
Image(systemName: "arrow.up.circle")
.font(.title)
Group {
TextField("Here is title text", text: $textFieldData)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(.horizontal)
}
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())
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()
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.
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))
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.
Text("Introduction")
.font(.title)
.foregroundColor(.gray)
Text("Circle")
Circle()
.padding()
Text("Capsule")
Capsule()
.padding()
Text("Ellipse")
Ellipse()
.padding()
}
.font(.title)
Text("Using .fill(Color.green)")
Circle()
.fill(Color.green)
.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.
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()
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()
}
ZStack {
Capsule()
.stroke(Color.purple, lineWidth: 50)
Capsule()
.stroke() // Black outline
}
ZStack {
Capsule()
.strokeBorder(Color.purple, lineWidth: 50)
Capsule()
.stroke() // Black outline
}
}
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)
}
Text("Size Modifier")
.font(.title)
.foregroundColor(.gray)
Group {
Text("Circle using .size(width: 40, height: 40)")
Circle()
.size(width: 40, height: 40)
.background(Color.blue)
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()
}
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)
The one important thing to know about the Color struct is that it too is a view, just like any other view. It can
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)
}
}
List {
Color.pink
Color.red
Color.purple
Color.blue
Color.green
Color.yellow
Color.orange
Text("Custom Colors")
Color("AccentColorDark")
Color("AccentColorLight")
}
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)
}
}
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)
}
Divider()
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)
}
}
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)
VStack(spacing: 20) {
Text("Color")
.font(.largeTitle)
Text("Opacity Modifier")
.font(.title)
.foregroundColor(.white)
Image("Opacity")
Dividers are views that operate like drawn lines. You cannot make them thicker but you can adjust how long
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("Horizontal Divider")
Divider()
Image(systemName: "arrow.left.and.right")
.font(.system(size: 60))
Divider()
HStack {
Text("Horizontal Divider")
Divider()
Image(systemName: "arrow.up.and.down")
.font(.system(size: 60))
Divider()
}
}
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)
}
}
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.
Text("Introduction")
.font(.title)
.foregroundColor(.gray)
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)")
}
}
}
}
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.
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)
Image("SwiftUI.red.small")
Image("SwiftUI.red.small")
.resizable()
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)
}
Text("Clipping (Masking)")
.font(.title)
.foregroundColor(.gray)
Text("Circle")
Image("valley")
.clipShape(Circle())
Text("Rounded Rectangle")
Image("valley")
.clipShape(RoundedRectangle(cornerRadius: 10))
.shadow(radius: 10, x: 1, y: 1)
HStack(spacing: 40) {
Image(systemName: "cloud.sun.fill")
Image(systemName: "cloud.sun.rain.fill")
Image(systemName: "cloud.sun.bolt.fill")
}
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)
}
}
HStack(spacing: 40) {
Image(systemName: "thermometer.sun").imageScale(.large)
Image(systemName: "thermometer.sun").imageScale(.medium)
Image(systemName: "thermometer.sun").imageScale(.small)
}
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)
}
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))
}
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))
}
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))
}
You use a path to draw lines or to describe a two-dimensional shape. The lines or shape you describe will not
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)
}
}
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.
Text("Introduction")
.foregroundColor(.gray)
Text("Rectangle")
Rectangle()
.padding()
Text("Rounded Rectangle")
RoundedRectangle(cornerRadius: 20)
.padding()
Text("Colors").font(.title).foregroundColor(.gray)
Text("Using .fill(Color.pink)")
RoundedRectangle(cornerRadius: 20)
.fill(Color.pink)
.padding()
Rectangle()
.foregroundColor(Color.green)
.padding()
Text("Using .background(Color.blue)")
RoundedRectangle(cornerRadius: .infinity)
.background(Color.blue)
.padding()
}
RoundedRectangle(cornerRadius: 25)
.stroke(Color.blue, lineWidth: 20)
.padding()
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()
}
ZStack {
RoundedRectangle(cornerRadius: 40)
.stroke(Color.orange, lineWidth: 40)
RoundedRectangle(cornerRadius: 40)
.stroke() // Outline
}
ZStack {
RoundedRectangle(cornerRadius: 40)
.strokeBorder(Color.orange, lineWidth: 40)
RoundedRectangle(cornerRadius: 40)
.stroke() // Outline
}
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)
}
Text("Size Modifier")
.font(.title)
.foregroundColor(.gray)
Group {
Text("Rectangle using .size(width: 80, height: 80)")
Rectangle()
.size(width: 80, height: 80)
.background(Color.orange)
RoundedRectangle(cornerRadius: 20)
.size(width: 200, height: 180)
.background(Color.orange)
}
.padding()
}
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()
}
}
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)
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: [email protected]
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.
370