Ios SwiftUI登录页面布局

Ios SwiftUI登录页面布局,ios,swift,swiftui,swift5.1,Ios,Swift,Swiftui,Swift5.1,我正在探索SwiftUI,因为我试图构建一个登录视图,现在我面临一个问题 这就是我努力实现的目标: 正如您所看到的,我已经达到了这一点,但我不喜欢我的实现 struct ContentView : View { @State var username: String = "" var body: some View { VStack(alignment: .leading) { Text("Login") .font(.title)

我正在探索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 > <代码> >代码> > <代码> >中,我不喜欢它的文字间距值,因为它很可能在不同的设备大小上不能工作。 有人认为实现同样结果的更好方法吗


谢谢

您可以使用
垫片
s以及
固定尺寸
高度修饰符。您应该设置任意行对象的高度,以实现精确的
表格样式
视图:

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()
    }
}

请注意
文本字段上设置高度
不会直接影响其高度,但它只会设置其内容文本高度的高度。

我们将实施两种新的
视图
修改器方法,以便编写以下内容:

struct ContentView:View{
@状态变量labelWidth:CGFloat?=nil
@状态变量username=“”
@State var password=“”
var body:一些观点{
VStack{
HStack{
文本(“用户:”)
.equalSizedLabel(宽度:labelWidth,对齐:。尾随)
文本字段(“用户”,文本:$username)
}
HStack{
文本(“密码:”)
.equalSizedLabel(宽度:labelWidth,对齐:。尾随)
SecureField(“密码”,文本:$Password)
}
}
.padding()
.textFieldStyle(.roundedBorder)
.storeMaxLabelWidth(单位:$labelWidth)
}
}
这两个新的修改器是
equalSizedLabel(宽度:对齐:)
storeMaxLabelWidth(in:)

equalSizedLabel(宽度:对齐)
修改器做两件事:

  • 它将
    宽度
    对齐
    应用于其内容(
    文本(“用户:”)
    文本(“密码:”)
    视图)
  • 它测量内容的宽度,并将其传递给任何需要它的祖先视图
  • storeMaxLabelWidth(in:)
    修饰符接收由
    equalSizedLabel
    测量的宽度,并将最大宽度存储在传递给它的
    $labelWidth
    绑定中

    那么,我们如何实现这些修饰符呢?我们如何将值从后代视图传递给祖先?在SwiftUI中,我们使用(当前未记录的)“首选项”系统来实现这一点

    为了定义一个新的首选项,我们定义了一个符合
    PreferenceKey
    的类型。为了符合
    PreferenceKey
    ,我们必须为首选项定义默认值,并且必须定义如何组合多个子视图的首选项。我们希望我们的首选项是所有标签的最大宽度,因此默认值为零,我们通过取最大值来组合首选项。以下是我们将使用的
    首选项键

    struct MaxLabelWidth:PreferenceKey{
    静态var defaultValue:CGFloat{0}
    静态func REDUCT(值:inout值,nextValue:()->值){
    value=max(value,nextValue())
    }
    }
    
    preference
    修饰符函数设置一个首选项,因此我们可以说
    .preference(key:MaxLabelWidth.self,value:width)
    来设置我们的首选项,但我们必须知道要设置的
    宽度。我们需要使用
    GeometryReader
    来获得宽度,这有点棘手,因此我们将在
    ViewModifier
    中对其进行总结,如下所示:

    扩展名MaxLabelWidth:ViewModifier{ func正文(内容:内容)->某些视图{ 返回内容 .background(GeometryReader{proxy in 颜色。清晰 .首选项(键:Self.Self,值:proxy.size.width) }) } }
    上面发生的事情是我们将背景
    视图
    附加到内容上,因为背景总是与其附加到的内容大小相同。背景
    视图
    是一个
    GeometryReader
    ,它(通过
    代理
    )提供对自身大小的访问。我们必须给出
    GeometryReader
    自己的内容。因为我们实际上不想在原始内容后面显示背景,所以我们使用
    Color.clear
    作为
    GeometryReader
    的内容。最后,我们使用
    首选项
    修饰符将宽度存储为
    MaxLabelWidth
    首选项

    现在可以定义
    equalSizedLabel(宽度:对齐:)
    storeMaxLabelWidth(in:)
    修改器方法:

    扩展视图{
    func equalSizedLabel(宽度:CGFloat?,对齐方式:对齐)->一些视图{
    回归自我
    .modifier(MaxLabelWidth())
    .框架(宽度:宽度,对齐:对齐)
    }
    }
    扩展视图{
    func storeMaxLabelWidth(在绑定中:binding)->一些视图{
    返回self.onPreferenceChange(MaxLabelWidth.self){
    binding.value=$0
    }
    }
    }
    
    结果如下:


    如果您希望在macOS上使用
    按钮执行类似操作,请注意,从Xcode 11.3开始,您将在运行时完成以下操作:

    而不是:


    我已经写下了我的解决方案。它与@罗布·梅耶夫的答案非常相似,而且对于标签和按钮都是有效的。

    对不起,我没有正确地考虑这个问题。没问题:你应该考虑从“文本:文本字段”中查看。对于用户名|密码:输入并将其放在VStack中,而不是在HStack中有两个单独的VStack,即文本和文本字段。@MartinM是的,当我这样做时,文本字段未在前导位置对齐。每个文本字段的X位置取决于左侧文本的宽度。I