Search code examples
iosswiftswiftuiscrollviewswift5

Fill height in scrollView in swiftUI


I am coding useing swiftUI and I have a vertical scrollView (see screenshots), and inside that scrollView I have another horizontal scrollView, and below that I have a VStack that I want the height to fill the rest of the screen to the bottom of the vertical scrollView, but I can't seem to make it work.

The content in the vertical scrollView can be larger than the actually remaining space in some cases that is why I need a scrollView, but in some other cases the content is smaller than the remaining space

Here is the code I have :

    VStack {
        HeaderView
        
        ScrollView(.vertical, showsIndicators: false) {
            FriendsHorizontalScrollView()
            
            VStack {
                // I want this view to take all the remaining height left in the scrollView
                FullHeightView()
            }
            .frame(width: UIScreen.main.bounds.width)
        }
    }

What I end up having is something like this :

enter image description here

What I want to have :

enter image description here

I have tried several solutions like using geometryReader or putting .frame(maxHeight: .infinity) on the VStack but nothing seems to work properly.


Solution

  • Here is a demo of possible approach based on view preferences (ViewHeightKey is taken from my this solution. Tested with Xcode 12 / iOS 14.

    demo

    struct DemoLayoutInScrollView: View {
        @State private var height1: CGFloat = .zero
        @State private var height2: CGFloat = .zero
        var body: some View {
            VStack(spacing: 0) {
                HeaderView()
    
                GeometryReader { gp in
                    ScrollView(.vertical, showsIndicators: false) {
                        VStack(spacing: 0) {
                            FriendsHorizontalScrollView()
                                .background(GeometryReader {
                                    Color.clear
                                        .preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
                                })
                                .onPreferenceChange(ViewHeightKey.self) { self.height1 = $0 }
                            VStack(spacing: 0) {
                                // I want this view to take all the remaining height left in the scrollView
                                FullHeightView()
                                    .background(GeometryReader {
                                        Color.clear
                                            .preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
                                    })
                            }.frame(height: max(gp.size.height - self.height1, self.height2))
                            .background(Color.yellow)
                        }
                    }
                    .onPreferenceChange(ViewHeightKey.self) { self.height2 = $0 }
                }
            }.frame(maxWidth: .infinity)
        }
    }
    

    replicated helper views (for testing)

    struct FriendsHorizontalScrollView: View {
        var body: some View {
            Text("Horizontal Scroller")
                .frame(maxWidth: .infinity)
                .frame(height: 100)
                .background(Color.green)
        }
    }
    
    struct FullHeightView: View {
        var body: some View {
            ZStack {
                Color.red
                Text("Dynamic Content")
            }
            .frame(maxWidth: .infinity)
            .frame(height: 300)
        }
    }
    
    struct HeaderView: View {
        var body: some View {
            Rectangle().foregroundColor(.blue)
                .frame(height: 60)
                .overlay(Text("Header"))
        }
    }