How to display a List or Table without empty rows at bottom?

268 views Asked by At

I'm looking for a way to display a SwiftUI Table or List at its "intrinsic size" without including empty rows or empty space below the rows, as it does in the following example, in which the table is given a blue border for clarity:

Example

It is generated by the following code:

struct ContentView: View {
    let names = ["Achmed", "Balthus", "Chaim"]
    var body: some View {
        VStack {
            Text("List of Names")
                .font(.largeTitle)
                .padding()
            List(names, id: \.self) { Text($0) }
                .border(.blue)
                .padding()
            Spacer()
        }
    }
}

I would like the Spacer() to reduce the height of the List, removing the extra space inside the List View below its last row - but it does not.

Nor does adding .fixedSize() modifier. (I wonder why the list doesn't have an idealHeight that does not include empty rows, such that adding .fixedSize() would cause it to be displayed that way.)

2

There are 2 answers

0
Don Nissen On
I don't think you can do it with a List. You might try something like this:

struct ContentView: View {
let names = ["Achmed", "Balthus", "Chaim"]

@State var textSize:CGSize = .zero
@State var listSize:CGSize = .zero

var body: some View {
    VStack {
        VStack {
            Text("List of Names")
                .font(.largeTitle)
                .padding()
                .readSize { size in
                    textSize = size
                }
                .border(.green)
            VStack{
                VStack {
                    ForEach(names, id: \.self) {
                        Text($0).padding(2)
                        Divider()
                    }
                }
                .readSize { size in
                    listSize = size
                }
                .frame(width: textSize.width, height: listSize.height)
                .background(.white)
                .clipShape(RoundedRectangle(cornerRadius: 10))
                .padding()
            }
            .background(Color(.systemGray6))
           .border(.blue)
             Text("Bottom of list")
            Spacer()
            Text("Bottom of page")
        }
    }
}

}

extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
    background(
        GeometryReader { geometryProxy in
            Color.clear
                .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
        }
    )
    .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}

}

You'll also have to define the following Struct.

struct SizePreferenceKey: PreferenceKey {
  static var defaultValue: CGSize = .zero
  static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

Result:

enter image description here

4
koen On

This works for iOS, I changed the listStyle to insetGrouped and commented out padding(). Also, alternatingRowBackgrounds gave a compiler error, so I commented that one out as well.

struct ContentView: View {
    let names = ["Achmed", "Balthus", "Chaim"]
    var body: some View {
        VStack {
            Text("List of Names")
                .font(.largeTitle)
                .padding()
            List(names, id: \.self) { Text($0) }
                .listStyle(.insetGrouped)
//                .alternatingRowBackgrounds()
//                .padding()
            Spacer()
        }
    }
}

Result:

enter image description here

For macOS, you could try .listStyle(.automatic).

EDIT: try using a GeometryReader:

struct ContentView: View {
    let names = ["Achmed", "Balthus", "Chaim"]
    var body: some View {
        VStack {
            Text("List of Names")
                .font(.largeTitle)
                .padding()
            GeometryReader { g in
                List(names, id: \.self) { Text($0) }
                    .frame(height: g.size.height / 3)
                    .border(.blue)
                    .padding()
            }
        }
    }
}

Result:

enter image description here

You will have to tweak the .frame(height: g.size.height / 3) part for your specific case.