I have a List
that displays days in the current month. Each row in the List
contains the abbreviated day, a Divider
, and the day numbe
I got this to work using GeometryReader
and Preferences
.
First, in ContentView
, add this property:
@State var maxLabelWidth: CGFloat = .zero
Then, in DayListItem
, add this property:
@Binding var maxLabelWidth: CGFloat
Next, in ContentView
, pass self.$maxLabelWidth
to each instance of DayListItem
:
List(currentMonth.days.identified(by: \.self)) { date in
DayListItem(date: date, maxLabelWidth: self.$maxLabelWidth)
}
Now, create a struct called MaxWidthPreferenceKey
:
struct MaxWidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
let nextValue = nextValue()
guard nextValue > value else { return }
value = nextValue
}
}
This conforms to the PreferenceKey
protocol, allowing you to use this struct as a key when communicating preferences between your views.
Next, create a View
called DayListItemGeometry
- this will be used to determine the width of the VStack
in DayListItem
:
struct DayListItemGeometry: View {
var body: some View {
GeometryReader { geometry in
Color.clear
.preference(key: MaxWidthPreferenceKey.self, value: geometry.size.width)
}
.scaledToFill()
}
}
Then, in DayListItem
, change your code to this:
HStack {
VStack(alignment: .center) {
Text(weekdayFormatter.string(from: date))
.font(.caption)
.foregroundColor(.secondary)
Text(dayNumberFormatter.string(from: date))
.font(.body)
.foregroundColor(.red)
}
.background(DayListItemGeometry())
.onPreferenceChange(MaxWidthPreferenceKey.self) {
self.maxLabelWidth = $0
}
.frame(width: self.maxLabelWidth)
Divider()
}
What I've done is I've created a GeometryReader
and applied it to the background of the VStack
. The geometry tells me the dimensions of the VStack
which sizes itself according to the size of the text. MaxWidthPreferenceKey
gets updated whenever the geometry changes, and after the reduce
function inside MaxWidthPreferenceKey
calculates the maximum width, I read the preference change and update self.maxLabelWidth
. I then set the frame of the VStack
to be .frame(width: self.maxLabelWidth)
, and since maxLabelWidth
is binding, every DayListItem
is updated when a new maxLabelWidth
is calculated. Keep in mind that the order matters here. Placing the .frame
modifier before .background
and .onPreferenceChange
will not work.