In the final installment of our SwiftUI tutorial series, we’ll create our Profile view.
Posts in this series:
- Part 1: Project Setup
- Part 2: Category Card View
- Part 3: Categories List
- Part 4: Dynamic Categories & Navigation
- Part 5: Profile View
Here’s how our prototype will look at the end of this section.
First, let’s make a new file for our Profile code following the same process we used to create our Categories file in Part Two.
To get our Live Preview working, we’ll replace Profile()
on line 19 with
ContentView()
.
struct Profile_Previews: PreviewProvider {
static var previews: some View {
ContentView() // Replace Profile() with ContentView()
}
}
Back in ContentView.swift, we’ll wire up our Profile tab item to our Profile
view by replacing Text("Second View")
with Profile()
.
Categories()
.font(.title)
.tabItem {
VStack {
Image(systemName: "globe")
Text("Categories")
}
}
.tag(0)
Profile() // Change Text("Second View") to Profile()
.font(.title)
.tabItem {
We’ll also change the default tab of our TabView to show the Profile view by
providing 1
to our selection parameter instead of 0
.
struct ContentView: View {
@State private var selection = 1
var body: some View {
Now let’s add a NavigationView to our Profile view with our name as the title.
struct Profile: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.navigationBarTitle("Devin Jameson")
}
}
}
Notice that we use .navigationBarTitle()
within NavigationView
.
Next, we’ll add some content to our Profile view. Since we want to display three
navigation links here, we can start by creating an array that stores the names
of our links. We’ll call this variable profileLinkNames.
struct Profile: View {
let profileLinkNames: [String] = ["Saved Articles", "Folowers", "Following"]
Now we can list these strings using something Swift provides called ForEach
,
which enables us to provide dynamic content to views. We’ll put the ForEach
in
a VStack
and remove Text("Hello, World!")
.
let profileLinkNames: [String] = ["Saved Articles", "Followers", "Following"]
var body: some View {
NavigationView {
VStack {
ForEach(profileLinkNames, id: \.self) { profileLinkName in
Text(profileLinkName)
.font(.body)
}
}
.navigationBarTitle("Devin Jameson")
}
}
You’ll notice we include the id
parameter in ForEach
. Swift requires this
id
so it can differentiate between elements in our array when making updates
to the UI. Providing the argument \.self
gives each element an id equal to its
own value, which is fine in this case.
Now that we’re displaying some items in our Profile view, we can move on to
styling them. Let’s begin by adding a chevron right symbol next to each item.
We’ll embed the Text and Image views in an HStack
add a Spacer()
view in
between them to spread them apart.
VStack {
ForEach(profileLinkNames, id: \.self) { profileLinkName in
HStack { // Embed it all in an HStack
Text(profileLinkName)
.font(.body)
Spacer() // Spread the Text and Image views apart
Image(systemName: "chevron.right") // Add symbol
.foregroundColor(Color(.systemGray3))
}
}
}
Now let’s add a Divider()
view after our HStack
and embed the HStack
in a
VStack
. This will give us a divider underneath each profile link.
VStack {
ForEach(profileLinkNames, id: \.self) { profileLinkName in
VStack { // Embed both the HStack and Divider in a VStack
HStack {
Text(profileLinkName)
.font(.body)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(Color(.systemGray3))
}
Divider() // Add a divider
}
}
}
Now for some padding around each HStack
.
HStack {
Text(profileLinkName)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(Color(.systemGray3))
}
.padding(EdgeInsets(top: 17, leading: 21, bottom: 17, trailing: 21))
Divider()
It’s not obvious, but behind the scenes, each VStack
in our view is adding
some extra spacing between our profile links. Let’s tell each VStack
not to do
that.
NavigationView {
VStack(spacing: 0) {
ForEach(profileLinkNames, id: \.self) { profileLinkName in
VStack(spacing: 0) {
HStack {
Our chevron right symbol also looks a little big , so we’ll make that smaller.
Image(systemName: "chevron.right")
.foregroundColor(Color(.systemGray3))
.font(.system(size: 20))
You might notice that our Profile view code is becoming hard to read. Let’s clean it up.
Command + click on the VStack
just beneath ForEach
and select Extract
Subview
. Boom! We now have a separate subview for each profile link. Let’s
rename this subview from ExtractedView
to ProfileLink
. Right click on
ExtractedView
and select Refactor > Rename. Type ProfileLink
and hit enter.
Your ProfileLink view code should now look like this.
struct ProfileLink: View {
var body: some View {
VStack(spacing: 0) {
HStack {
Text(profileLinkName)
.font(.body)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(Color(.systemGray3))
.font(.system(size: 20))
}
.padding(EdgeInsets(top: 17, leading: 21, bottom: 17, trailing: 21))
Divider()
}
}
}
Now we just have to pass our profileLinkName
to the ProfileLink view.
ForEach(profileLinkNames, id: \.self) { profileLinkName in
ProfileLink(profileLinkName: profileLinkName) // Pass profileLinkName to ProfileLink
}
struct ProfileLink: View {
let profileLinkName: String // Add parameter for profileLinkName
var body: some View {
VStack(spacing: 0) {
HStack {
Text(profileLinkName)
.font(.body)
To finish up our link styling, we’ll add another Spacer()
below our ForEach
to push our links to the top of our VStack
.
VStack(spacing: 0) {
ForEach(profileLinkNames, id: \.self) { profileLinkName in
ProfileLink(profileLinkName: profileLinkName)
}
Spacer()
}
Looking good!
Now we can wire up our navigation. Let’s wrap our ProfileLink view in a
NavigationLink
and set the destination to an empty Text view. To prevent our
profile links from turning blue, we’ll use the buttonStyle()
method on our
NavigationLink
.
NavigationLink(destination: Text("")) {
VStack(spacing: 0) {
HStack {
Text(profileLinkName)
.font(.body)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(Color(.systemGray3))
.font(.system(size: 20))
}
.padding(EdgeInsets(top: 17, leading: 21, bottom: 17, trailing: 21))
Divider()
}
}
.buttonStyle(PlainButtonStyle())
To keep things simple, we’ll leave the navigation link destination as an empty Text view.
We have a small issue now. If we try to navigate to one of the profile links, we
can only do so by clicking directly on the text or symbol. Clicking in the white
space in between them does nothing. To fix this, we can use the contentShape()
method on our HStack
.
HStack {
Text(profileLinkName)
.font(.body)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(Color(.systemGray3))
.font(.system(size: 20))
}
.contentShape(Rectangle()) // Defining the shape of the HStack
.padding(EdgeInsets(top: 17, leading: 21, bottom: 17, trailing: 21))
Now let’s handle the rest of the navigation bar elements.
To add content to the navigation bar area, we can use the navigationBarItems
method in our NavigationView
. This method takes arguments for leading
and
trailing
parameters, to which we can provide Views.
Let’s add the text Premium Member
within the leading view and leave the
trailing view out for now.
.navigationBarTitle("Devin Jameson")
.navigationBarItems(
leading: // Add our leading view
Text("Premium Member")
.font(.body)
.foregroundColor(Color(.systemGray)),
)
Getting close! Now we can add our avatar image to our trailing view. First, add your personal avatar image to your Assets in Xcode and name it whatever you want.
Now let’s add this avatar to our trailing view. We’ll also make it resizable, provide a size, and put it inside a circular clip shape.
.navigationBarItems(
leading:
Text("Premium Member")
.font(.body)
.foregroundColor(Color(.systemGray)),
trailing: // Add trailing view
Image("avatar")
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())) // Clip the image to a circle
This concludes our journey with SwiftUI! By leveraging Apple’s native tools and APIs, we were able to whip up an extremely high-resolution prototype with only a small amount of code.
I hope you learned something useful in this series! Tweet me @devinjameson if you have any questions or comments.
Bonus: our prototype is fully Dark Mode compatible! To see it in action, click the play button on the top left of the XCode window. When the app starts in Simulator, navigate to Features > Toggle Appearance.