In this installment of our SwiftUI tutorial series, we’ll make our categories list dynamic by displaying a unique image and title on each card. Then we’ll add navigation to our Categories list.
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 a look at what we’ll create in this section.
Since we want each CategoryCard to receive a different name, we’ll start by creating a category name parameter within our CategoryCard view.
struct CategoryCard: View {
let geometry: GeometryProxy
let categoryName: String
Now we can use our categoryName in place of the string “Business” within our CategoryCard.
Text(categoryName) // Use categoryName in place of our static string
.font(.headline)
.foregroundColor(Color.white)
.padding(12)
We’ll see an error in our CategoryRow view because we haven’t supplied an
argument to the categoryName
parameter. Let’s fix that. For the first
CategoryCard, we’ll set categoryName
equal to categoryNameLeft
. For the
second, we’ll set categoryName
equal to categoryNameRight
. Later, we’ll pass
in values for categoryCardLeft
and categoryCardRight
from our Categories
view.
HStack {
CategoryCard(geometry: geometry, categoryName: categoryNameLeft)
CategoryCard(geometry: geometry, categoryName: categoryNameRight)
}
Now we’ll get the error: Use of unresolved identifier
on both
CategoryCards. To fix this, we’ll add parameters for categoryCardLeft
and
categoryCardRight
to CategoryRow.
struct CategoryRow: View {
let geometry: GeometryProxy
let categoryNameLeft: String // Add parameter for categoryNameLeft
let categoryNameRight: String // Add parameter for categoryNameRight
var body: some View {
We have one more error to deal with because we haven’t provided arguments for
categoryNameLeft
and categoryNameRight
when we use CategoryRow
. Let’s fix
that, too.
ScrollView {
VStack {
CategoryRow(geometry: geometry, categoryNameLeft: "Business", categoryNameRight: "Science")
CategoryRow(geometry: geometry, categoryNameLeft: "Sports", categoryNameRight: "Opinion")
CategoryRow(geometry: geometry, categoryNameLeft: "Finance", categoryNameRight: "Politics")
CategoryRow(geometry: geometry, categoryNameLeft: "Health", categoryNameRight: "Arts")
}
Looking good! Now we want to show a different image for each category. Download the rest of the images here and add them to your Assets using the process from Part Two.
Now that we have access to our images, all we have to do is lowercase our
categoryName
and use that value, instead of the string “business”, to
reference our category image.
var body: some View {
ZStack(alignment: .bottomTrailing) {
Image(categoryName.lowercased()) // Replace our static image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width * 0.45, height: geometry.size.width * 0.55)
Text(categoryName)
Our Categories view is almost complete! All that’s left is to add some navigation.
In our Categories view, outside GeometryReader, we’ll add a NavigationView
.
struct Categories: View {
var body: some View {
NavigationView { // Add NavigationView
GeometryReader { geometry in
ScrollView {
You’ll notice some white space appear at the top of our Categories view. This
is where our navigation bar title would go. However, we don’t want a title
on this screen given that the title is in our TabView. We can hide this space
by using the .navigationBarTitle()
and .navigationBarHidden()
methods.
NavigationView {
GeometryReader { geometry in
ScrollView {
VStack {
CategoryRow(geometry: geometry, categoryCardLeft: "Business", categoryCardRight: "Science")
CategoryRow(geometry: geometry, categoryCardLeft: "Sports", categoryCardRight: "Opinion")
CategoryRow(geometry: geometry, categoryCardLeft: "Finance", categoryCardRight: "Politics")
CategoryRow(geometry: geometry, categoryCardLeft: "Health", categoryCardRight: "Arts")
}
.padding()
}
.navigationBarTitle("Categories") // Set the navigation bar title
.navigationBarHidden(true) // Hide the navigation bar title
}
}
We still have to provide a navigation bar title, but now it will be hidden on
this screen. Note that we must use the .navigationBarTitle()
and
.navigationBarHidden()
methods within NavigationView
.
Next, we’ll wrap each of our Category Cards in a NavigationLink
. We have to
provide a destination
for each NavigationLink, so for now we’ll just use a
Text view with an empty string.
var body: some View {
HStack {
NavigationLink(destination: Text("")) {
CategoryCard(geometry: geometry, categoryName: categoryNameLeft)
}
NavigationLink(destination: Text("")) {
CategoryCard(geometry: geometry, categoryName: categoryNameRight)
}
}
}
It appears the Blue Man Group has invaded our device. To fix our category card
styling, we can change the buttonStyle
of our navigation links.
HStack {
NavigationLink(destination: Text("")) {
CategoryCard(geometry: geometry, categoryName: categoryCardLeft)
}
NavigationLink(destination: Text("")) {
CategoryCard(geometry: geometry, categoryName: categoryCardRigh
}
}
.buttonStyle(PlainButtonStyle()) // Change button style of navigation links
We can now click on each Category and be brought to a blank screen. As a final step, we’ll swap out this blank screen and instead show the name of the clicked category.
First, we’ll create a view for our Category screen. We don’t want any content on
this page besides the navigation bar title, so we’ll just add a VStack
with an
empty Text view.
struct Category: View {
var body: some View {
VStack {
Text("")
}
}
}
Now let’s supply our new Category view to our NavigationLink destination parameters.
HStack {
NavigationLink(destination: Category()) { // Change the destination to Category()
CategoryCard(geometry: geometry, categoryName: categoryNameLeft)
}
NavigationLink(destination: Category()) { // Change the destination to Category()
CategoryCard(geometry: geometry, categoryName: categoryNameRight)
}
}
To display the category name in the Category view, we’ll just pass the
categoryName
through to the Category View and supply it to the
navigationBarTitle
method.
HStack {
NavigationLink(destination: Category(categoryName: categoryNameLeft)) { // Pass the categoryName to Category()
CategoryCard(geometry: geometry, categoryName: categoryNameLeft)
}
NavigationLink(destination: Category(categoryName: categoryNameRight)) { // Pass the categoryName to Category()
CategoryCard(geometry: geometry, categoryName: categoryNameRight)
}
}
struct Category: View {
let categoryName: String // Add a parameter for the category name
var body: some View {
VStack {
Text("")
}
.navigationBarTitle(categoryName) // Use the category name
}
}
That’s it for this section! See you for Part Five, in which we’ll create our Profile view.