在列表行中制作等宽的 SwiftUI 视图 [英] Make Equal-Width SwiftUI Views in List Rows
问题描述
我有一个 List
显示当前月份的天数.List
中的每一行都包含缩写的日期、一个 Divider
和 VStack
中的日期编号.然后将 VStack
嵌入到 HStack
中,以便我可以在日期和数字的右侧添加更多文本.
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 number within a VStack
. The VStack
is then embedded in an HStack
so that I can have more text to the right of the day and number.
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)
}
}
}
代码结果如下:
这可能不是很明显,但分隔线没有对齐,因为文本的宽度可能因行而异.我想要实现的是让包含日期信息的视图具有相同的宽度,以便它们在视觉上对齐.
It may not be obvious, but the dividers are not lined up because the width of the text can vary from row to row. What I would like to achieve is to have the views that contains the day information be the same width so that they are visually aligned.
我曾尝试使用 GeometryReader
和 frame()
修饰符来设置最小宽度、理想宽度和最大宽度,但我需要确保文本可以通过动态类型设置缩小和增长;我选择不使用占父级百分比的宽度,因为我不确定如何确保本地化文本始终适合允许的宽度.
I have tried using a GeometryReader
and the frame()
modifiers to set the minimum width, ideal width, and maximum width, but I need to ensure that the text can shrink and grow with Dynamic Type settings; I chose not to use a width that is a percentage of the parent because I was uncertain how to be sure that localized text would always fit within the allowed width.
如何修改我的视图,使行中的每个视图都具有相同的宽度,而不管文本的宽度如何?
How can I modify my views so that each view in the row is the same width, regardless of the width of text?
关于动态类型,我将创建一个不同的布局,以便在更改该设置时使用.
Regarding Dynamic Type, I will create a different layout to be used when that setting is changed.
推荐答案
我使用 GeometryReader
和 Preferences
让它工作.
I got this to work using GeometryReader
and Preferences
.
首先,在ContentView
中,添加这个属性:
First, in ContentView
, add this property:
@State var maxLabelWidth: CGFloat = .zero
然后,在DayListItem
中,添加这个属性:
Then, in DayListItem
, add this property:
@Binding var maxLabelWidth: CGFloat
接下来,在 ContentView
中,将 self.$maxLabelWidth
传递给 DayListItem
的每个实例:
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)
}
现在,创建一个名为 MaxWidthPreferenceKey
的结构:
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
}
}
这符合 PreferenceKey
协议,允许您在视图之间通信首选项时将此结构用作密钥.
This conforms to the PreferenceKey
protocol, allowing you to use this struct as a key when communicating preferences between your views.
接下来,创建一个名为 DayListItemGeometry
的 View
- 这将用于确定 DayListItem
中 VStack
的宽度代码>:
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()
}
}
然后,在 DayListItem
中,将您的代码更改为:
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()
}
我所做的是创建了一个 GeometryReader
并将其应用到 VStack
的背景.几何图形告诉我 VStack
的尺寸,它根据文本的大小调整自身的大小.MaxWidthPreferenceKey
每当几何变化时都会更新,并且在 MaxWidthPreferenceKey
中的 reduce
函数计算出最大宽度后,我读取了偏好变化并更新了 self.maxLabelWidth
.然后我将 VStack
的框架设置为 .frame(width: self.maxLabelWidth)
,并且由于 maxLabelWidth
是绑定的,每个 DayListItem
在计算新的 maxLabelWidth
时更新.请记住,顺序在这里很重要.将 .frame
修饰符放在 .background
和 .onPreferenceChange
之前将不起作用.
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.
这篇关于在列表行中制作等宽的 SwiftUI 视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!