After going through the previous chapter, I believe you should now understand how to build a complex UI using stacks. Of course, it will take you a lot of practice before you can master SwiftUI. Therefore, before we dive deep into ScrollView to make the views scrollable, let's begin this chapter with a challenge. Your task is to create a card view like that shown in figure 1.

By using stacks, image, and text views, you should be able to create the UI. While I will go through the implementation step by step with you later, please take some time to work on the exercise and figure out your own solution.
Once you create the card view, I will discuss ScrollView with you and build a scrollable interface using the card view. Figure 2 shows you the complete UIs.

If you haven't opened Xcode, fire it up and create a new project using the App template (under iOS). In the next screen, set the product name to SwiftUIScrollView (or whatever name you like) and fill in all the required values. Make sure you select SwiftUI for the Interface option.
So far, we have coded the user interface in the ContentView.swift file. It's very likely you wrote your solution code there. That's completely fine, but I want to show you a better way to organize your code. For the implementation of the card view, let's create a separate file. In the project navigator, right click SwiftUIScrollView and choose New File...

In the User Interface section, choose the SwiftUI View template and click Next to create the file. Name the file CardView and save it in the project folder.

The code in CardView.swift looks very similar to that of ContentView.swift. Similarly, you can preview the UI in the canvas.

Now we're ready to code the card view. But first, you need to prepare the image files and import them in the asset catalog. If you don't want to prepare your own images, you can download the sample images from https://www.appcoda.com/resources/swiftui/SwiftUIScrollViewImages.zip. Once you unzip the image archive, select Assets and drag all the images to the asset catalog.

Now switch back to the CardView.swift file. If you look at figure 1 again, the card view is composed of two parts: the upper part is the image and the lower part is the text description.
Let's start with the image. I'll make the image resizable and scale it to fit the screen while retaining the aspect ratio. You write the code like this:
struct CardView: View {
var body: some View {
Image("swiftui-button")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
If you forgot what these two modifiers do, go back and read the chapter about the Image view. Next, let's implement the text description. You may write the code like this:
VStack(alignment: .leading) {
Text("SwiftUI")
.font(.headline)
.foregroundColor(.secondary)
Text("Drawing a Border with Rounded Corners")
.font(.title)
.fontWeight(.black)
.foregroundColor(.primary)
.lineLimit(3)
Text("Written by Simon Ng".uppercased())
.font(.caption)
.foregroundColor(.secondary)
}
You need to use Text to create the text view. Since we actually have three text views in the description, that are vertically aligned, we use a VStack to embed them. For the VStack, we specify the alignment as .leading. This will align the text view to the left of the stack view.
The modifiers of Text are all discussed in the chapter about the Text object. You can refer to it if you find any of the modifiers are confusing. But one topic about the .primary and .secondary colors should be highlighted.
While you can specify a standard color like .black and .purple in the foregroundColor modifier, iOS provides a set of system colors that contain primary, secondary, and tertiary variants. By using these color variants, your app can easily support both light and dark modes. For example, the primary color of the text view is set to black in light mode by default. When the app is switched over to dark mode, the primary color will be adjusted to white. This is automatically arranged by iOS, so you don't have to write extra code to support the dark mode. We will discuss dark mode in depth in a later chapter.
To arrange the image and these text views vertically, we use a VStack to embed them. The current layout is shown in the figure below.

We are not done yet! There are still a couple of things we need to implement. First, the text description block should be left aligned to the edge of the image.
How do you do that?
Based on what we've learned, we can embed the VStack of the text views in a HStack. And then, we will use a Spacer to push the VStack to the left. Let's see if this works.
If you've changed the code to match the one shown in figure 8, the VStack of the text views are aligned to the left of the screen.

It would be better to add some padding around the HStack. Insert the padding modifier like this (line 34 in figure 9) :

Lastly, it's the border. We have discussed how to draw a border with rounded corners in an earlier chapter. We use the overlay modifier and draw the border using RoundedRectangle. Here is the complete code:
struct CardView: View {
var body: some View {
VStack {
Image("swiftui-button")
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
VStack(alignment: .leading) {
Text("SwiftUI")
.font(.headline)
.foregroundColor(.secondary)
Text("Drawing a Border with Rounded Corners")
.font(.title)
.fontWeight(.black)
.foregroundColor(.primary)
.lineLimit(3)
Text("Written by Simon Ng".uppercased())
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}
.padding()
}
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(.sRGB, red: 150/255, green: 150/255, blue: 150/255, opacity: 0.1), lineWidth: 1)
)
.padding([.top, .horizontal])
}
}
In addition to the border, we also add some padding for the top, left, and right sides. Now you have completed the card view layout.

While the card view works, we've hard-coded the image and text. To make it more flexible, let's refactor the code. First, declare these variables for the image, category, heading, and author in CardView:
var image: String
var category: String
var heading: String
var author: String
Next, replace the values of the Image and Text views with the variables like this:
VStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
VStack(alignment: .leading) {
Text(category)
.font(.headline)
.foregroundColor(.secondary)
Text(heading)
.font(.title)
.fontWeight(.black)
.foregroundColor(.primary)
.lineLimit(3)
Text("Written by \(author)".uppercased())
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}
.padding()
}
Once you made the changes, you will see an error in the CardView_Previews struct. This is because we've introduced some variables in CardView. We have to specify the parameters when using it.

Modify the code like this:
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView(image: "swiftui-button", category: "SwiftUI", heading: "Drawing a Border with Rounded Corners", author: "Simon Ng")
}
}
This should fix the error. Great! You have built a flexible CardView that accepts different images and text.
Take a look at figure 2 again. That's the user interface we're going to implement. At first, you may think we can embed four card views using a VStack. You can switch over to ContentView.swift and insert the following code:
VStack {
CardView(image: "swiftui-button", category: "SwiftUI", heading: "Drawing a Border with Rounded Corners", author: "Simon Ng")
CardView(image: "macos-programming", category: "macOS", heading: "Building a Simple Editing App", author: "Gabriel Theodoropoulos")
CardView(image: "flutter-app", category: "Flutter", heading: "Building a Complex Layout with Flutter", author: "Lawrence Tan")
CardView(image: "natural-language-api", category: "iOS", heading: "What's New in Natural Language API", author: "Sai Kambampati")
}
If you did that, the card views will be squeezed to fit the screen because VStack is non-scrollable, just like that shown in figure 12.

To support scrollable content, SwiftUI provides a view called ScrollView. When the content is embedded in a ScrollView, it becomes scrollable. What you need to do is to enclose the VStack within a ScrollView to make the views scrollable. In the preview canvas, you can drag the views to scroll the content.

Your task is to add a header to the existing scroll view. The result is displayed in figure 14. If you understand VStack and HStack thoroughly, you should be able to create the layout.

By default, the ScrollView allows you to scroll the content in vertical orientation. Alternatively, it also supports scrollable content in horizontal orientation. Let's see how to convert the current layout into a carousel UI with a few changes.
Update the ContentView like this:
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
// Your code for exercise #1
HStack {
CardView(image: "swiftui-button", category: "SwiftUI", heading: "Drawing a Border with Rounded Corners", author: "Simon Ng")
.frame(width: 300)
CardView(image: "macos-programming", category: "macOS", heading: "Building a Simple Editing App", author: "Gabriel Theodoropoulos")
.frame(width: 300)
CardView(image: "flutter-app", category: "Flutter", heading: "Building a Complex Layout with Flutter", author: "Lawrence Tan")
.frame(width: 300)
CardView(image: "natural-language-api", category: "iOS", heading: "What's New in Natural Language API", author: "Sai Kambampati")
.frame(width: 300)
}
}
}
}
We've made three changes in the code above:
ScrollView to use a horizontal scroll view by passing it a .horizontal value.VStack to HStack.After changing the code, you'll see the card views are arranged horizontally and they are scrollable.

While you're scrolling the views, did you notice there is a scroll indicator near the bottom of the screen? This indicator is displayed by default. If you want to hide it, you can change the ScrollView by adding showsIndicators: false to it:
ScrollView(.horizontal, showsIndicators: false)
By setting showIndicators to false, iOS will no longer show the indicator.
If you look at the code again, you will see that all the CardViews have a .frame modifier to limit their width to 300 points. Is there any way we can simplify that and remove the duplicated code? The SwiftUI framework provides a Group view for developers to group related content. More importantly, you can attach modifiers to the group to apply the changes to each of the views embedded in the group.
For example, you can rewrite the code in HStack like this to achieve the same result:
HStack {
Group {
CardView(image: "swiftui-button", category: "SwiftUI", heading: "Drawing a Border with Rounded Corners", author: "Simon Ng")
CardView(image: "macos-programming", category: "macOS", heading: "Building a Simple Editing App", author: "Gabriel Theodoropoulos")
CardView(image: "flutter-app", category: "Flutter", heading: "Building a Complex Layout with Flutter", author: "Lawrence Tan")
CardView(image: "natural-language-api", category: "iOS", heading: "What's New in Natural Language API", author: "Sai Kambampati")
}
.frame(width: 300)
}
As you can see in figure 15, the title of the first card is truncated. How do you fix this? In SwiftUI, you can use the .minimumScaleFactor modifier to automatically downscale text. Switch over to CardView.swift and attach the following modifier to Text(heading):
.minimumScaleFactor(0.5)
SwiftUI will automatically scale down the text to fit the available space. The value sets the minimum amount of scaling that the view permits. In this case, SwiftUI can draw the text in a font size as small as 50% of the original font size.
Here comes to the final exercise. Modify the current code and re-arrange it like that shown in figure 16. Please note that the title and the date should be visible to users when he/she scrolls through the card views.

For reference, you can download the complete scrollview project here: