SwiftUI登录页面布局

Mos*_*fat 2 ios swift swiftui swift5.1

我正在尝试建立登录视图时正在探索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()
    }
}
Run Code Online (Sandbox Code Playgroud)

因为为了使在文本框的中间正好对准用户名和密码的文本,我不得不把字面间距值20VStack,我不喜欢,因为最有可能它不会在不同的设备尺寸的工作。

有人看到一种更好的方法来达到相同的结果吗?

谢谢

Moj*_*ini 5

您可以将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()
    }
}
Run Code Online (Sandbox Code Playgroud)

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


rob*_*off 5

我们将实现两个新的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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这两个新的修饰符是equalSizedLabel(width:alignment:)storeMaxLabelWidth(in:)

equalSizedLabel(width:alignment)修改做了两两件事:

  1. 它将widthalignment应用于其内容(Text(“User:”)Text(“Password:”)视图)。
  2. 它测量其内容的宽度,并将其传递给想要它的任何祖先视图。

所述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())
    }
}
Run Code Online (Sandbox Code Playgroud)

preference修正功能设置首选项,所以我们可以说.preference(key: MaxLabelWidth.self, value: width),以确定我们的喜好,但我们要知道什么width来定。我们需要使用a 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)
            })
    }
}
Run Code Online (Sandbox Code Playgroud)

上面发生的事情是我们View为内容附加了背景,因为背景的大小总是与其所附加内容的大小相同。背景ViewGeometryReader,通过(可通过proxy)访问其自身的大小。我们必须给出GeometryReader自己的内容。由于我们实际上并不希望在原始内容后面显示背景,因此我们将其Color.clear用作GeometryReader的内容。最后,我们使用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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

结果截图