Search code examples
swiftuiuiscrollviewgeometryreadervstackhstack

Make view in scroll view centered in available space between top vstack with labels (it can change)


I have scroll view in which on the top can be different number of texts, from zero to many. And in the center I have another view which should be vertically centered in AVAILABLE SPACE, so if we have no texts on the top - my view should be in the center of screen, if there are a few texts - it should be entered in available space.If I have no available space already, padding for my view should be 24 on top and bottom.I tried to use spacers between stack with texts and my view, and between view and scroll view but the behaviour isn't as expected.Also I tried to add horizontal stack(with center alignment) under stack with texts but it doesn't stretch. And also tried set minHeight, but it just sets concrete size for my "centered" view.enter image description here


Solution

  • One approach is to use an "outer" VStack that has a minimum height of the height of the scroll view.

    As the "number of rows" in the top, blue VStack increases, the Text object in the lower portion of that "outer" stack will decrease until it cannot compress any more.

    At that point, the "outer" stack will become taller than the scroll view, and you'll be able to scroll.

    Here's a quick example:

    import SwiftUI
    
    struct ScrollTestView: View {
        
        @State var numRows: Int = 0
        
        var body: some View {
            
            GeometryReader { geometry in
                
                ScrollView {
                    
                    // "outer" VStack
                    //  minimum Height is the scroll view height
                    VStack(alignment: .center, spacing: 0) {
                        if numRows > 0 {
                            // "blue" VStack with variable number of "rows"
                            //  remove a row on each tap
                            VStack(alignment: .center, spacing: 20) {
                                Text("Each tap in blue frame removes a row.")
                                ForEach((1...numRows), id: \.self) {
                                    Text("Hello \($0)")
                                }
                            }
                            .frame(maxWidth: .infinity)
                            .background(Color.blue)
                            .onTapGesture {
                                if numRows > 0 {
                                    numRows -= 1
                                }
                            }
                        }
    
                        Text("Hello World\nEach tap in gray frame adds a row above.")
                            .frame(maxWidth: .infinity, maxHeight: .infinity)
                            .multilineTextAlignment(.center)
                            .background(Color.gray)
                            .padding(24)
                            .onTapGesture {
                                numRows += 1
                            }
    
                    }
                    .frame(minHeight: geometry.size.height)
                    
                }
                
            }
            
        }
    
    }
    
    struct ScrollTestView_Previews: PreviewProvider {
        static var previews: some View {
            ScrollTestView()
        }
    }