Author:
Last Updated: 22 September, 2022
- Getting Started
- Writing your first Swift code
- Combining Views Using Stacks
- How body works
- Populating body
- Image and SFSymbols
- Text and basic styling
- Spacers, Padding, and Frame
- More text styling and Foreground Color
- Extracting Subviews & List
- What’s List
- Abstracting our ListRowView()
- Iterating over data
- Navigation
- Finished Version Sample Code
Getting Started
To get started, click Create a new Xcode project
. With App
selected, click Next
. Name your first app, and make sure your interface is SwiftUI
. Click Next
and then choose a place to store your project. Click Create
and your project should open up.
When you first open Xcode you should see something like this:
- The left sidebar is your File Browser where you can see files associated with your project.
- The second column is the Code Editor where you can edit the code in each file.
- The third column is your Previews. After loading for a bit, you’ll see an iPhone with your project being run inside. If this section never stops loading, it’s worth quitting and reopenning Xcode (get used to doing this when things stop working).
- The rightmost column shows the Attributes Inspector. You’ll use this different tabs in this column to edit specific attributes of certain files.
Writing your first Swift code
- Xcode should open your project to the
ContentView.swift
file. Inside, you’ll see astruct
namedContentView
of typeView
. Inside, is a computed property namedbody
of typesome View
; meaning the body can be any type of view (Text
,Image
,VStack
, etc.). Right now, the body is aVStack
. - All objects in SwiftUI are eventually
Views
. Keep this in mind if you ever are trying to return aString
or anInteger
instead of aText
view for example. - Let’s start by modifying the
Text
view. You can add.font(.title)
and.foregroundColor(.green)
below it, just like.imageScale(...)
and.foregroundColor(...)
on theImage
view above it. Your text should now be larger and in green. You’ve written your first SwiftUI code!
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
}
}
Combining Views Using Stacks
How body
works
Let’s clear our ContentView
’s body property and start building the foundations of a user interface. We’re going to build one row of a List
that we’ll create later. Your ContentView
should look like this (and Xcode might have throw a ton of errors):
struct ContentView: View {
var body: some View {
}
}
If Xcode is throwing errors, it’s because the body property needs to return a view, but at the moment it’s empty so it’s not returning anything. Let’s fix this.
We want to display more than one item at a time so we’ll need to use some structures which let us combine more than one view into a single view. The most common form of these are Stacks
. SwiftUI has three types of Stack, the HStack
, VStack
, and ZStack
. They let you layout content in Horizontal, Vertical, and Z-direction space.
Since we’re building a row for a list, let’s layout our content horizontally using a HStack
. Add HStack {}
to your body property.
Populating body
Now, let’s populate our layout with some content. We’re going to build a Wollaston’s Bakery catalogue, so let’s think through what this row might need. It’s going to represent an item they offer, and when we tap on the row it will show a detali view with more information about the item.
Helpful information to put on this list row might be an Image to show the type of item, some Text to show the name of the item, and on the far right edge we can show the price.
To our body
property let’s add an Image(systemName: “”)
, Text("")
, Spacer()
, and another Text("")
. Then we’ll walk through each item one-by-one.
Image and SFSymbols
Starting with the Image
. We used the systemName initializer because we want to access the large library of SFSymbols. These are built in images you can use in your apps which scale with fonts and are easy to add.
Type cmd shift L
and navigate to the ⭐ section to see the entire list of SFSymbols. To represent an item, let’s use “tag.fill”
.
Your image property should look like this: Image(systemName: "tag.fill")
and your Preview might show an image of a tag.
Text and basic styling
Text is one of the most common Views you’ll use in SwiftUI. In its most basic form, it accepts a String and displays it using attributes you describe through modifiers
.
Let’s represent Wollaston’s best muffin, the French Toast muffin as text: Text("French Toast Muffin")
.
This item’s important, so let’s modify the font a little bit using the .font(...)
modifier. Typing .
between the parentheses will display a list of autocomplete font options. Let’s choose .headline
.
Your Text should look like this now:
Text("French Toast Muffin")
.font(.headline)
Spacers, Padding, and Frame
Between your two text views we’ve added a Spacer()
. Most views in SwiftUI take up only the space they need, but spacers are special because they work in the opposite way. Spacers fill up all the available space given to them. In this case, the spacer pushes our Text and Image views towards each edge. You can modify how much space items take up using the .frame(...)
modifier.
If you want to add a little space around an item, you can use .padding()
. Let’s place a padding modifier on our HStack to give it a little breathing room from the edges of the screen.
More text styling and Foreground Color
Before we move on, let’s add a price too. Muffins are “$2.50” at Wolly’s.
If you want a little more control over your text styling, it might be worth using the .font(.system(style:,design:,weight:)
modifier. This modifier lets you define the size (style), design (rounded, monospaced, serif, or sans-serif [default]), and the weight (regular, bold, heavy, thin, etc.)
Since we want the price to have less significance than the name, let’s modify that text view using: .font(.system(.caption, design: .monospaced, weight: .regular))
.
We also want to change the color so that the color reflects its importance too. To do this we can use .foregroundColor(.secondary)
. Secondary
is a built in color that’s a gray which adjusts based on light and dark mode. This is what your price Text view should look like:
Text("$2.50")
.font(.system(.caption, design: .monospaced, weight: .regular))
.foregroundColor(.secondary)
After populating our body, here’s what we should have in ContentView
. I’ve added a .foregroundColor(.pink)
to the Image
to add a small pop of color.
struct ContentView: View {
var body: some View {
HStack {
Image(systemName: "tag.fill")
.foregroundColor(.pink)
Text("French Toast Muffin")
.font(.headline)
Spacer()
Text("$2.50")
.font(.system(.caption, design: .monospaced, weight: .regular))
.foregroundColor(.secondary)
}
.padding()
}
}
Extracting Subviews & List
Now it’s great that we have this list row, but if we have to define everything in one body property that will get messy really fast. Luckily, there’s a method for dealing with this.
You can place any view inside another view, meaning not only the ContentView
struct can have a body
property. Any struct which adopts the View
protocol can have a body
.
Let’s create a new SwiftUI View file (cmd n
) called ListRowView
. Let’s replace the contents of the body in this view, with the current contents of our ContentView’s body. (Your ContentView body will be empty and throwing errors again.
Now, add a List() {}
to your ContentView.
What’s List
List
let’s us define how to deal with a list of views. We do this by iterating over some set of data and describing what view to output for each datum.
Let’s start by iterating over a range and displaying our row for each iteration:
List(0..<20) { _ in
ListRowView()
}
We should see our row has been replicated 10 times. We can scroll up and down this list too.
Now what if we want to iterate over different data for each entry? We’ll have to start by modifying our list row.
Abstracting our ListRowView()
Right now our ListRowView
only display the single best item at Wollastons. But what if we want to give people more options? We’ll need to abstract the view’s properties.
Let’s define some constants to use in our view instead. Add this above the body
property in ListRowView
:
let imageName: String
let title: String
let price: Double
Now, let’s use these properties in our view, replacing the hard-coded content with the property names. For Strings, we’ll need to interpolate using “\(propertyName)”
. You can also specify a rounding format using String(format: "$%.02f", propertyName)
to round to 2 decimal places with a preceding $ as an example.
HStack {
Image(systemName: imageName)
.foregroundColor(.pink)
Text(title)
.font(.headline)
Spacer()
Text(String(format: "$%.02f", price))
.font(.system(.caption, design: .monospaced, weight: .regular))
.foregroundColor(.secondary)
}
.padding()
Your Preview and the ContentView will throw errors. Respond to them accordingly, adding the required properties to their initializers.
// Updated Preview
struct ListRowView_Previews: PreviewProvider {
static var previews: some View {
ListRowView(imageName: "tag.fill", title: "French Toast Muffin", price: 2.5)
}
}
// Updated ContentView
struct ContentView: View {
var body: some View {
List(0..<20) { _ in
ListRowView(imageName: <#String#>, title: <#String#>, price: <#Double#>)
}
}
}
Iterating over data
Example Data
We’ll need some data to iterate over first. I’ve defined a struct to represent one WollyItem
and then some examples in a static struct.
struct WollyItem: Identifiable {
var id = UUID()
var title: String
var imageName: String = "tag.fill"
var price: Double
}
struct Examples {
static let wollyItems = [
WollyItem(title: "French Toast Muffin", price: 2.5),
WollyItem(title: "Iced Coffee", price: 4.49),
WollyItem(title: "Bagel", price: 1.65),
WollyItem(title: "Cookie", price: 1.99),
WollyItem(title: "Brownie", price: 1.8)
]
}
Note the WollyItem
has adopts the Identifiable
protocol, which is required for items that List will iterate over (which lets list keep track of which item is which).
I’ve also defined a default value for id
and imageName
because they won’t be changing for now. This makes it easier to define examples.
Iterating
In List
, let’s replace our range with some data. Turn List(0..<20) { _ in
into List(Examples.wollyItems) { item in
. We’ve replaced the data with some specific data of a new type instead of a numbered range, and we’ve replaced the _
with a named item
.
In Swift, _
means we want to ignore the given item, but we acknowledge it exits. Naming that value item
means we can interact with it now.
Let’s populate the parameters of our ListRowView
too: ListRowView(imageName: item.imageName, title: item.title, price: item.price).
List(Examples.wollyItems) { item in
ListRowView(imageName: item.imageName, title: item.title, price: item.price)
}
If you reset the preview you should see a list with our defined example data.
Navigation
Let’s add some simple Navigation. This section will be in broader strokes and less of a follow along.
To add Navigation, Views need to be contained in some sort of Navigation layout. NavigationStack
is a great place to start. We’ll wrap our list in one. At first this will appear to not have done anything.
We want to display some details for each item in our catalogue. To do this, we’ll wrap the ListRowView in a NavigationLink to link to the detail view. This will add arrows to the right side of each list item, and tapping on the items will bring us to the detail views (which are just a Text view for now).
NavigationStack {
List(Examples.wollyItems) { item in
NavigationLink {
Text("Detail view")
} label: {
ListRowView(imageName: item.imageName, title: item.title, price: item.price)
}
}
}
SwiftUI makes it really easy to add a title too! Let’s add a .navigationTitle("Wolly's Mobile")
inside our NavigationStack.
We can modify the accentColor
on the NavigationStack too to change the color of the Back buttons, and we’ve got ourselves a handy little app with very little work.
Finished Version Sample Code
This is the version built using the above tutorial.