I've got this simple view with ScrollView, Text, and Buttons. The Buttons are just left/right chevrons for displaying prev/next scroll item. The code is this:
struct PreviewWrapper: View {
@State private var activeIndex: Int = 0
private let colors: [Color] = [.red, .green, .blue, .yellow, .orange]
var body: some View {
ZStack {
// Cannot use scrollPosition because we require min version = iOS 15.
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<colors.count) { index in
colors[index]
.fixedSize(horizontal: false, vertical: false)
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
.id(index)
}
}
}
VStack {
Spacer()
HStack {
Button {
guard activeIndex > 0 else { return }
activeIndex -= 1
proxy.scrollTo(activeIndex)
} label: {
Label("", systemImage: "chevron.left")
}
.padding(.leading, 48)
Spacer()
Text("\(activeIndex + 1)/\(colors.count)")
Spacer()
Button {
guard activeIndex < colors.count - 1 else { return }
activeIndex += 1
proxy.scrollTo(activeIndex)
} label: {
Label("", systemImage: "chevron.right")
}
.padding(.trailing, 48)
}
}
}
}
}
}
The preview didn't show the VStack at all in this case like so:
When I changed the height of the colors to 300 using .frame(width: UIScreen.main.bounds.width,height: 300)
, it worked just fine:
What did I do wrong here? Can anybody help me? Thank you.
Giving the colors a height of UIScreen.main.bounds.height
actually makes the ScrollView
taller than the screen. This is because a scroll view that respects the safe area will inset its content by the safe area insets. As a result, the ZStack
becomes taller than the screen, and pushing the buttons downwards.
Instead of setting the height of the colors directly, just have them fill up the scroll view naturally. Add .ignoresSafeArea
for them to fill the whole screen instead.
Instead of using UIScreen
properties, you should read the available width using a GeometryReader
. "Available width == screen width" not only is often an incorrect assumption, but SwiftUI also cannot observe changes to the screen size this way.
GeometryReader { geo in
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<colors.count) { index in
colors[index]
.ignoresSafeArea()
.frame(width: geo.size.width)
// in iOS 17+, you can replace .frame with .containerRelativeFrame and get rid of the geometry reader
// .containerRelativeFrame(.horizontal)
.id(index)
}
}
}
}