在列表行中创建等宽的SwiftUI视图

Nic*_*hrn 4 swiftui

我有一个List显示当前月份的天数。中的每一行都List包含缩写的天,a Divider和a中的天数VStack。在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()
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

的实例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)
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

代码的结果如下:

在Xcode Simulator中运行的应用程序的屏幕截图

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

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

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

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

gra*_*ell 9

我使用GeometryReader和来工作Preferences

首先,在ContentView中添加此属性:

@State var maxLabelWidth: CGFloat = 0
Run Code Online (Sandbox Code Playgroud)

然后,在DayListItem中添加此属性:

@Binding var maxLabelWidth: CGFloat
Run Code Online (Sandbox Code Playgroud)

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

List(currentMonth.days.identified(by: \.self)) { date in
    DayListItem(date: date, maxLabelWidth: self.$maxLabelWidth)
}
Run Code Online (Sandbox Code Playgroud)

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

struct WidthPreferenceKey: PreferenceKey {
    typealias Value = CGFloat

    static var defaultValue: CGFloat = 0

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}
Run Code Online (Sandbox Code Playgroud)

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

接下来,创建一个View被调用的DayListItemGeometry-这将用于确定VStackin 的宽度DayListItem

struct DayListItemGeometry: View {
    var body: some View {
        GeometryReader { geometry in
            Rectangle()
                .fill(Color.clear)
                .preference(key: WidthPreferenceKey.self, value: geometry.size.width)
        }
        .scaledToFill()
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在中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()
}
Run Code Online (Sandbox Code Playgroud)

我完成的工作是创建一个GeometryReader可缩放的,以填充的背景,该背景VStack本身根据文本的大小进行调整。然后,我将其宽度设置GeometryReader为首选项,然后阅读该首选项更改并maxLabelWidth在必要时进行更新。然后,我将的框架设置VStack.frame(width: self.maxLabelWidth),并且由于maxLabelWidth是绑定的,因此每当计算DayListItem新的框架时都会对其进行更新maxLabelWidth

画布截图