Search code examples
swiftswiftuiswiftui-listswiftui-navigationlinkswiftui-navigationview

SwiftUI List Rows Different Heights for Different System Images


In my SwiftUI app, I have a List inside of a sidebar NavigationView with 2 rows, like this:

List {
    NavigationLink(destination: MyView1()) {
        Label("Test Row 1", systemImage: "list.bullet")
    }
    
    NavigationLink(destination: MyView2()) {
        Label("Test Row 2", systemImage: "shippingbox")
            .frame(maxWidth: .infinity, alignment: .leading)
    }
}

However the second row is taller that the first row, I believe due to the image. If I change the image to this:

NavigationLink(destination: MyView2()) {
    Label("Test Row 2", systemImage: "list.number")
        .frame(maxWidth: .infinity, alignment: .leading)
}

the rows are now the same height. I don't want to fix the height of the rows, but I was wondering if there was a solution where the rows could be the same height.


Solution

  • If you want to ensure that the rows are even, you can use PreferenceKeys to set the row heights to the height of the tallest row like this:

    struct EvenHeightListRow: View {
        
        @State var rowHeight = CGFloat.zero
    
        var body: some View {
            List {
                NavigationLink(destination: Text("MyView1")) {
                    Label("Test Row 1)", systemImage: "list.bullet")
                        // This reads the height of the row
                        .background(GeometryReader { geometry in
                            Color.clear.preference(
                                key: HeightPreferenceKey.self,
                                value: geometry.size.height
                            )
                        })
                        .frame(height: rowHeight)
                }
                
                NavigationLink(destination: Text("MyView2")) {
                    Label("Test Row 2)", systemImage: "shippingbox")
                        .background(GeometryReader { geometry in
                            Color.clear.preference(
                                key: HeightPreferenceKey.self,
                                value: geometry.size.height
                            )
                        })
                        .frame(height: rowHeight)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
            }
            // this sets backgroundSize to be the max value of the tallest row
            .onPreferenceChange(HeightPreferenceKey.self) {
                rowHeight = max($0, rowHeight)
            }
        }
    }
    
    // This is the actual preferenceKey that makes it work.
    fileprivate struct HeightPreferenceKey: PreferenceKey {
        static var defaultValue: CGFloat = .zero
        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}
    }