在尝试构建登录视图时,我正在探索SwiftUI,但现在我面临着一个问题
这就是我想要实现的:

正如您所看到的,我已经达到了这一点,但是我不喜欢我的实现
struct ContentView : View {
@State var username: String = ""
var body: some View {
VStack(alignment: .leading) {
Text("Login")
.font(.title)
.multilineTextAlignment(.center)
.lineLimit(nil)
Text("Please")
.font(.subheadline)
HStack {
VStack (alignment: .leading, spacing: 20) {
Text("Username: ")
Text("Password: ")
}
VStack {
TextField($username, placeholder: Text("type something here..."))
.textFieldStyle(.roundedBorder)
TextField($username, placeholder: Text("type something here..."))
.textFieldStyle(.roundedBorder)
}
}
}.padding()
}
}因为为了使用户名和密码文本在文本字段的中间完全对齐,我不得不在VStack中放入20的文字间隔值,这是我不喜欢的,因为它很可能在不同大小的设备上不起作用。
有没有人看到更好的方法来达到同样的结果?
谢谢
发布于 2019-06-20 02:05:34
我们将实现两个新的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和alignment应用于其内容( 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。我们需要使用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是一个GeometryReader,它(通过proxy)提供对其自身大小的访问。我们必须给GeometryReader提供自己的内容。因为我们实际上不想在原始内容后面显示背景,所以我们使用Color.clear作为GeometryReader的内容。最后,我们使用preference修饰符将宽度存储为MaxLabelWidth首选项。
现在have可以定义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
}
}
}结果如下:

发布于 2019-06-17 19:10:49
您可以将Spacers与高度的fixedSize修饰符一起使用。您应该设置任何行的对象的高度,以实现精确的table style视图:
struct ContentView : View {
private let height: Length = 32
@State var username: String = ""
var body: some View {
VStack(alignment: .leading) {
Text("Login")
.font(.title)
.multilineTextAlignment(.center)
.lineLimit(nil)
Text("Please")
.font(.subheadline)
HStack {
VStack (alignment: .leading) {
Text("Username: ") .frame(height: height)
Spacer()
Text("Password: ") .frame(height: height)
}
VStack {
TextField($username, placeholder: Text("type something here..."))
.textFieldStyle(.roundedBorder)
.frame(height: height)
Spacer()
TextField($username, placeholder: Text("type something here..."))
.textFieldStyle(.roundedBorder)
.frame(height: height)
}
}
.fixedSize(horizontal: false, vertical: true)
}
.padding()
}
}请注意,在TextField上设置高度不会直接影响它的高度,但它只会设置其内容文本的高度。
发布于 2020-01-21 22:54:53
如果您希望在macOS上使用Buttons执行类似的操作,请注意,从Xcode11.3开始,您将在运行时得到以下代码:

而不是:

我已经写好了我的解决方案in this blog post。它与@rob-mayoff的答案非常相似,对标签和按钮都有效。
https://stackoverflow.com/questions/56623310
复制相似问题