我有一个List
,显示当月的天数。 List
中的每一行都包含缩写的日期,Divider
和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()
}
}
}
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)
}
}
}
代码的结果如下:
这可能不是很明显,但是分隔线没有对齐,因为文本的宽度可能在行与行之间变化。我要实现的是使包含日期信息的视图具有相同的宽度,以便它们在视觉上对齐。
我尝试使用GeometryReader
和frame()
修饰符来设置最小宽度,理想宽度和最大宽度,但是我需要确保使用动态类型设置可以使文本缩小和增长;我选择不使用父级百分比的宽度,因为我不确定如何确保本地化文本始终适合在允许的宽度内。
如何修改视图,以使行中的每个视图具有相同的宽度,而与文本的宽度无关?
关于动态类型,我将创建一个用于更改该设置的不同布局。
答案 0 :(得分:4)
我使用GeometryReader
和Preferences
来工作。
首先,在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}}-这将用于确定DayListItemGeometry
中VStack
的宽度:
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
都会更新。