swiftuiswiftui-scrollviewswiftui-gridrow

Make rows of SwiftUI Grid 'scrollable'


I have SwiftUI grid of 25 rows with a first header row and a totals row at the bottom. I would like the header and the totals row to remain fixed, but allow the user to scroll the 23 rows (as they often do not fit the screen). Using ScrollView on certain GridRows does not work...

import SwiftUI

struct ContentView: View {
    var body: some View {
        Grid {
            GridRow {
                Text("Name")
                Text("Age")
            }
            .font(.headline)
            
            // would like to make the following 'scrollable'
            GridRow {
                Text("Marc")
                Text("25")
            }
            
            GridRow {
                Text("Francis")
                Text("36")
            }
            // ... many more GridRows ...
            
            // end of scrolling
            
            GridRow {
                Text("Total Age:")
                Text("61")
            }
            
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Solution

  • I assume you want the scrollable rows to scroll vertically, right?

    Some possible approaches:


    Here is another approach that keeps it all dynamic:

    Here is an example to show it working. The width of the first column (Name) is determined by the grid contents, the width of the second column (Age) is determined by the title.

    struct ContentView: View {
        private let spacing: CGFloat = 10
        @State private var colWidths = [CGFloat](repeating: 0, count: 2)
    
        var body: some View {
            VStack {
                HStack(spacing: spacing) {
                    Text("Name")
                        .frame(width: colWidths[0], alignment: .leading)
                    Text("Age")
                        .modifier(WidthRecorder(maxWidth: $colWidths[1]))
                }
                .font(.headline)
    
                ScrollView {
                    Grid(alignment: .leading, horizontalSpacing: spacing) {
                        GridRow {
                            Text("Marc")
                                .modifier(WidthRecorder(maxWidth: $colWidths[0]))
                            Text("25")
                                .frame(width: colWidths[1], alignment: .leading)
                        }
                        GridRow {
                            Text("Francis")
                                .modifier(WidthRecorder(maxWidth: $colWidths[0]))
                            Text("36")
                                .frame(width: colWidths[1], alignment: .leading)
                        }
                        ForEach(1..<50) { i in
                            GridRow {
                                Text("Another row \(i)")
                                    .modifier(WidthRecorder(maxWidth: $colWidths[0]))
                                Text("\(i)")
                                    .frame(width: colWidths[1], alignment: .leading)
                            }
                        }
                    }
                }
                HStack(spacing: spacing) {
                    Text("Total Age:")
                        .frame(width: colWidths[0], alignment: .leading)
                    Text("61")
                        .frame(width: colWidths[1], alignment: .leading)
                }
            }
        }
    
        struct WidthRecorder: ViewModifier {
            @Binding var maxWidth: CGFloat
            func body(content: Content) -> some View {
                content
                    .onGeometryChange(for: CGFloat.self) { proxy in
                        proxy.size.width
                    } action: { newVal in
                        if newVal > maxWidth {
                            maxWidth = newVal
                        }
                    }
            }
        }
    }
    

    Animation