swiftswiftui

The front most View in ZStack not showing


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: Error

When I changed the height of the colors to 300 using .frame(width: UIScreen.main.bounds.width,height: 300), it worked just fine:

Success

What did I do wrong here? Can anybody help me? Thank you.


Solution

  • 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)
                }
            }
        }
    }