我们将实施两个新的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:)
and storeMaxLabelWidth(in:)
.
The equalSizedLabel(width:alignment)
修改器做了两件事:
- 它应用了
width
and alignment
其内容(Text(“User:”)
and Text(“Password:”)
views).
- 它测量其内容的宽度并将其传递给任何需要它的祖先视图。
The 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())
}
}
The preference
修饰符函数设置一个偏好,所以我们可以说.preference(key: MaxLabelWidth.self, value: width)
设置我们的偏好,但我们必须知道什么width
设置。我们需要使用一个GeometryReader
来获得宽度,正确地做到这一点有点棘手,所以我们将它包裹在一个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
is a GeometryReader
,其中(通过proxy
) 提供对其自身大小的访问。我们必须给予GeometryReader
它自己的内容。由于我们实际上不想显示原始内容背后的背景,因此我们使用Color.clear
as the GeometryReader
的内容。最后,我们使用preference
修饰符将宽度存储为MaxLabelWidth
偏爱。
现在可以定义equalSizedLabel(width:alignment:)
and 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
}
}
}
结果如下: