在列表行中制作等宽的SwiftUI视图

时间:2019-07-11 21:50:18

标签: swiftui

我有一个List,显示当月的天数。 List中的每一行都包含缩写的日期,DividerVStack中的天数。然后将VStack嵌入到HStack中,以便在当天和数字的右边添加更多文本。

struct DayListItem : View {

    // MARK: - Properties

    let date: Date

    private let weekdayFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "EEE"
        return formatter
    }()

    private let dayNumberFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "d"
        return formatter
    }()

    var body: some View {
        HStack {
            VStack(alignment: .center) {
                Text(weekdayFormatter.string(from: date))
                    .font(.caption)
                    .foregroundColor(.secondary)
                Text(dayNumberFormatter.string(from: date))
                    .font(.body)
                    .foregroundColor(.red)
            }
            Divider()
        }
    }

}

DayListItem中使用了ContentView的实例:

struct ContentView : View {

    // MARK: - Properties

    private let dataProvider = CalendricalDataProvider()

    private var navigationBarTitle: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMMM YYY"
        return formatter.string(from: Date())
    } 

    private var currentMonth: Month {
        dataProvider.currentMonth
    }

    private var months: [Month] {
        return dataProvider.monthsInRelativeYear
    }

    var body: some View {
        NavigationView {
            List(currentMonth.days.identified(by: \.self)) { date in
                DayListItem(date: date)
            }
                .navigationBarTitle(Text(navigationBarTitle))
                .listStyle(.grouped)
        }
    }

}

代码的结果如下:

Screenshot of app running in Xcode Simulator

这可能不是很明显,但是分隔线没有对齐,因为文本的宽度可能在行与行之间变化。我要实现的是使包含日期信息的视图具有相同的宽度,以便它们在视觉上对齐。

我尝试使用GeometryReaderframe()修饰符来设置最小宽度,理想宽度和最大宽度,但是我需要确保使用动态类型设置可以使文本缩小和增长;我选择不使用父级百分比的宽度,因为我不确定如何确保本地化文本始终适合在允许的宽度内。

如何修改视图,以使行中的每个视图具有相同的宽度,而与文本的宽度无关?

关于动态类型,我将创建一个用于更改该设置的不同布局。

1 个答案:

答案 0 :(得分:4)

我使用GeometryReaderPreferences来工作。

首先,在ContentView中,添加以下属性:

@State var maxLabelWidth: CGFloat = 0

然后,在DayListItem中,添加以下属性:

@Binding var maxLabelWidth: CGFloat

接下来,在ContentView中,将self.$maxLabelWidth传递给DayListItem的每个实例:

List(currentMonth.days.identified(by: \.self)) { date in
    DayListItem(date: date, maxLabelWidth: self.$maxLabelWidth)
}

现在,创建一个名为WidthPreferenceKey的结构:

struct WidthPreferenceKey: PreferenceKey {
    typealias Value = CGFloat

    static var defaultValue: CGFloat = 0

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

这符合PreferenceKey协议,允许您在视图之间传递首选项时将此结构用作键。

接下来,创建一个名为View的{​​{1}}-这将用于确定DayListItemGeometryVStack的宽度:

DayListItem

然后,在struct DayListItemGeometry: View { var body: some View { GeometryReader { geometry in Rectangle() .fill(Color.clear) .preference(key: WidthPreferenceKey.self, value: geometry.size.width) } .scaledToFill() } } 中,将代码更改为此:

DayListItem

我所做的是创建了一个HStack { VStack(alignment: .center) { Text(weekdayFormatter.string(from: date)) .font(.caption) .foregroundColor(.secondary) Text(dayNumberFormatter.string(from: date)) .font(.body) .foregroundColor(.red) } .frame(width: self.maxLabelWidth) .background(DayListItemGeometry()) .onPreferenceChange(WidthPreferenceKey.self) { if $0 > self.maxLabelWidth { self.maxLabelWidth = $0 } } Divider() } ,它可以缩放以填充GeometryReader的背景,该背景本身的大小取决于文本的大小。然后,我将VStack的宽度设置为首选项,然后阅读该首选项更改并在必要时更新GeometryReader。然后,我将maxLabelWidth的帧设置为VStack,由于.frame(width: self.maxLabelWidth)是绑定的,因此在计算新的maxLabelWidth时,每个DayListItem都会更新。

Canvas Screenshot