Apple’s all-new declarative UI framework SwiftUI is here! It is not likely that adoption will be high in a professional environment for a little while, however, it is still fun to play with this new framework which could revolutionize app development. SwiftUI has some very powerful capabilities built in, such as the cross-device compatibility, automatic dynamic type support, automatic dark mode support, built in localization and accessibility features. In this tutorial, we will be creating a small app where we will display a list of safari animals and then be able to see details on our selected animal.
Let’s start by creating our project, make sure that when you are setting up you select the “Use SwiftUI” tickbox. This will set up your default project with a different looking AppDelegate and a new SceneDelegate.swift file.
First of all, we want to create our Animal model file, which will contain all of our attributes for our given animal. Our Animal is going to conform to the Identifiable protocol, by having an id Int variable and also have a name, description and imageName. We also will have a dynamic variable which will return an Image object (this is comparable to a UIImageView in UIKit).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: – Frameworks | |
import SwiftUI | |
// MARK: – Animal | |
struct Animal: Hashable, Identifiable { | |
// MARK: – Variables | |
var id: Int | |
var name: String | |
var description: String | |
var imageName: String | |
// MARK: – Dynamic Varaibles | |
var image: Image { | |
return Image(imageName) | |
} | |
} |
Then we will add this extension file on Animal, containing static constants of animals that we are going to hardcode into our app. Here we are going to just add four animals, however, feel free to add more yourself. Don’t forget to add an image to the project for each animal you add.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: – Animal Constants | |
extension Animal { | |
static let all: [Animal] = [.lion, .elephant, .giraffe, .zebra] | |
static let lion = Animal(id: 1, name: "Lion", | |
description: "Biggest cat in Africa, the king of the jungle.", | |
imageName: "lion") | |
static let elephant = Animal(id: 2, name: "Elephant", | |
description: "Huge plant eating animal with long nose.", | |
imageName: "elephant") | |
static let giraffe = Animal(id: 3, name: "Giraffe", | |
description: "Like a cow with a very long neck.", | |
imageName: "giraffe") | |
static let zebra = Animal(id: 4, name: "Zebra", | |
description: "A horse with black and white stripes.", | |
imageName: "zebra") | |
} |
Now we have our animal information in our app, we can work with our new framework SwiftUI. Let’s start by adding this code as AnimalList.swift, here we are going to create our list of animals that when tapped will navigate us through to our details page.
First of all, take a good look at the syntax below. All of the Navigation and View objects are carefully nested in a specific order. We have the NavigationView encompassing everything at the top layer, inside this, we have a List which takes an array of our Animal objects. The list is composed of NavigationButtons which have a destination of our AnimalDetails view. Inside the NavigationButton we have our AnimalRow to give us our list. You will also notice the navigation bar title modifier which changes the title attributes of our NavigationView.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: – Frameworks | |
import SwiftUI | |
// MARK: – AnimalList | |
struct AnimalList: View { | |
var body: some View { | |
NavigationView { | |
List(Animal.all) { animal in | |
NavigationButton(destination: AnimalDetail(animal: animal)) { | |
AnimalRow(animal: animal) | |
} | |
} | |
.navigationBarTitle(Text("Animals"), displayMode: .large) | |
} | |
} | |
} |
For each row in the list, we need to create our AnimalRow view. To do this we will create AnimalRow.swift, which will be instantiated with an animal and simply display the name of the animal.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: – Frameworks | |
import SwiftUI | |
// MARK: – AnimalRow | |
struct AnimalRow: View { | |
var animal: Animal | |
var body: some View { | |
HStack { | |
Text(animal.name) | |
Spacer() | |
} | |
} | |
} |
Then we need to create our details page, our details page takes an animal model and uses that to display relevant information on the page. Here we create a VStack (vertical stack view) and add a CircleImage and another VStack. Our second VStack will contain our Text elements (comparable to UILabel’s) with animal name and description. We also add some spacing and padding to help let the views breath a little bit.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: – Frameworks | |
import SwiftUI | |
// MARK: – AnimalDetail | |
struct AnimalDetail: View { | |
var animal: Animal | |
var body: some View { | |
VStack { | |
Spacer() | |
CircleImage(image: animal.image) | |
.offset(x: 0, y: -130) | |
.padding(.bottom, -130) | |
VStack(alignment: .leading) { | |
Text(animal.name) | |
.font(.title) | |
Text(animal.description) | |
.font(.subheadline) | |
} | |
.padding() | |
Spacer() | |
} | |
} | |
} |
Finally, we will add this simple supporting view which will help us elegantly display the image of our animal. This view takes an image, clips it to the shape of a circle, overlays a white circle around the image and then provides a shadow underneath to give a perception of depth. This creates a great visual effect for your users, all inside a handy little self-contained view.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: – Frameworks | |
import SwiftUI | |
// MARK: – CircleImage | |
struct CircleImage: View { | |
var image: Image | |
var body: some View { | |
image | |
.clipShape(Circle()) | |
.overlay(Circle().stroke(Color.white, lineWidth: 4)) | |
.shadow(radius: 12) | |
} | |
} |
That is it, we are all done and now if you build the project you should see your first SwiftUI app running. This is only a very simple example and my first time using SwiftUI. It will be exciting to see how others use SwiftUI for much more complicated use cases, as I can already see how powerful this framework is.
The source code for the finished SwiftUI example project is available here: