SwiftUI GeometryReader: Parallax Scrolling - Part 3
Mark Moeykens
Mark Moeykens
Big Mountain Studio was started by Mark Moeykens. The company specializes in providing visual picture books to be used as a reference.
Nov 16, 2019
This is part 3 in the GeometryReader Series. Part 2 is here.
In this article, you will learn a way to create a parallax effect when vertically scrolling. In order to do this, you want the background image to scroll more slowly than the rest of the content. Here is what you will build:
Get the Image Assets
You can definitely use your own image assets to build this screen. If you want to use the same images I used (National Parks in Utah) then go to my GitHub repo where I added the images.
Add these images to your Assets.xcassets in your new Xcode project.
Setup the Project
1. Create A New SwiftUI File
In your SwiftUI project, add a new SwiftUI file.
2. Setup the Background You'll need a scroll view since you'll be scrolling the content. But notice the background image you're using is INSIDE the scroll view.
The opacity and blur modifiers are used to lessen attention. You don't want your background competing for attention with your main content.
The scaleEffect was used to make the image bigger because I was too lazy to find a bigger image. 😃 (The correct thing to do would be to use a bigger image.)
Preview your view (or run the project) and it should look like this: (That's actually a map of Utah.)
3. Build the Tiles Let's create the image tiles for the national parks in Utah.
It's just an image with a text view overlayed on top of it.
Now you could just copy and paste this in 5 times but that creates a lot of duplicate code. Meaning, if you want to make a change, you have to change it in many places. And as you already saw above I'm kind of lazy. 😃
What you should do instead is to create another view (struct) for the image tile that you can reuse!
Create a new SwiftUI file for the tile. Here is the code I have:
struct Tile: View { var imageName = "" var tileLabel = ""
struct Tile_Previews: PreviewProvider { static var previews: some View { Tile(imageName: "Arches", tileLabel: "Arches") } }
You can see in the Preview code how you will be initializing this tile by passing in the image name and title text that will be overlayed on the image.
The preview should look similar to this: (By the way, that's the famous "Delicate Arch" in Arches National Park in Utah. Fun Fact: That's where I proposed marriage to my wife. 💍)
4. Add the Tiles and Title Great, you have your tile view ready. Now let's create more tiles for the national parks.
Since you're using a ZStack, you want to add the title and image tiles of the national parks on TOP of the map. So you will be adding a VStack as the next view in your ZStack.
Depending on the width of your background image, your tiles may seem too wide. The tiles are just stretching out to fit within the width of the background image.
Don't worry about this right now.
You will fix it soon. Your app should now look like this: Notice the background map is moving at the same speed as the tiles.
Create the Parallax Effect with Help from the GeometryReader
In Part 2, you saw how the geometry reader can track the current location of a view while it is being scrolled:
You will be using this geometry data with the background image.
The Basic Concept To create a parallax effect, you need two layers moving at different speeds. You can either
Speed up the tiles or
Slow down the background
In this example, you're going with the second option; slow down the background. How do you slow down the scroll speed of the background?
Offset to the Rescue The offset modifier is used to move a view using an X and Y coordinate. In the book SwiftUI Views, there is a section on using the offset modifier: When you scroll a view, it's offset is changing.
Scroll up 100 points, the Y offset moves to -100.
Scroll down 100 points, the Y offset moves to 100.
What you're going to do is change that Y offset to slow it down. For example:
Scroll up 100 points, you change the Y offset to move only -50.
Scroll down 100 points, you change the Y offset to move only 50.
So you need the GeometryReader to get the Y position and then subtract points.
How will you do this in code?
SwiftUI makes it super easy with two steps:
You wrap the background image in a GeometryReader
Apply an offset modifier to the background image that observes the Y position and then divides it by two (or really any number you want to get your desired effect).
Here are the two steps applied in code:
struct ParallaxScrolling: View { var body: some View { ScrollView { ZStack { GeometryReader { gr in Image("map") .resizable() .aspectRatio(contentMode: .fill) .blur(radius: 1) .scaleEffect(1.8) .opacity(0.4) // Subtract half the offset .offset(y: -gr.frame(in: .global).origin.y / 2) }
Don't forget the minus sign in there. If you don't use it, your background will move FASTER than the tiles (which is also a cool effect).
Also, remember, instead of .origin.y you can also use .minX to get the same value.
Summary
You just learned a really cool effect with the use of the:
ZStack (Depth Stack)
GeometryReader
Offset Modifier
Congratulations! 👏 🎉
The fun doesn't stop there though. Now that you understand this concept of being able to adjust the offset of a view in response to scrolling, there are more cool things you can do with the assistance of the GeometryReader.
Next
The GeometryReader can also be used to get the size of views and with that, you can adjust the size of views in response to scrolling. Coming up next, you will learn more about how to get the size of views using the GeometryReader and how you can apply that.
That's great Mark. Works a treat.
One small question..... when you have your simulator set to Dark Mode, is there a way of applying a white background that overcomes the fact that the Dark Mode background is black and bleeds through?
Chris Parker
Mark Moeykens