SwiftUI Prototype Tutorial 5 of 5: Profile View

Devin Jameson

In the final installment of our SwiftUI tutorial series, we’ll create our Profile view.

Posts in this series:

Here’s how our prototype will look at the end of this section.

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Above this headline on the left is the text "Premium Member" in gray. Above the headline on the right is a circular image of Devin Jameson. Below the headline are three selectable rows stacked on top of one another. Each row has text on the left and a gray, right-facing arrow on the right. Between each row of text is a grey dividing line. From top to bottom, the text for each row reads "Saved Articles", "Followers", and "Following".

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.

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Below this headline, in the middle of the screen, is the text "Hello, World!"

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")
    }
}

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Below this headline, in the middle of the screen, are three lines of text stacked on top of one another. From top to bottom, they read "Saved Articles", "Followers", and "Following".

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))
        }
    }
}

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Below this headline, in the middle of the screen, are three lines of text stacked on top of one another. From top to bottom, they read "Saved Articles", "Followers", and "Following". These lines of text are positioned on the far left of the screen. On the right side of the screen, parallel to each line of text, is a gray arrow pointing right.

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
        }
    }
}

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Below this headline, in the middle of the screen, are three lines of text stacked on top of one another. From top to bottom, they read "Saved Articles", "Followers", and "Following". These lines of text are positioned on the far left of the screen. On the right side of the screen, parallel to each line of text, is a gray arrow pointing right. Below each line of text is a thin grey dividing line.

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()

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Below this headline, in the middle of the screen, are three lines of text stacked on top of one another. From top to bottom, they read "Saved Articles", "Followers", and "Following". These lines of text are positioned on the far left of the screen. On the right side of the screen, parallel to each line of text, is a gray arrow pointing right. Below each line of text is a thin grey dividing line. Comapred to the previous image, there is more space between each line of text.

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 prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The categories tab is blue and the profile tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Above this headline in the middle of the screen are three lines of text stacked on top of one another. From top to bottom, they read "Saved Articles", "Followers", and "Following". These lines of text are positioned on the far left of the screen. On the right side of the screen, parallel to each line of text, is a gray arrow pointing right. Below each line of text is a thin grey dividing line. Comapred to the previous image, there is less space between each line of text.

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))

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The categories tab is blue and the profile tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Below this headline, in the middle of the screen, are three lines of text stacked on top of one another. From top to bottom, they read "Saved Articles", "Followers", and "Following". These lines of text are positioned on the far left of the screen. On the right side of the screen, parallel to each line of text, is a gray arrow pointing right. Compared to the previous image, these gray arrows are smaller. Below each line of text is a thin grey dividing line.

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()
}

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The categories tab is blue and the profile tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Below this headline are three lines of text stacked on top of one another. From top to bottom, they read "Saved Articles", "Followers", and "Following". These lines of text are positioned on the far left of the screen. On the right side of the screen, parallel to each line of text, is a gray arrow pointing right. Below each line of text is a thin grey dividing line.

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))

The prototype we'll be creating, previewed on an iPhone 11 Pro Max. A cursor is clicking on the screen to navigate to different views. The prototype has two tabs shown at the bottom of the screen: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. The Profile view contains a headline with the name "Devin Jameson". Above the bottom tab bar are three selectable rows stacked on top of one another. Each row has text on the left and a gray, right-facing arrow on the right. Between each row of text is a grey dividing line. From top to bottom, the text for each row reads "Saved Articles", "Followers", and "Following". The cursor is clicking on each of these rows, which brings us to a new screen. On the top left of this screen is a blue back button with the text "Categories". The bottom tab bar is still on this screen. The cursor then clicks on this back button to navigate to the prior screen.

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)),
)

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Above this headline on the left is the text "Premium Member" in gray. Below the headline are three selectable rows stacked on top of one another. Each row has text on the left and a gray, right-facing arrow on the right. Between each row of text is a grey dividing line. From top to bottom, the text for each row reads "Saved Articles", "Followers", and "Following".

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

Our prototype running on iPhone 11 Pro Max. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The profile tab is blue and the categories tab is black. Above the bottom tab bar is the Profile view. The Profile view contains a headline with the name "Devin Jameson". Above this headline on the left is the text "Premium Member" in gray. Above the headline on the right is a circular image of Devin Jameson. Below the headline are three selectable rows stacked on top of one another. Each row has text on the left and a gray, right-facing arrow on the right. Between each row of text is a grey dividing line. From top to bottom, the text for each row reads "Saved Articles", "Followers", and "Following".

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.