I’m experiencing some unexpected behavior with a slide transition in a SwiftUI view.
I have a series of slides within an HStack that I want to animate through when a button is pressed.
I expected that I’d need to calculate individual offsets for each slide to make them appear and disappear correctly.
However, when using the same offset calculation for each slide, the transition still behaves correctly, and I’m trying to understand why.
Here’s the relevant part of my code:
struct OnboardingView: View {
@StateObject private var viewModel = OnboardingViewModel()
func calculateOffset(_ index: Int, _ screenWidth: CGFloat) -> CGFloat {
let difference = index - viewModel.currentPage
return screenWidth * CGFloat(difference)
}
var body: some View {
GeometryReader { geometry in
let screenWidth = geometry.frame(in: .global).size.width
HStack(spacing: 0) {
SlideView1()
.frame(width: screenWidth)
.offset(x: calculateOffset(1, screenWidth))
SlideView2()
.frame(width: screenWidth)
.offset(x: calculateOffset(1, screenWidth))
SlideView3()
.frame(width: screenWidth)
.offset(x: calculateOffset(1, screenWidth))
}
}
.background(Color.blue.ignoresSafeArea())
.safeAreaInset(edge: .bottom) {
VStack {
Text("Current Page: (viewModel.currentPage)")
Button("Next") {
withAnimation {
viewModel.currentPage += 1
}
}
}
}
}
}
class OnboardingViewModel: ObservableObject {
@Published var currentPage = 1
}
struct SlideView1: View {
var body: some View {
Text("Slide 1")
}
}
struct SlideView2: View {
var body: some View {
Text("Slide 2")
}
}
struct SlideView3: View {
var body: some View {
Text("Slide 3")
}
}
class OnboardingViewModel: ObservableObject {
@Published var currentPage = 1
}
As you can see, calculateOffset(1, width) is used for all slides, yet the view transitions correctly with each button press.
To provide more context, here’s an image from the Xcode view hierarchy debugger showing all the slides lined up side by side, which seems to confirm that the layout is as expected for an HStack:
I’m puzzled as to why this works. My expectation was that each slide should have its own offset calculation based on its position in the stack.
Could someone help me understand the underlying mechanics of why the shared offset leads to the correct behavior?
2
Answers
First of all
Let’s make it a lot more readable and viewable.
we can have a check in
Debug View Hierarchy
and that seems to be like thatThen
we can adjust the code a little bit
If we have a check for
Debug View Hierarchy
again, you can find it totally the same.Every time you click the button, the
HStack
‘s position is changedConclusion
Your are just adjusting the
HStack
‘s position not theSlideView
‘s.What’s more
I think your func
calculateOffset
is not the same you want, you can change it to.and make it effect by
It’s working because each of the slides is being given the full width of the screen and they are all contained inside an
HStack
. This means, the width of theHStack
is 3x the width of the screen, so at any time you are only seeing one third of theHStack
and it is always the first third.The offset for all slides is being calculated as
screenWidth * (1 - currentPage)
. This means:HSTack
-> OKHStack
-> OKHStack
-> OK.Another way to do it would be to apply the offset to the
HStack
instead. The parameter should still be passed as 1 in this case too, so in fact the parameter is redundant and you can change the function to work without a parameter.