Make Equal-Width SwiftUI Views in List Rows

前端 未结 1 771
一生所求
一生所求 2020-12-14 10:27

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

相关标签:
1条回答
  • 2020-12-14 10:58

    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.

    0 讨论(0)
提交回复
热议问题