在列表行中创建等宽 SwiftUI 视图
Make Equal-Width SwiftUI Views in List Rows
我有一个 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()
修饰符来设置最小宽度、理想宽度和最大宽度,但我需要确保文本可以随着 Dynamic 收缩和增长类型设置;我选择不使用占父级百分比的宽度,因为我不确定如何确保本地化文本始终适合允许的宽度。
如何修改我的视图,使行中的每个视图都具有相同的宽度,而不考虑文本的宽度?
关于动态类型,我会创建一个不同的布局,以便在更改该设置时使用。
我使用 GeometryReader
和 Preferences
让这个工作。
首先,在 ContentView
中添加 属性:
@State var maxLabelWidth: CGFloat = .zero
然后,在 DayListItem
中添加 属性:
@Binding var maxLabelWidth: CGFloat
接下来,在ContentView
中,将self.$maxLabelWidth
传递给DayListItem
的每个实例:
List(currentMonth.days.identified(by: \.self)) { date in
DayListItem(date: date, maxLabelWidth: self.$maxLabelWidth)
}
现在,创建一个名为 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
}
}
这符合 PreferenceKey
协议,允许您在视图之间交流首选项时将此结构用作键。
接下来,创建一个名为 DayListItemGeometry
的 View
- 这将用于确定 DayListItem
中 VStack
的宽度:
struct DayListItemGeometry: View {
var body: some View {
GeometryReader { geometry in
Color.clear
.preference(key: MaxWidthPreferenceKey.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)
}
.background(DayListItemGeometry())
.onPreferenceChange(MaxWidthPreferenceKey.self) {
self.maxLabelWidth = [=15=]
}
.frame(width: self.maxLabelWidth)
Divider()
}
我所做的是创建了一个 GeometryReader
并将其应用于 VStack
的背景。几何图形告诉我 VStack
的尺寸,它会根据文本的大小自行调整大小。 MaxWidthPreferenceKey
会在几何形状发生变化时更新,并且在 MaxWidthPreferenceKey
中的 reduce
函数计算出最大宽度后,我读取首选项更改并更新 self.maxLabelWidth
。然后我将 VStack
的框架设置为 .frame(width: self.maxLabelWidth)
,并且由于 maxLabelWidth
具有约束力,因此在计算新的 maxLabelWidth
时每个 DayListItem
都会更新。请记住,这里的顺序很重要。将 .frame
修饰符放在 .background
和 .onPreferenceChange
之前将不起作用。
我有一个 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()
修饰符来设置最小宽度、理想宽度和最大宽度,但我需要确保文本可以随着 Dynamic 收缩和增长类型设置;我选择不使用占父级百分比的宽度,因为我不确定如何确保本地化文本始终适合允许的宽度。
如何修改我的视图,使行中的每个视图都具有相同的宽度,而不考虑文本的宽度?
关于动态类型,我会创建一个不同的布局,以便在更改该设置时使用。
我使用 GeometryReader
和 Preferences
让这个工作。
首先,在 ContentView
中添加 属性:
@State var maxLabelWidth: CGFloat = .zero
然后,在 DayListItem
中添加 属性:
@Binding var maxLabelWidth: CGFloat
接下来,在ContentView
中,将self.$maxLabelWidth
传递给DayListItem
的每个实例:
List(currentMonth.days.identified(by: \.self)) { date in
DayListItem(date: date, maxLabelWidth: self.$maxLabelWidth)
}
现在,创建一个名为 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
}
}
这符合 PreferenceKey
协议,允许您在视图之间交流首选项时将此结构用作键。
接下来,创建一个名为 DayListItemGeometry
的 View
- 这将用于确定 DayListItem
中 VStack
的宽度:
struct DayListItemGeometry: View {
var body: some View {
GeometryReader { geometry in
Color.clear
.preference(key: MaxWidthPreferenceKey.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)
}
.background(DayListItemGeometry())
.onPreferenceChange(MaxWidthPreferenceKey.self) {
self.maxLabelWidth = [=15=]
}
.frame(width: self.maxLabelWidth)
Divider()
}
我所做的是创建了一个 GeometryReader
并将其应用于 VStack
的背景。几何图形告诉我 VStack
的尺寸,它会根据文本的大小自行调整大小。 MaxWidthPreferenceKey
会在几何形状发生变化时更新,并且在 MaxWidthPreferenceKey
中的 reduce
函数计算出最大宽度后,我读取首选项更改并更新 self.maxLabelWidth
。然后我将 VStack
的框架设置为 .frame(width: self.maxLabelWidth)
,并且由于 maxLabelWidth
具有约束力,因此在计算新的 maxLabelWidth
时每个 DayListItem
都会更新。请记住,这里的顺序很重要。将 .frame
修饰符放在 .background
和 .onPreferenceChange
之前将不起作用。