2

I'm encountering a layout issue in SwiftUI on iOS 17 where Color views within a VStack inside a ScrollView begin to overlap as I scroll down. Each Color view is supposed to take up the full container's relative frame, but as I scroll, the next Color view encroaches upon the space of the previous one. This issue compounds with further scrolling, with subsequent views taking more and more space.

I have attached a video to demonstrate the issue, and here is the code to reproduce the problem. Just copy and paste it.

struct TestView: View {
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            VStack(spacing: 0) {
                ForEach(0..<10, id:\.self) { idx in
                    Color.random()
                    .containerRelativeFrame([.horizontal, .vertical])
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.paging)
    }
}

#Preview {
    TestView()
}

I'm using .containerRelativeFrame([.horizontal, .vertical]) for each color view to make them fill the available space, and .scrollTargetLayout() on the VStack. The .scrollTargetBehavior(.paging) modifier is applied to the ScrollView to enable paging behavior.

The expected result is for each color view to occupy its own page space without affecting the others. However, when I scroll, the layout starts to break as shown in the video.

Has anyone experienced something similar or can provide insight into what might be causing this issue?

My Environment:

Simulator running version: iOS 17.0

XCode Version: 15.0.1 (15A507)

Swift Version: swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) Target: x86_64-apple-macosx13.0

enter image description here

2 Answers 2

5

I think this must be a bug.

It seems to be compensating for the bottom insets incorrectly. If you try it on an iPhone SE then it works, because the bottom insets are 0. Otherwise, the "slippage" appears to be half the size of the bottom insets.

Possible workarounds:

  1. Ignore bottom safe area insets
ScrollView(.vertical, showsIndicators: false) {
    // content as before
}
.scrollTargetBehavior(.paging)
.ignoresSafeArea(edges: .bottom) // <- ADDED
  1. Use a GeometryReader to measure the safe area insets and set the VStack spacing accordingly
GeometryReader { proxy in // <- ADDED
    ScrollView(.vertical, showsIndicators: false) {
        VStack(spacing: proxy.safeAreaInsets.bottom / 2) { // <- CHANGED
            // content as before
        }
        .scrollTargetLayout()
    }
    .scrollTargetBehavior(.paging)
}
  1. Add padding below the pages

With this approach, you can eliminate the space between the colors that is seen with solution 2.

GeometryReader { proxy in
    ScrollView(.vertical, showsIndicators: false) {
        VStack(spacing: 0) {
            ForEach(0..<10, id:\.self) { idx in
                Color.clear
                    .containerRelativeFrame([.horizontal, .vertical])
                    .padding(.bottom, proxy.safeAreaInsets.bottom / 2)
                    .background(Color.random())
            }
        }
        .scrollTargetLayout()
    }
    .scrollTargetBehavior(.paging)
}
  1. Set the height and width of the subviews explicitly, instead of using .containerRelativeFrame
GeometryReader { proxy in
    ScrollView(.vertical, showsIndicators: false) {
        VStack(spacing: 0) {
            ForEach(0..<10, id:\.self) { idx in
                Color.random()
                    .frame(
                        width: proxy.size.width,
                        height: proxy.size.height + (proxy.safeAreaInsets.bottom / 2)
                    )
            }
        }
        .scrollTargetLayout()
    }
    .scrollTargetBehavior(.paging)
}

I would go for the first workaround if you can, because if the bug is fixed in a future version then it should still continue to work.

Ps. You might want to report the bug as feedback to Apple.

Sign up to request clarification or add additional context in comments.

Comments

2

While the solutions by @BenzyNeez work, you can also fix this simply by changing from .paging to .viewAligned(limitBehavior: .always):

 .scrollTargetBehavior(.viewAligned(limitBehavior: .always))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.