WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

How to build your first SwiftUI app with Swift Playgrounds

Learn the basics of SwiftUI and build a real app along the way.

Paul Hudson       @twostraws

Swift Playgrounds lets you build a complete iOS app from scratch, right on your iPad. You get to draw upon Apple’s powerful SwiftUI framework, you can pull in libraries that other people have written, and if you want you can even move your project over to your Mac and continue it in Xcode, Apple’s full-blown code editor.

In this tutorial I’m going to walk you through building your very first app using Swift Playgrounds for iPad, where you’ll be able to show pictures of your friends and family, and tapping one of them will play a sound – it’s just for fun, but you’ll learn a lot along the way, and also make use of other built-in software on your iPad. This tutorial is aimed at beginners, so you don’t need any prior Swift experience.

To follow along you’ll need to install Playgrounds from the App Store, but that’s it – you don’t need Xcode or even a Mac. In case you were curious, I’m using Apple’s Magic Keyboard with an iPad Pro, running iOS 15.2.

Hacking with Swift is sponsored by RevenueCat

SPONSORED You know StoreKit, but you don’t want to do StoreKit. RevenueCat makes it easy to deploy, manage, and analyze in-app subscriptions on iOS and Android so you can focus on building your app.

Explore the docs

Sponsor Hacking with Swift and reach the world's largest Swift community!

First steps

To get started, press the “App” button under “Get a Playground” to create a new project. This will be called My App by default, and it will be given a random icon – I’ll show you how to change those later.

Go ahead and tap your new playground to open it for editing, and you’ll see Playgrounds splits into two parts: on the left is some simple Swift code to get us started, and on the right is a live preview that shows your actual code running.

I expect the starter code will change over time, but that’s okay because I want you to delete almost all of it – leave only this behind:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
    }
}

As you delete parts, you’ll see the preview on the right change immediately – it really is live, and later on you’ll see it’s interactive too.

Our remaining code does several things:

  1. It tells Swift to bring in the SwiftUI framework. Swift is the programming language we use to build our apps, but SwiftUI is a set of tools that gives us buttons, images, and other common user interface elements.
  2. It creates a new piece of UI, known as a view in SwiftUI, that is called ContentView. You’ll create lots of views in your apps, but each one needs its own unique name.
  3. It says the body of this view – the thing that’s actually shown on the screen – will return some kind of SwiftUI view. That means our ContentView will show other pieces of UI inside.
  4. Finally, it says the UI we want to show is the text “Hello, world!”

We’re going to be writing a lot more code in there over the course of this tutorial, but first I want to spend a few minutes just noodling around with the basics so you have a better grasp of what’s going on.

We can customize the way our text looks by adding modifiers. There are lots of these built right in to SwiftUI, and you’ll often find yourself using several at once to get exactly the right effect.

For example, try changing your text to include these modifiers below:

Text("Hello, world!")
    .font(.largeTitle)    
    .foregroundColor(.blue)

That will give the text a large font and a blue color.

You can also control the background color of the text by adding another modifier. For example, we could give our text a white foreground color and a blue background color like this:

Text("Hello, world!")
    .font(.largeTitle)    
    .foregroundColor(.white)
    .background(.blue)

There are two things you’ll notice there:

  1. We set the foreground color of the text using foregroundColor(), but the background color using just background(). This is intentional: foreground colors are always simple colors, but backgrounds can include all sorts of other things such as pictures.
  2. Notice how the background fits almost exactly around the edge of the text – Swift knows the exact size of the text, including its natural line spacing allowance, and makes sure the background color is the same size.

If you wanted to make the background a little bigger than the text, ask SwiftUI to include some padding before the background, like this:

Text("Hello, world!")
    .font(.largeTitle)    
    .foregroundColor(.white)
    .padding()
    .background(.blue)

And now you’ll see the blue background extending beyond the text, which is nicer. You can specify where you want the padding to be placed, e.g. padding(.horizontal), but for now just stick with padding().

Many SwiftUI modifiers can be applied multiple times, causing them to stack up and create interesting effects. For example, we could apply more padding and a second background color, then even more padding and a third background color:

Text("Hello, world!")
    .font(.largeTitle)    
    .foregroundColor(.white)
    .padding()
    .background(.blue)
    .padding()
    .background(.mint)
    .padding()
    .background(.green)

Tip: All these color names we’re using are built into SwiftUI, but you can create your own from scratch too.

Making a list

Now that you’ve seen some of the basics of SwiftUI, I want to start moving over to the actual app we’re going to build.

In iOS two of the most common types of view are called List and NavigationView: one represents a scrolling list of data like you see in Settings, and one provides the ability to display a title at the top and optionally the ability to show more information when something is tapped.

You can place all sorts of data inside your lists, but let’s start with just some text for now – change your code to this:

NavigationView {
    List {
        Text("Hello, world!")
    }
}

You’ll see our text appears on the right, with a curved box around it to highlight that this is a list row. There’s also a lot of space above it – that’s space for the title of our navigation view, which is empty right now. We can provide our own title using another modifier attached to the list, like this:

NavigationView {
    List {
        Text("Hello, world!")
    }
    .navigationTitle("Friendface")
}

We’ll add some more to that in a moment, but first I’d like you to try swiping around in our previous – you’ll see you can scroll up and down a little, the title “Friendface” kind of stretches when you scroll down, and when you scroll up it shrinks away into a smaller title. This is exactly how all the rest of iOS behaves, and we get it all for free with SwiftUI.

That preview is our real code running, but Swift Playgrounds also has a Play button that runs the app fullscreen, as if it were running on the iPad all by itself. If you press that now you’ll see something interesting: our list takes up only the left-hand corner of the screen – the whole right-hand side is empty!

This happens because the system thinks our list is designed to let users choose from a list of data, so that when they choose something from that list they see more details on the right – again, exactly how the Settings app works, but also Mail, Messages, and many others.

In this app we don’t want that behavior, so we can ask our NavigationView to take up all available space.

This is done with another modifier, this time attached to the NavigationView. So, you need to exit app playback and head back to ContentView.swift – tap the orange and white Swift icon in the top, then press Stop from the list of options.

Back in ContentView add this modifier at the end of our NavigationView:

.navigationViewStyle(.stack)

Now if you press Play again you’ll see our List takes up the whole screen – much better!

Working with dynamic data

Inside our List we have a single piece of text, but really we want to show lots of pieces of text – one for each friend or family member we want to have in our app. We can do this using another kind of view called ForEach, but it needs to be given two important pieces of data:

  1. A list of all the items we want to show. It will feed us one item at a time, and ask us to convert that into a SwiftUI view that can be shown in the list.
  2. A way to identify each item in our list uniquely, otherwise SwiftUI has no way of knowing which piece of data should belong to which list row.

Both of these take some new learning, but only a little. The first new thing is that we need to provide a list of names outside the body of the view, like this:

struct ContentView: View {
    let names = ["Paul", "Sophie", "Charlotte"]

    var body: some View {

That creates a new value called names, and it contains an array of text: Paul, Sophie, Charlotte. Arrays let us hold several values in one place, and they are exactly the kind of thing ForEach likes to be given.

The second new thing is our ForEach view. Remember, this needs to be given a list of all the data we want to show, which will be our names array, but it also needs to be told how to identify each item in the array uniquely.

We have Paul, Sophie, and Charlotte in our names array, which means every name is unique. Other than the text itself there is nothing else unique about the items in our array – they don’t have ID numbers attached, for examples, they just have the text itself. So when it comes to telling SwiftUI what makes each piece of data unique, we use a special value, \.self, which means we’re promising that the item itself is unique. If later on you add more names that aren’t unique you’ll hit a problem, but it’s good enough here!

So, adjust your List code to this:

List {
    ForEach(names, id: \.self) { name in
        Text(name)
    }
}

You’ll see our preview updates to show all three times, just as we wanted – nice!

Let’s break down that new code:

  • We still have a list of items, except now we’re specifying them using an array of data rather than hard-coding all our text views.
  • We’ve told ForEach to create its items from the names array, and also that each item in that array can be identified uniquely by itself.
  • ForEach will hand us one name from the array at a time, and ask us what we want to do with it. That’s the name in part – it’s literally sending one name into our list row, so we can do with it whatever we want.
  • In this case we’re showing that name in a text view, which is why we see the names in our list.

Having data in an array is much nicer than typing it in by hand, because now we can modify our array to see more items in our list:

let names = ["Paul", "Sophie", "Charlotte", "Adrian", "Novall", "Allen"]

Bringing in pictures

One of the great things about building software on your iPad is that there are so many other apps you can lean on to bring in extra content. Here we’re going to bring in some photos, but later on we’ll also use Apple’s Voice Memos to import some sounds too.

First, photos: in the sidebar for Swift playgrounds you’ll see an Add New Item button, which looks like a document with a + over it – please tap that now, then select Photo. You’ll see your photo library showing all the pictures you’ve taken previously, and I’d like you to select a photo either of yourself or of a friend or family member.

For me, I’m going to bring in a photo of my eldest daughter, Sophie. When it’s imported, it will be given the default name of “Image Asset”, but I’m going to rename that to “Sophie”. You can rename your picture by long-pressing on it, then selecting Rename from the popover menu. I’ll then bring in another photo, this time of my youngest daughter Charlotte, then rename it from Image Asset to Charlotte.

Finally, I’m going to bring in one more picture, but this time I’m going to take it live because it shows you an interesting problem you’re likely to face. I would consider it a bug in Swift Playgrounds, so perhaps by the time you follow this video it’s no longer an issue!

Anyway, I’m going to launch the Camera app now, switch to front-facing camera, and take a photo of myself. Now I’m going to head back to Swift Playgrounds and import that photo, and you’ll see when I do so it’s upside down! This happens because most phone cameras store the picture in one fixed way, but then save a hidden flag to remember which orientation to use when drawing it. That orientation is being ignored by Swift Playgrounds, so we’re seeing the picture the way iOS sees it internally.

To fix this, we’re going to start by deleting the image because it’s no good to us as-is. Now you need to head to the Photos app, select the picture you just took, and make any change at all – even a tiny crop is fine. When you press Done iOS will have to rewrite the image file to apply your change, and it automatically takes this opportunity to write it in the correct orientation.

So, once you’ve made a small change to your picture you can head back to Swift Playgrounds and import it once again – it should all be fine now. As that’s a picture of me, I’ll rename it from Image Asset to Paul.

We have three pictures of people, which is more than enough for us to continue on with, but I’m going to add one bonus picture of my dog, Luna. She’s not the smartest dog around, but she’s a good girl! This will once again by given the default name of Image Asset, but I’ll rename it to Luna.

Now I’m going to update our names array to match the four picture names we added:

let names = ["Paul", "Sophie", "Charlotte", "Luna"]

And now for the best bit: rather than just showing the text Paul, Sophie, and Charlotte, we can show pictures instead. Change your ForEach view to this:

ForEach(names, id: \.self) { name in
    Image(name)
}

That will make your preview change, but it’s going to look a bit strange now – certainly not the pictures you were expecting.

This happens because our pictures are being displayed at their original size, which is way too big for our little preview. We can bring them in size by attaching two modifiers: one to let SwiftUI resize the images, and one to make them scale down so they fit the available space:

Image(name)
    .resizable()
    .scaledToFit()

Much better!

Now, remember how earlier I point out the curved box around our list rows? That was welcome earlier because it made our rows look visually distinct in our layout, but now it’s not so nice – it would be much nicer if our images went edge to edge.

We can get that by changing List to ScrollView – literally just change the word “List” to be “ScrollView” and we’ll get a better layout. SwiftUI’s List view is for scrolling tables of data like we have in the Settings app, or when you’re choosing a contact to talk to in Messages, but when you want your own custom layout a simple ScrollView is simpler because we can put whatever we want in there.

Speaking of putting whatever we want in there, how about we add a little extra styling to our pictures? Try adding these two modifiers below scaledToFit():

.cornerRadius(25)
.padding(.horizontal)

Nice!

Playing some audio

In this app we’re going to show faces belonging to various friends and family, then play sounds when they get tapped. We’ve handled pictures, but how do make them do something when tapped?

Well, this is handled through another SwiftUI view called Button, which is created with two pieces of information: some code to run when the button is tapped, and a label for the button – what you want users to see on the screen.

For us the label is our image, so we can start off by wrapping the image inside a button like this:

Button {
    // ???
} label: {
    Image(name)
        .resizable()
        .scaledToFit()
        .cornerRadius(25)
        .padding(.horizontal)
}

The // ??? line is called a comment – when you start a line of code with two slashes like that, it tells Swift to ignore the line. Comments are useful to remind yourself how code works, but here it’s a placeholder for our button’s action – what do we want to happen when the button is pressed?

Well, when you’re just building your app you might want to print a message to the screen by replacing the comment with this:

print("\(name) was tapped!")

The \(name) part tells Swift we want to put the current value of name into the message, so now tapping Sophie will print “Sophie was tapped!” – you can see these messages by showing the console when your app is running.

What we want here is to play sounds for our people, which means we need to start by importing some audio files we can work with. I already have sounds recorded for Sophie, Charlotte, and Luna, so I’ll import those by choosing Insert From when adding a new item. This will bring up the list of recent documents in my iCloud Files, where I have my files ready to go – I’ll just tap to bring them in, and you can do the same. Swift Playgrounds can preview audio files for us: select one to open it, then tap the Play button to hear how it sounds.

Each of these sound files matches the names of my photos, with “m4a” on the end – that’s the file extension used for audio files. There wasn’t one for me, but that’s okay because I can just record one now by launching Voice Memos and recording something. If you need to do the same, record your audio as normal then tap the Share icon and select Save to Files and save it wherever you want – I’ll choose iCloud Drive because it’s more convenient, but you can do whatever works. For me that new audio will already be copied over to iCloud Drive, so I can head back to Swift Playgrounds and import Paul.m4a just like the other audio files.

So now we have a bunch of pictures and a bunch of sounds, how do we actually make one sound play when its appropriate picture is tapped?

Well, SwiftUI doesn’t have a built-in way of playing sounds – the “UI” part means it does user interfaces, not audio. But that’s okay, and in fact it’s all part of my plan because it gives me a chance to show you a real power feature of SwiftUI: you can bring in code anyone else has written, straight from code sharing sites such as GitHub.

In this case, I already wrote a free framework to make playing audio easier in SwiftUI, and we can bring it in here and start using it. To do that, go back to the Add New Item button and this time choose Swift Package.

Swift Playgrounds will ask you for the package URL, which should be an internet location that contains the code you want to read. For this project I’d like you to enter the following: https://github.com/twostraws/Subsonic. That’s the name of my audio framework, and when you press return Swift Playgrounds will fetch some information about it so it can be used in our app.

When you see the “Version” and “Allow Updates” options appear, tap “Add to Project” to bring it in. You’ll see Subsonic gets listed under Packages in your sidebar, so if you ever decide you don’t want it in the future you can long-press and choose Remove Package Dependency to get rid of it entirely.

Anyway, here want to use it, so start by heading back to your code in ContentView.swift. If you have a bunch of m4a files or images open, you can tap the close icon near the top to get rid of them.

Now we need to make just two more changes to ContentView.swift to get this app working for real.

First, we need to add one extra line at the very top, to tell Swift we want to import all the functionality from the Subsonic package. That means adding import Subsonic next to import SwiftUI – I prefer to put it before because I keep my import lines in alphabetical order, but it doesn’t actually matter.

And the second change is in your button’s action, replace the print() code with this:

play(sound: "\(name).m4a")

That adds “m4a” to the current person’s name to match our sound files, then asks Subsonic to play them. That’s it! You can immediately tap the faces on the right to play the matching sounds – Subsonic will happily place many sounds at once, so you can tap the same face repeatedly or tap a variety.

Making it work better fullscreen

So far we’ve mostly been using the little preview area on the right-hand side of our code, but if you press the Play button you’ll see our app running fullscreen. This is how folks who install the app from the App Store will see our app, and although it works it’s not a great experience because our pictures are so big!

To fix this we can take a more scalable approach: rather than just showing one picture at a time, we can show a grid of pictures. This takes two steps, starting with describing to SwiftUI how many columns we want to have in our grid.

Add this below the let names line, outside the body of our view:

let columns = [GridItem(.adaptive(minimum: 250))]

That means “create an array of grid items, but put only one item in there: an adaptive grid item that has a minimum size of 250 points.” Adaptive grid items let us say we don’t care how many columns we have in our grid, as long as they are at least 250 points wide – we’re giving SwiftUI the opportunity to make them wider if necessary, but they need to be at least 250 points wide.

The second change is to wrap our whole ForEach code – everything that’s currently inside the ScrollView – inside another new view type, called LazyVGrid. This creates grids of views, and you need to tell it how you want your grid laid out, and what should be inside the grid.

For us the grid layout is that new columns array we just made, and what should be inside is all our ForEach code. So, change your code to this:

ScrollView {
    LazyVGrid(columns: columns) {
        ForEach(names, id: \.self) { name in
            // all the existing ForEach code
        }
    }
}

In the preview area everything will look the same, because our grid is automatically adapting to make best use of the space. But if you press the Play button you’ll see the pictures spread out across a grid row – you still get all the same scrolling behavior as before, but now lots of pictures can appear on the same row.

Wrapping up

That’s all the code we’re going to write in this project, but before we’re done I want to show you one important last feature: changing your app’s settings that control its name, accent color, and icon.

These are all accessed under the App Settings window, which you can get by tapping “My App” in the sidebar. The name of your app will be what’s shown when selecting a project in Swift Playgrounds, but will also be used on the Home Screen if you ship your app to the App Store – I’m going to change mine to Friendface, to match what I’ve used elsewhere.

Next, your app’s accent color is used to color two parts of your app: any interactive things such as colored icons and button text, but also the placeholder icon. Swift Playgrounds will automatically choose a random accent color for you, but you can change it if you like.

Finally, we can change the app icon to something else. There are a bunch of built-in icons to choose from, which combine with your accent color to create something fairly unique. Alternatively, you can tap Custom to bring in a picture you designed from Photos or Files.

Where next?

We’ve covered a lot in this tutorial, but really it’s just the beginning – Swift Playgrounds can take pictures with the camera, read the user’s location, authenticate using Face ID, access the microphone, and so much more.

Plus SwiftUI itself is really packed with some incredible features, letting you create rich, powerful apps for iPhone and iPad, but also Apple Watch, Apple TV, and macOS. In fact, Swift Playgrounds can even share your project to your Mac, so you can switch over to Apple’s Xcode instead – it’s the much bigger tool that most professional iOS developers use, but all the Swift and SwiftUI you learned work just as well here.

Anyway, that’s the end of this tutorial. We’ve covered a lot of ground:

  • The basics of SwiftUI views and modifiers
  • Using List and NavigationView
  • Bringing in dynamic data with an array
  • Importing pictures and audio using Photos and Voice Memos
  • Resizing images and rounding their edges
  • Adding buttons that trigger actions
  • Importing Swift packages written by someone else
  • Laying out our images as a grid rather than a list
  • Customizing your app settings

If you want to carry on learning Swift and SwiftUI, I have a huge, free course that includes over 20 projects, quizzes to test your learning, tips, answers, and so much more. It’s called the 100 Days of SwiftUI, and like I said it’s completely free – it does use Xcode, but I think you’ll find that all of it works great in Swift Playgrounds too! To find out more, click here: the 100 Days of SwiftUI.

I look forward to seeing the amazing things you make with Swift Playgrounds! Send me a tweet @twostraws and let me know what you think of Swift Playgrounds 4 – are you excited to start using it, are there particular things you miss, and do you think it will help a whole new range of developers get started with Swift and SwiftUI?

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain. The code can be hard to write, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app.

Explore the docs

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and a speaker at Swift events around the world. If you're curious you can learn more here.

Was this page useful? Let us know!

Average rating: 4.3/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.