我们将实现两个新的View
修饰符方法,以便我们可以这样写:
struct ContentView: View {
@State var labelWidth: CGFloat? = nil
@State var username = ""
@State var password = ""
var body: some View {
VStack {
HStack {
Text("User:")
.equalSizedLabel(width: labelWidth, alignment: .trailing)
TextField("User", text: $username)
}
HStack {
Text("Password:")
.equalSizedLabel(width: labelWidth, alignment: .trailing)
SecureField("Password", text: $password)
}
}
.padding()
.textFieldStyle(.roundedBorder)
.storeMaxLabelWidth(in: $labelWidth)
}
}
两个新的修饰符是equalSizedLabel(width:alignment:)
和storeMaxLabelWidth(in:)
。
修饰符做了equalSizedLabel(width:alignment)
两件事:
- 它将
width
andalignment
应用于其内容(Text(“User:”)
和Text(“Password:”)
视图)。
- 它测量其内容的宽度并将其传递给任何需要它的祖先视图。
storeMaxLabelWidth(in:)
修改器接收那些由我们测量的宽度并将equalSizedLabel
最大宽度存储在$labelWidth
我们传递给它的绑定中。
那么,我们如何实现这些修饰符呢?我们如何将值从后代视图传递给祖先?在 SwiftUI 中,我们使用(当前未记录的)“偏好”系统来执行此操作。
为了定义一个新的偏好,我们定义了一个符合PreferenceKey
. 为了符合PreferenceKey
,我们必须为我们的偏好定义默认值,并且我们必须定义如何组合多个子视图的偏好。我们希望我们的偏好是所有标签的最大宽度,所以默认值是零,我们通过取最大值来组合偏好。这是PreferenceKey
我们将使用的:
struct MaxLabelWidth: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = max(value, nextValue())
}
}
preference
修饰符函数设置一个偏好,所以我们可以说设置我们的.preference(key: MaxLabelWidth.self, value: width)
偏好,但我们必须知道width
要设置什么。我们需要使用 aGeometryReader
来获取宽度,并且要正确执行它有点棘手,所以我们将它包装成ViewModifier
这样:
extension MaxLabelWidth: ViewModifier {
func body(content: Content) -> some View {
return content
.background(GeometryReader { proxy in
Color.clear
.preference(key: Self.self, value: proxy.size.width)
})
}
}
上面发生的事情是我们View
为内容附加了一个背景,因为背景总是与它所附加的内容大小相同。背景View
是 a GeometryReader
,它(通过proxy
)提供对其自身大小的访问。我们要给GeometryReader
它自己的内容。由于我们实际上并不想在原始内容后面显示背景,因此我们将Color.clear
其用作GeometryReader
's 内容。最后,我们使用preference
修饰符将宽度存储为首MaxLabelWidth
选项。
现在已经可以定义equalSizedLabel(width:alignment:)
和storeMaxLabelWidth(in:)
修饰方法:
extension View {
func equalSizedLabel(width: CGFloat?, alignment: Alignment) -> some View {
return self
.modifier(MaxLabelWidth())
.frame(width: width, alignment: alignment)
}
}
extension View {
func storeMaxLabelWidth(in binding: Binding<CGFloat?>) -> some View {
return self.onPreferenceChange(MaxLabelWidth.self) {
binding.value = $0
}
}
}
结果如下: