I have some troubles with dynamically changing List height that dependent on elements count.
I tried this solution but it didn't work.
List {
ForEach(searchService.searchResult, id: \.self) { item in
Text(item)
.font(.custom("Avenir Next Regular", size: 12))
}
}.frame(height: CGFloat(searchService.searchResult.count * 20))
TL;DR
This is not how the designers of SwiftUI want you to use lists. Either you will have to come up with a hacky solution that will probably break in the future (see below), or use something other than a list.
Background
SwiftUI tends to have two types of Views
An example of type 1 would be Text. You can change font size, weight, typeface, color, background, padding, etc. It is designed for you to modify it.
An example of type 2 would be List. You are not in direct control of row height, you can't change the padding around views, you can't tell it to show only so many rows, etc. They don't want it to be very customizable, because then each app's lists would behave differently, defeating the purpose of a standard control.
List is designed to fill the entire parent View with as many rows as possible, even if they are empty or only partially on screen (and scroll if there are too many to show at once).
Your issue
The problem you are having comes about because the size of the List does not affect the size of its rows in any way. SwiftUI doesn't care if there are too many or too few rows to fit in your preferred size; it will happily size its rows according to content, even if it means they don't all show or there are empty rows shown.
If you really need rows to resize according to the size of their parent, you should use a VStack. If it needs to scroll, you will need to wrap the VStack in a ScrollView.
Hacky solution
If you still insist on using a list, you will have to do something like the following:
struct ContentView: View {
@State private var textHeight: Double = 20
let listRowPadding: Double = 5 // This is a guess
let listRowMinHeight: Double = 45 // This is a guess
var listRowHeight: Double {
max(listRowMinHeight, textHeight + 2 * listRowPadding)
}
var strings: [String] = ["One", "Two", "Three"]
var body: some View {
VStack {
HStack {
Text(String(format: "%2.0f", textHeight as Double))
Slider(value: $textHeight, in: 20...60)
}
VStack(spacing: 0) {
Color.red
List {
ForEach(strings, id: \.self) { item in
Text(item)
.font(.custom("Avenir Next Regular", size: 12))
.frame(height: CGFloat(self.textHeight))
.background(Color(white: 0.5))
}
}
// Comment out the following line to see how List is expected to work
.frame(height: CGFloat(strings.count) * CGFloat(self.listRowHeight))
Color.red
}.layoutPriority(1)
}
}
}
The slider is there to show how the list row heights change with the height of their child view. You would have to manually pick listRowPadding
and listRowMinHeight
to get the appearance that best matches your expectation. If Apple ever changes how a List looks (changes padding, minimum row heights, etc.) you will have to remember to come back and adjust these values manually.