Skip to content

Adding Getting Started tutorial for SwiftUI / iOS 16. #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 3, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 312 additions & 0 deletions getting-started/swiftui/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
---
layout: page
title: Build an iOS app
---

In this tutorial you’re going to use Swift and SwiftUI to build a small app to recommend fun new activities to users. Along the way you’ll meet several of the basic components of a SwiftUI app, including text, images, buttons, shapes, stacks, and program state.

To get started, you’ll need to [download Xcode from the Mac App Store](https://apps.apple.com/app/xcode/id497799835?mt=12). It’s free, and comes with all the tools you need to follow this tutorial.

Go ahead and launch Xcode once it’s installed, then select Create a new Xcode Project. Select the iOS tab at the top, then select the App template and press Next.

**Tip:** Although we’ll be targeting iOS 16, our code will also work great on macOS Ventura and beyond.

When making a new project, Xcode will ask you for a few pieces of information:

- For Product Name, enter “WhyNotTry”.
- For Organization Identifier you can enter com.example. In real apps we’d normally enter our own domain name here, e.g. org.swift.
- For Interface make sure SwiftUI is selected.
- You can uncheck the boxes for Core Data and Include Tests; we won’t be using them here.

Xcode will create the new project for you, and open ContentView.swift for editing. This is where we’ll write all our code, and you’ll see some default SwiftUI code in there for us.

The example code Xcode made for us creates a new view called `ContentView`. Views are how SwiftUI represents our app’s user interface on the screen, and we can add custom layout and logic in there.

On the right-hand side of Xcode, you’ll see a live preview of that code running – if you make a change to the code on the left, it will appear in the preview straight away.

For example, try changing the default `body` code to this:

var body: some View {
Text("Hello, SwiftUI!")
}

You should see your preview update immediately, which makes for really fast prototyping while you work. This is a computed property called `body`, and SwiftUI will call that whenever it wants to display our user interface.


## Building a static UI

In this app we’re going to show the user a new activity they could try to keep fit, such as basketball, golf, and hiking. To make it a little more attractive, we’ll display each activity using its name, and also an icon representing the activity, then add a splash of color behind it.

The main part of our user interface will be a circle showing the currently recommended activity. We can draw circles just by writing `Circle`, so replace the `Text("Hello, SwiftUI!")` view with this:

Circle()

In your preview you’ll see a large black circle fills the available screen width. That’s a start, but it’s not quite right – we want some color in there, and ideally adding a little space on either side so it doesn’t look so tight.

Both of these can be accomplished by calling methods on the `Circle` view. We call these *modifiers* in SwiftUI because they modify the way the circle looks or works, and in this case we need to use the `fill()` modifier to color the circle, then the `padding()` modifier to add some space around it, like this:

Circle()
.fill(.blue)
.padding()

The `.blue` color is one of several built-in options, such as `.red`, `.white`, and `.green`. These are all appearance aware, which means they look subtly different depending on the whether the device is in dark mode or light mode.

Over that blue circle we’re going to place an icon showing the activity we recommend. iOS comes with several thousand free icons called *SF Symbols*, and there’s a [free app you can download that shows you all the options](https://developer.apple.com/sf-symbols/). Each of these icons is available in multiple weights, can be scaled up or down smoothly, and many can also be colored.

Here, though, we want something nice and simple: we want just one icon placed over our circle. This means using another modifier called `overlay()`, which places one view over another. Modify your code to this:

Circle()
.fill(.blue)
.padding()
.overlay(
Image(systemName: "figure.archery")
)

You should see a small, black archery icon over our large, blue circle – it’s the right idea, but it doesn’t look great.

What we really want is the archery icon to be much bigger, and also much more visible on that background. For that we need another two modifiers: `font()` to control the size of the icon, and `foregroundColor()` to change its color. Yes, we use a font modifier to control the icon’s size – SF Symbols like this one automatically scale with the rest of our text, which makes them really flexible.

Adjust your `Image` code to this:

Image(systemName: "figure.archery")
.font(.system(size: 144))
.foregroundColor(.white)

**Tip:** That `font()` modifier asks for a 144-point system font, which is nice and big on all devices.

That should now a look a lot better.

Next, let’s add some text below the image so it’s clear to the user what the suggestion is. You already met the `Text` view and the `font()` modifier, so you can add this code below the `Circle` code:

Text("Archery!")
.font(.title)

Rather than using a fixed font size, that uses one of SwiftUI’s built in Dynamic Type sizes called `.title`. This means the font will grow or shrink depending on the user’s settings, which is usually a good idea.

If everything has gone to plan, your code should look like this:

var body: some View {
Circle()
.fill(.blue)
.padding()
.overlay(
Image(systemName: "figure.archery")
.font(.system(size: 144))
.foregroundColor(.white)
)

Text("Archery!")
.font(.title)
}

However, what you see in Xcode’s preview probably won’t match what you were expecting: you’ll see the same icon as before, but no text. What gives?

The problem here is that we’ve told SwiftUI our user interface will have two views inside – the circle and some text – but we haven’t told it how to arrange them. Do we want them side by side? One above the other? Or in some other kind of layout?

We get to choose, but I think here a vertical layout will look better. In SwiftUI we get that with a new view type called `VStack`, which is placed *around* our current code, like this:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should maybe not write the guide in first person, but rather passive (I think it's called?), i.e. not write "I think..."


VStack {
Circle()
.fill(.blue)
.padding()
.overlay(
Image(systemName: "figure.archery")
.font(.system(size: 144))
.foregroundColor(.white)
)

Text("Archery!")
.font(.title)
}

And now you should see the layout you expected earlier: our archery icon above the text “Archery!”.

That’s much better!

To finish up our first pass at this user interface, we can add a title at the top. We already have a `VStack` that allows us to position views one above the other, but I don’t want the title inside there too because later on we’ll be adding some animation for that part of our screen.

Fortunately, SwiftUI lets us nest stacks freely, meaning that we can place a `VStack` inside another `VStack` to get the exact behavior we want. So, change your code to this:

VStack {
Text("Why not try…")
.font(.largeTitle.bold())

VStack {
Circle()
.fill(.blue)
.padding()
.overlay(
Image(systemName: "figure.archery")
.font(.system(size: 144))
.foregroundColor(.white)
)

Text("Archery!")
.font(.title)
}
}

That makes the new text have a large title font, and also makes it bold so it stands out better as a real title for our screen.

Now we have two `VStack` views: an inner one that holds the circle and “Archery!” text, and an outer one that adds a title around the inner `VStack`. This will be very helpful later on when we add animation!


## Bringing it to life

As much fun as archery is, this app really needs to suggest a random activity to users rather than always showing the same thing. That means adding two new properties to our view: one to store the array of possible activities, and one to show whichever one is currently being recommended.

SF Symbols has lots of interesting activities to choose from, so I’ve picked out a handful that work well here. Our `ContentView` struct already has a `body` property containing our SwiftUI code, but we want to add new properties outside that. So, change your code to this:

struct ContentView: View {
var activities = ["Archery", "Baseball", "Basketball", "Bowling", "Boxing", "Cricket", "Curling", "Fencing", "Golf", "Hiking", "Lacrosse", "Rugby", "Squash"]

var selected = "Archery"

var body: some View {

That creates an array of various activity names, and selects archery as the default. Now we can use the selected activity in our UI using string interpolation – we can place the `selected` variable directly inside strings.

For the activity name this is straightforward:

Text("\(selected)!")
.font(.title)

For the image this is a little more complicated, because we need to prefix it with `figure.` then lowercase the activity name – we want `figure.archery` rather than `figure.Archery`, otherwise the SF Symbol won’t be loaded.

So, change your `Image` code this:

Image(systemName: "figure.\(selected.lowercased())")

Those changes mean our UI will display whatever the `selected` property is set to, so can see it all change if you place a new string in that property:

var selected = "Baseball"

Of course, we want that to change *dynamically* rather than having to edit the code each time, so we’re going to add a button below our inner `VStack` that will change the selected activity every time it’s pressed. This is still inside the outer `VStack`, though, which means it will be arranged below the title and activity icon.

Add this code now:

Button("Try again…") {
// change activity
}
.buttonStyle(.borderedProminent)

So, your structure should be this:

VStack {
// "Why not try…" text

// Inner VStack with icon and activity name

// New button code
}

The new button code does three things:

1. We create the `Button` by passing in a title to show as the button’s label.
2. The `// change activity` comment is code that will be run when the button is pressed.
3. The `buttonStyle()` modifier tells SwiftUI we want this button to stand out, so you’ll see it appear in a blue rectangle with white text.

Just having a comment as the button’s action isn’t very interesting – really we want to make it set `selected` to a random element from the `activities` array. We can pick a random element from the array by calling the helpfully named `randomElement()` method on it, so replace the comment with this:

selected = activities.randomElement()

That code *looks* right, but it will actually cause compiler errors. We’re telling Swift to pick a random element from the array and place it into the `selected` property, but there’s no way for Swift to be sure there’s anything in that array – it could be empty, in which case there’s no random element to return.

Swift calls these *optionals*: `randomElement()` won’t return a regular string, it will return an *optional* string. This means the string might not be there, so it’s not safe to assign to the `selected` property.

Even though we know the array will never be empty – it will *always* have activities in there – we can give Swift a sensible default value to use just in case the array happens to be empty in the future, like this:

selected = activities.randomElement() ?? "Archery"

That partly fixes our code, but Xcode will still be showing an error. The problem now is that SwiftUI doesn’t like us changing our program’s state right inside our view structs without warning – it wants us to mark all the mutable state ahead of time, so it knows to watch for changes.

This is done by writing `@State` before any view properties that will change, like this:

@State var selected = "Baseball"

This is called a *property wrapper*, meaning that it wraps our `selected` property with some extra logic. The `@State` property wrapper allows us to change view state freely, but it also automatically watches its property for changes so that it can make sure the user interface stays up to date with the latest values.

That fixes the two errors in our code, so you can now press Cmd+R to build and run your app in the iOS simulator. It will suggest baseball by default, but every time you press “Try again” you’ll see it change.


## Adding some polish

Before we’re done with this project, let’s add a handful more tweaks to make it better.

First, an easy one: Apple recommends that local view state always be marked with `private` access control. In larger projects, this means you can’t accidentally write code that reads one view’s local state from another, which helps keep your code easier to understand.

This means modifying the `selected` property like so:

@State private var selected = "Baseball"

Second, rather than always showing a blue background, we can pick a random color each time. This takes two steps, starting with a new property of all the colors we want to select from – put this next to the `activities` property:

var colors: [Color] = [.blue, .cyan, .gray, .green, .indigo, .mint, .orange, .pink, .purple, .red]

Now we can change our circle’s `fill()` modifier to use `randomElement()` on that array, or `.blue` if somehow the array ends up being empty:

Circle()
.fill(colors.randomElement() ?? .blue)

Third, we can separate the activity `VStack` and “Try again” button by adding a new SwiftUI view between them, called `Spacer`. This is a flexible space that automatically expands, which means it will push our activity icon to the top of the screen, and the button to the bottom.

Insert it between the two, like this:

VStack {
// current Circle/Text code
}

Spacer()

Button("Try again…") {

If you add multiple spacers, they will divide the space equally between them. If you try placing a second spacer before the “Why not try…” text you’ll see what I mean – SwiftUI will create and equal amount of space above the text and below the activity name.

And fourth, it would be nice if the change between activities was smoother, which we can do by animating the change. In SwiftUI, this is done by wrapping changes we want to animate with a call to the `withAnimation()` function, like this:

Button("Try again…") {
withAnimation {
selected = activities.randomElement() ?? "Archery"
}
}
.buttonStyle(.borderedProminent)

That will cause our button press to move between activities with a gentle fade. If you want, you can customize that animation by passing the animation you want to the `withAnimation()` call, like this:

withAnimation(.easeInOut(duration: 1)) {

That’s an improvement, but we can do better!

The fade happens because SwiftUI sees the background color, icon, and text changing, so it removes the old views and replaces it with new views. Earlier I made you create an inner `VStack` to house those three views, and now you can see why: we’re going to tell SwiftUI that these views can be identified as a single group, and that the group’s identifier can change over time.

To make that happen, we need to start by defining some more program state inside our view. This will be the identifier for our inner `VStack`, and because it will change as our program runs we’ll use `@State`. Add this property next to `selected`:

@State private var id = 1

**Tip:** That’s more local view state, so it’s good practice to mark it with `private`.

Next, we can tell SwiftUI to change that identifier every time our button is pressed, like this:

Button("Try again…") {
withAnimation(.easeInOut(duration: 1)) {
selected = activities.randomElement() ?? "Archery"
id += 1
}
}
.buttonStyle(.borderedProminent)

Finally, we can use SwiftUI’s `id()` modifier to attach that identifier to the whole inner `VStack`, meaning that when the identifier changes SwiftUI should consider the whole `VStack` as new. This will make it animate the old `VStack` being removed and a new `VStack` being added, rather than just the individual views inside it. Even better, we can control how that add and remove transition happens using a `transition()` modifier, which has various built-in transitions we can use.

So, add these two modifiers to the inner `VStack`, telling SwiftUI to identify the whole group using our `id` property, and animate its add and removal transitions with a slide:

.transition(.slide)
.id(id)

Press Cmd+R to run your app one last time, and you should see that pressing “Try Again” now smoothly animates the old activity off the screen, and replaces it with a new one. It even overlaps animations if you press “Try Again” repeatedly!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you explicitly tell the reader to build and run, but possibly also note that the animation works when you run in the simulator, but doesn't quite work as expected if you run it as a Preview.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did consider that, because you're right that the SwiftUI preview is incorrect. However, it felt a bit like drawing attention to an Xcode bug, and I figured Apple would prefer we didn't do that. Any views, @TimTr?



## Where now?

We’ve covered a lot of SwiftUI basics in this tutorial, including text, images, buttons, stacks, and even using `@State` to mark values that change over time. SwiftUI is capable of so much more, and can be used to build huge cross-platform apps if you needed.

If you’d like to continue learning SwiftUI, there are lots of free resources available. For example, [Apple publishes a wide variety of tutorials](https://developer.apple.com/tutorials/swiftui) covering essential topics, drawing and animation, app design, and more. We’ll also post links here on Swift.org to some other popular tutorials – we’re a big and welcoming community, and we’re glad to have you join!