Our March code-alongs we will dedicate to different options to create a ScrollView which showcase the images of several animals and their names. To organise the app, we will use TabView
to have all the different views in a tab-based navigation. The today’s result will look the following:

Let’s get started!
Step 1: Set up your project
- Open Xcode: Launch Xcode and select Create a new Xcode project.
- Choose Template: Select App under the iOS tab and click Next.
- Name Your Project: Enter a name for your project, like
DifferentScrollViews
. Choose SwiftUI for the interface and Swiftfor the language. Click Next, and then save your project.
When you open your project, you’ll see the already familiar standard code presenting a globe and the Text “Hello, world!” in the ContentView.swift
. This file will be our “Home”-View which will present all the other views by using a TabView
. This means that the content will be in other files which will be presented here.
Step 2: Preparing the animal views
Before creating the first ScrollView, we need to define our AnimalPhoto
-Views that will be shown in our scrollviews for each and every animal.
Before continuing, please download the animal images here:
Unzip it and from your file explorer, move all the animal images over to your Xcode project into Assets.xcassets
. It should look like this:

Create an AnimalPhoto
We will create a new SwiftUI file. Go to File - New - File from Template...
or simply press cmd + N
, choose iOS Template
and here SwiftUI View
, click Next
, choose AnimalPhoto
as the name of the new view, ensure that DifferentScrollViews
is selected under Group
, check the tick box for Targets: DifferentScrollViews
, click Create
.
You will see the familiar structure. This time just showing the text “Hello, World!” in a Text
view. Replace Text
by:
Image("elephant")
.resizable()
.scaledToFit()
.frame(width: 300)
.cornerRadius(12)
You got to know Image
in our first code-along “Button Animation”. .scaledToFit()
resizes the image to fit to the available space. We provide here a frame of width 300
i.e. our image will have a width of 300
. The modifier .cornerRadius(12)
rounds the edges of the image with a radius of 12
.
So, we see already our cute litte elephant. However, here we have defined the animal shown in this image in a static way. We want to re-use this view for all other animals as well.
Create an Animal model
Create a new swift file. Go to File - New - File from Template...
or simply press cmd + N
, choose iOS Template
and here Swift File
, click Next
, choose Animal
as the name of the new view, ensure that DifferentScrollViews
is selected under Group
, check the tick box for Targets: DifferentScrollViews
, click Create
.
import SwiftUI
struct Animal: Identifiable {
let id = UUID()
let imageName: String
static let all: [Animal] = [
"lion", "zebra","cheetah", "elephant",
"giraffe", "hippo", "meerkat", "ostrich",
"buffalo", "rhino", "wild-dog", "gorilla"
].map { Animal(imageName: $0) }
static let example = Animal(imageName: "elephant")
}
We define a struct
named Animal
and it confirms to the Identifiable protocol: This allows SwiftUI to uniquely identify each Animal when using e.g. ForEach
in lists, grids, etc.
id = UUID()
generates a unique identifier for each Animal instance and imageName: String´: Stores the name of the image (e.g., "lion", "zebra"), which can be used with
Image(imageName)`.
The static property ‘all’ defines a static array of Animal objects. It starts with an array of our animal names (strings) and .map { Animal(imageName: $0) }
converts each string into an Animal object. The .map {}
function transforms each element in a collection (such as an array) and returns a new array with modified values. ‘$0’ is Swift’s shorthand for the first parameter inside a closure. And this means that ‘.map { Animal(imageName: $0) }’ transforms each string (e.g. lion
) into an Animal object
(e.g. Animal(imageName: "lion")
).
The example
property provides a sample Animal instance that can be used for previews or testing. This avoids having to create test data manually in multiple places.
Updating the AnimalPhoto
We created the AnimalPhoto
-View to just present our cute little elephant. To show also all the other animals, we first need to define a new property called animal
by
let animal: Animal
It expects an Animal instance when AnimalPhoto is created. The passed Animal object will provide an image name (e.g., “lion”, “elephant”).
Now, replace "elephant"
by animal.imageName
(as defined in our Animal
struct) so that the full AnimalPhoto
View looks the following:
import SwiftUI
struct AnimalPhoto: View {
let animal: Animal
var body: some View {
Image(animal.imageName)
.resizable()
.scaledToFit()
.frame(width: 300)
.cornerRadius(12)
}
}
#Preview {
AnimalPhoto(animal: Animal.example)
}
We defined the elephant image as the example in our Animal
model and that’s why you see in the preview of the AnimalPhoto
a nice cute elephant image. This is just relevant for the preview. If you don’t provide the example, the Preview will not work and show an error.
Now we are ready to create our first simple scroll view.
Step 3: Create a simple ScrollView
Create a new swift file. Go to File - New - File from Template...
or simply press cmd + N
, choose iOS Template
and here SwiftUI View
, click Next
, choose SimpleScrollView
as the name of the new view, ensure that DifferentScrollViews
is selected under Group
, check the tick box for Targets: DifferentScrollViews
, click Create
.
Directly after the struct
line, before the var body
line, insert the following:
let animals = Animal.all
In the body
, we want to show a title and the scroll view underneath each other. Therefore, inside the existing VStack
replace the content and replace by:
Text("Simple ScrollView")
.font(.title.bold())
This creates our title. Next, we want to create a scroll view. Below the Text
, insert the following:
ScrollView(.horizontal, showsIndicators: true) {
}
.frame(height: 300)
.padding(.bottom, 30)
This creates a horizontal scroll view which also shows indicators (showsIndicators: true
) which is the little bar below the scroll view which indicates where you are in the horizontal scroll. Our scroll view got a height of 300
.
In this scroll view we want to show all our animal photos – i.e. showing AnimalPhoto
for all animals. You are already familiar with VStack
which vertically aligns views. HStack
is similar, just horizontally aligned views next to each other. A LazyHStack
is very similar to a HStack
. The difference is that a HStack
loads all elements at once when the view appears. A LazyHStack works like HStack but with “lazy” loading. It only loads views when they appear on-screen instead of all at once. And therefore it is better for performance in large datasets. Ours is not that large but still. Therefore we will be using a LazyHStack
.
Inside the LazyHStack
we want to show all our animal images and the name below the image. To run through the animals array we are using a ForEach
loop and create a repeated UI for each item. ForEach(animals) { animal in ... }
iterates through each Animal in the array. Inside the loop, we will use a VStack
for each animal to display the image by using the AnimalPhoto
we created earlier and a Text
View, i.e. our LazyHStack
looks the following:
LazyHStack(spacing: 20) {
ForEach(animals) { animal in
VStack {
AnimalPhoto(animal: animal)
.padding(.bottom, 12)
Text(animal.imageName.capitalized)
.font(.title2)
}
}
}
The modifier .capitalized
displays the name of the animal with the first letter capitalized.
Congrats, you created your first scroll view!
But we can do better. In this scroll view, the images scroll through and the scroll stops depending how much we swiped. There is no “snap” for the images. Such a “snap” provides a better scroll control and more haptic effect. We can create such a “pagination effect”. We add to the LazyHStack
after its closing curly bracket the modifier
.scrollTargetLayout()
.scrollTargetLayout()
marks items in a LazyHStack or LazyVStack as scrollable targets. This is necessary to allow SwiftUI understand the intended scrolling structure.
To the ScrollView
‘s closing curly bracket we add
.scrollTargetBehavior(.viewAligned)
This ensures that when scrolling stops, one AnimalPhoto is always centered. If you swipe left or right, SwiftUI automatically snaps the next AnimalPhoto to the center. This gives a smooth and user-friendly paging effect.
However, you will see, that the first item is not centered but at the very left edge of the screen. We need to move the LazyHStack
to the center of the screen with the appropriate width – which is (screenWidth - imageFrameWidth) / 2
. We want the imageFrameWidth
to be 80%
of the screen width. How do we get the screen width?
For this, we introduce GeometryReader
. ‘GeometryReader’ measures the size and position of its parent view and provides that information for layout calculations. GeometryReader
takes up all available space and measures its parent. Inside its closure, geometry provides
geometry.size.width
: width of the available space,geometry.size.height
: height of the available space.
Please wrap ScrollView
in the following code:
GeometryReader { geometry in
let screenWidth = geometry.size.width
let imageFrameWidth = screenWidth * 0.8
}
Now, add the padding that centers the first image of the LazyHStack
, i.e. below .scrollTargetLayout()
add:
.padding(.horizontal, (screenWidth - imageFrameWidth) / 2)
The full code should look the following:
Text("Pagination Effect")
.font(.title.bold())
GeometryReader { geometry in
let screenWidth = geometry.size.width
let imageFrameWidth = screenWidth * 0.8
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 0) {
ForEach(animals) { animal in
VStack {
AnimalPhoto(animal: animal)
.frame(width: imageFrameWidth)
Text(animal.imageName.capitalized)
.font(.title2)
}
}
}
.scrollTargetLayout()
.padding(.horizontal, (screenWidth - imageFrameWidth) / 2)
}
.scrollTargetBehavior(.viewAligned)
}
.frame(height: 300)
Congrats, you have created 2 simple ScrollViews! Let’s create another one.
Step 4: Create a simple TabView
Create a new SwiftUI file. Go to File - New - File from Template...
or simply press cmd + N
, choose iOS Template
and here SwiftUI View
, click Next
, choose SimpleTabView
as the name of the new view, ensure that DifferentScrollViews
is selected under Group
, check the tick box for Targets: DifferentScrollViews
, click Create
. (Please note: don’t use the name “TabView”. This is a standing term and creates hustles if you are creating a view with this name.)
As you have seen already in SimpleScrollView
, let’s define the animals
directly after the struct
line, before the var body
line, by inserting the following:
let animals = Animal.all
Inside the VStack
let’s define a title for the view:
Text("TabView")
.font(.title.bold())
Instead of a scroll view, we will be using a TabView
using the same code inside the TabView
:
TabView {
ForEach(animals) { animal in
VStack {
AnimalPhoto(animal: animal)
.padding(.bottom, 12)
Text(animal.imageName.capitalized)
.font(.title2)
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(.page(backgroundDisplayMode: .always))
.frame(height: 350)
.padding(.bottom, 20)
.padding(.top, -40)
Spacer()
TabView { ... }
is a SwiftUI container that allows users to switch between different views. Usually, TabView
creates a tab bar at the bottom (default behavior). .tabViewStyle(PageTabViewStyle())
changes the TabView into a swiping page view instead of a standard tab bar. .indexViewStyle(.page(backgroundDisplayMode: .always))
enables page indicators (dots) below the pages. The backgroundDisplayMode: .always
makes the dots always visible.
The user can swipe left and right to navigate between views.
Step 5: Creating a TabView
with tabItems
If you were running your app as it is, you will not see all the views you created up to now – apart from a globe and the Text “Hello, world!”. Why is that? Because this is what your ContentView
shows. And why is this one shown and not the other ones?
Take a look to the DifferentScrollViewApp.swift
. This has been created when setting up the project. Here you see:
import SwiftUI
@main
struct DifferentScrollViewsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
This file is the “entry point” of your SwiftUI app. It defines the main structure of your app and specifies which view should be displayed first. The @main
marks this as the main entry point of the app. DifferentScrollViewsApp
is a struct
that conforms to App
, which is required for SwiftUI apps.
The
WindowGroup {
ContentView()
}
creates a WindowGroup
, which is the default container for SwiftUI apps. WindowGroup
manages multiple windows on macOS, iPadOS, and visionOS, while on iPhone, it behaves like a single window. Inside WindowGroup, the starting view of your app is set to ContentView()
.
That’s why you would still see just the globe and “Hello, world!”. Let’s change that and present all the views you have created before:
In ContentView
in the body
replace the VStack
(that holds the globe and the text) by
TabView {
SimpleScrollView()
.tabItem {
Image(systemName: "list.bullet")
Text("Simple Scroll")
}
SimpleTabView()
.tabItem {
Image(systemName: "square.and.arrow.up")
Text("Simple Tab")
}
}
TabView
creates a tab-based navigation where users can switch between different views using a tab bar. Each view inside TabView is assigned a “tab bar item” using .tabItem { }
.
Each tab consists of:
a view (here in our case: SimpleScrollView(), SimpleTabView())
- a tabItem modifier, which defines an icon using
Image(systemName: "icon-name")
(The icon-names are the SF symbols you already got to know when building your first app “ButtonAnimation”.) and a label usingText("Tab Name")
In our app, we got 2 tabs and we present the 3 views we created earlier.
Step 6: Run your app
You see already a preview of your app on the right-hand side in the canvas. To run your app in the simulator, please select a device at the very top-middle of the XCode window – you can also add your own physical device – and press the Play button.
Congratulations!
You’ve successfully built 3 different ScrollViews! 🎉
What you have learned
In this code-along, you’ve learned how to
- structure a SwiftUI project – Organising code across multiple SwiftUI files for better maintainability.
- work with
ScrollView
– Creating both horizontal and paginated scrolling views. - use a
HStack
and aLazyHStack
and what is the difference, and embeddingForEach
loop. - work with models in Swift – Creating an
Animal struct
and leveraging.map {}
for data transformation. - create reusable views – Defining
AnimalPhoto
and using it dynamically for different data. - use
GeometryReader
– Measuring screen width dynamically for layout adjustments. - use
.scrollTargetBehavior(.viewAligned)
– Implementing a smooth snap effect for scroll views. - create a
TabView
– Displaying multiple views with swipe-based navigation and tab bar items. - set up an app’s entry point – Understanding ‘@main’, ‘WindowGroup’, and why ‘ContentView’ is initially displayed.
That’s a lot to cover in just one code-along! You made it this far, very well done! 🎉 Learning to structure and build interactive UI components takes time, but each step adds up.
Keep learning, keep building, and let your curiosity guide you. Happy coding! ✨
“Stay hungry. Stay foolish.” — Steve Jobs
The project will be available for download on GitHub after the second part of the scroll view code-along.