Projects People Resources Semesters Blog About
Swift (iOS Apps)

Swift (iOS Apps)

Last Updated: 22 September, 2022

Useful Documentation

Link to the official Apple Developer Documentation. Start here when exploring a feature.

Getting Started Presentation

This presentation provides explanations for basic building blocks and how to apply this to creating a simple Shopping List app.

Click above to view the slides. Presentation given by Frank Anderson on 25 September, 2022 during Fall 2022 Hack Session 2.

MyFirstApp.zip3086.7KB

Download the sample code here. Please play with and manipulate it to see how different parts of the app work.

Presentation Content Summary

The Goal

These are the screens we want to make using SwiftUI
These are the screens we want to make using SwiftUI

Data in Swift

To build an interface, we need some data to show first. Let’s define some examples.

We’ll start with defining one item at Wollaston’s, a WollyItem. SwiftUI likes to use Structs, so let’s define a struct SwiftUI can identify with some properties to denote what type of WollyItem we have.

We’re representing a real item, so let’s think about the attributes we need to include for our code as well such that we accurately represent the real world.

// Represents an item Frank would get at Wollaston's
struct WollyItem: Identifiable { // marked Identifiable, requirement for List in SwiftUI
	var id = UUID() // UUID is an easy way to make unique identifiers that satisfy the `var id` requirement
	var title: String // Use : to require definition when instantiated
	var imageName: String = "tag.fill" // use = to define a default
	var price: Double // Use : to require definition when instantiated
}
Note Swift is strongly typed, just like Java, but (not shown here) uses optionals if an object can be nil.

Now that we have the ability to define an item, let’s define an example list of these items.

// Represents Examples of WollyItems
struct Examples {

	// static instances of each item 
  // Great if we want to preivew a single item (DetailView, ListRowView, etc.)
  static let muffin = WollyItem(
      title: "French Toast Muffin", price: 2.5)
  static let coffee = WollyItem(
      title: "Iced Coffee", price: 4.49)
  static let bagel = WollyItem(
      title: "Bagel", price: 1.65)
  static let cookie = WollyItem(
      title: "Cookie", price: 1.99)
  static let brownie = WollyItem(
      title: "Brownie", price: 1.8)
  
  // static list of many items
	// great for sampling a whole group (cart, list, etc.)
  static let wollyItems = [muffin, coffee,
                           bagel, cookie, 
													 brownie]
}
We are using this as our model to start. In the future, we could create a model that calls to an API to get this list of items.

Thinking about Layout

We’ve got some data, so now let’s build an interface. Rule #1 of SwiftUI is that everything is a View. You can combine and manipulate Views to build up an interface. Rule #2 is that each View structure can only return one view.

Because of the limitation of Rule #2, we use Stacks, Groups, and other Layouts to combine views.

⌨️
Examples of each Stack
VStack
image
image

🔑
Image above the Text
HStack
image
image
🔑
Image next to Text
ZStack
image
image
🔑
Images stacked on top of each other.

Using Stacks, you can build up different Views to build interfaces. By default, Stacks center views inside of them, and views take up only the space they require to render. You can just adjust how much space views take up using .frame() and .padding(). You can adjust the amount of space between views in a stack using SomeStack(spacing:) and you can adjust the alignment of views in stacks using SomeStack(alignment:).

The different colored rectangles show how different sections of code correspond to different outputs.
The different colored rectangles show how different sections of code correspond to different outputs.

Styling with Modifiers

You can style individual views and groups of views using Modifiers. Modifiers build up in order from top to bottom, so if you want some buffer space between a view and its background modifier, you’d apply padding and then a background. If you want more space between the background and the next element, you’d apply another padding. It’s hard to explain with words, so play around with it a bit on your own and see how switching the order changes the view. (Keyboard Shortcut tip: cmd + option + [ or ] lets you move code up and down lines.)

Fun thing: SFSymbols are a set of built-in icons you can use. Use the Image(systemName:) initializer to access them, or use Cmd + Shift + L and click the Star to see a library of them.
Fun thing: SFSymbols are a set of built-in icons you can use. Use the Image(systemName:) initializer to access them, or use Cmd + Shift + L and click the Star to see a library of them.

Abstraction (to remove Duplication)

Using reusable components to simplify our work and reduce duplication let’s us be more efficient programmers. It also maintains cohesion across you app since you don’t have to define every modifier and detail manually every time.

To create reusable components, you extract complex views into their own subviews.

Here’s an example of subviews at work:
image

To extract subviews, you can follow three quick steps:

  1. Create a new SwiftUI file which will have a struct of type View and a body which returns some View.
  2. Cut the parts from the main view and paste them into the body of the subview.
  3. Wait for Xcode to produce errors, pointing to missing variables that were in the main view which are not accessible in the subview. Create the necessary receivers (normally @Binding variables, @ObservedObject variables, or let constants) and pass in the values from the main view.
Example of extracted subview:
image
image

Something else you can do to remove duplication is to simplify modifiers into custom ViewModifiers/Extensions. There are some examples of this in the demo project which you can download. Look to DemoGuide.md to see where each of the examples are linked.

Data + Design = …

Now it’s time to connect our views and data together to build a responsive application. A key thing to remember about declarative UI (like SwiftUI or React) is that the views are a product of their states. States are the values of the instance variables in a given View’s structure. States that the views can respond to (not just display) are marked using property wrappers in SwiftUI. Examples of these are @State, @Binding, @ObservedObject, @Environment, @StateObject, etc.

When passing data around, it’s important to remember there should be one source of truth, and the other views should merely refer to the source, not recreate it. The two most common property wrappers to definen sources of truth are @State and @StateObject.

To refer to sources of truth, use @Binding or @ObservedObject when you want to make changes. If you just want to display immutable data, use let.

Here’s an example from our code where we define sources of truth:

The
The List displays values in items; A sheet is/is not presented based on the value of showCart

And here’s how we pass those sources of truth to sub-views that can modify and view the state:

•
@Binding lets us change the state of the cart and have those changes reflected across the app.

Using these principles, you can pass data around your app. If you want more global data, do some research into .environmentObject() and @StateObject.

Next Steps

  1. Look at the code for the sample project, linked at the top of this block.
  2. Check out the Next Steps block at the bottom of this page for a list of resources.

First Steps Tutorial

Installing Xcode

To get started with iOS App Development you’ll want to download and install Xcode. You can use the App Store, but downloading directly from Apple’s site works best.

When you open Xcode for the first time, just select iOS and MacOS (not WatchOS nor tvOS) to install for sake of simplicity. If you decide you need a watch or TV app later you can always add those components.

image

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.
image
When you first open Xcode you should see something like this:
image
  • 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 a struct named ContentView of type View. Inside, is a computed property named body of type some View; meaning the body can be any type of view (Text, Image, VStack, etc.). Right now, the body is a VStack.
  • All objects in SwiftUI are eventually Views. Keep this in mind if you ever are trying to return a String or an Integer instead of a Text view for example.
  • struct ContentView: View {
        var body: some View {
            VStack {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundColor(.accentColor)
                Text("Hello, world!")
            }
            .padding()
        }
    }
  • Let’s start by modifying the Text view. You can add .font(.title) and .foregroundColor(.green) below it, just like .imageScale(...) and .foregroundColor(...) on the Image view above it. Your text should now be larger and in green. You’ve written your first SwiftUI code!

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()
Updated body property.

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#>)
        }
    }
}
You’ll note we didn’t actually solve the ContentView yet. That’s because we’ll need to iterate over data.

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)
}
Our List should look like this.

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)
    }
	}
}
New version of the body property; now with Navigation.

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

MyFirstApp.zip33.8KB

This is the version built using the above tutorial.

Next Steps

This little tutorial barely scratches the surface of what SwiftUI offers. This is a list of resources for learning more and developing your skills beyond what you’ll be able to do during your time in Oasis.

🧡
What you do have are the basic foundations to start learning more on your own! Take advantage of different resources to further your learning, and please reach out if you feel you’ve gotten stuck and want a helping hand.

Apple Developer Documentation

Documentation can look daunting, but it’s your best friend. It’s the source of truth and the primary for how everything is supposed to work. Even at its most sparse, you can still see what the signatures for different functions are and it’s organized in a relatively discoverable way so that you can go from what you don’t know out to what you might.

This links to all of the SwiftUI documentation.
This tutorial covers all of the basics and is my first recommendation for where to go next.
This tutorial discusses how to pass data around your app.

Helpful Blogs

This blog has handy and approachable tips and tricks that I use all the time.
YouTube channel with How-to’s for a ton of APIs if videos are more your thing.