在列表行中制作等宽的 SwiftUI 视图
Posted
技术标签:
【中文标题】在列表行中制作等宽的 SwiftUI 视图【英文标题】:Make Equal-Width SwiftUI Views in List Rows 【发布时间】:2019-07-11 21:50:18 【问题描述】:我有一个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()
修饰符来设置最小宽度、理想宽度和最大宽度,但我需要确保文本可以通过动态类型设置缩小和增长;我选择不使用占父元素百分比的宽度,因为我不确定如何确保本地化文本始终适合允许的宽度。
如何修改我的视图,使行中的每个视图都具有相同的宽度,而不管文本的宽度如何?
关于动态类型,我将创建一个不同的布局以在更改该设置时使用。
【问题讨论】:
【参考方案1】:我使用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 = $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
之前将不起作用。
【讨论】:
这正是我正在寻找的行为!你还记得你在哪里了解到PreferenceKey
协议吗?我查看了文档,但它非常缺乏。
是的,不幸的是,SwiftUI 的文档几乎不存在。我从这个article 了解到PreferenceKey
。到目前为止,他们只发布了几篇文章,但他们发布的那些已经对我帮助很大,所以我会留意他们何时发布更多文章。
@graycampbell scaledToFill
导致我的内容挤压该行的其余内容。如果我删除它,那么修改后的内容就会变得非常薄。如何测量要修改的内容的原始宽度?
这已经获得了 28 票,所以它一定在某个时候奏效了。我遇到了一个已提交雷达的错误,但没有提供解决方案。 “绑定首选项 WidthPreferenceKey 尝试每帧更新多次。”有没有办法让这个工作?
我将列表嵌入到滚动视图中。我删除了它,错误消失了。再次感谢!以上是关于在列表行中制作等宽的 SwiftUI 视图的主要内容,如果未能解决你的问题,请参考以下文章