SwiftUI登录页面布局 [英] SwiftUI Login Page Layout

查看:764
本文介绍了SwiftUI登录页面布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建登录视图时正在探索SwiftUI,现在我遇到了问题

I am exploring SwiftUI as I am trying to build a login view and now I am facing a problem

这是我要实现的目标:

如您所见,我已经达到了这一点,但是我不喜欢我的实现方式

As you can see I already reached this point but I don't like my implementation

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,我不喜欢,因为很可能会这样.不能在不同尺寸的设备上使用.

Because in order to make the username and password text aligned exactly in the middle of the textfield, I had to put literal spacing value of 20 in the VStack which I don't like because most probably It won't work on different device sizes.

有人看到实现相同结果的更好方法吗?

Anyone sees a better way to achieve the same result?

谢谢

推荐答案

我们将实现两个新的View修饰符方法,以便我们可以编写此代码:

We're going to implement two new View modifier methods so that we can write this:

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)修饰符有两件事:

  1. 它将widthalignment应用于其内容(Text(User:)Text(Password:)视图).
  2. 它测量内容的宽度,并将其传递到任何需要它的祖先视图.
  1. It applies the width and alignment to its content (the Text("User:") and Text("Password:") views).
  2. It measures the width of its content and passes that up to any ancestor view that wants it.

storeMaxLabelWidth(in:)修饰符接收由equalSizedLabel测量的宽度,并将最大宽度存储在我们传递给它的$labelWidth绑定中.

The storeMaxLabelWidth(in:) modifier receives those widths measured by equalSizedLabel and stores the maximum width in the $labelWidth binding we pass to it.

那么,我们如何实现这些修饰符?我们如何将值从后代视图传递到祖先?在SwiftUI中,我们使用(当前未记录的)首选项"系统执行此操作.

So, how do we implement these modifiers? How do we pass a value from a descendant view up to an ancestor? In SwiftUI, we do this using the (currently undocumented) "preference" system.

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

To define a new preference, we define a type conforming to PreferenceKey. To conform to PreferenceKey, we have to define the default value for our preference, and we have to define how to combine the preferences of multiple subviews. We want our preference to be the maximum width of all the labels, so the default value is zero and we combine preferences by taking the maximum. Here's the PreferenceKey we'll use:

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中,如下所示:

The preference modifier function sets a preference, so we can say .preference(key: MaxLabelWidth.self, value: width) to set our preference, but we have to know what width to set. We need to use a GeometryReader to get the width, and it's a little tricky to do properly, so we'll wrap it up in a ViewModifier like this:

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,因为背景的大小总是与其所附加内容的大小相同.后台ViewGeometryReader,它(通过proxy)提供对其自身大小的访问.我们必须给GeometryReader自己的内容.由于我们实际上并不希望在原始内容后面显示背景,因此我们将Color.clear用作GeometryReader的内容.最后,我们使用preference修饰符将宽度存储为MaxLabelWidth首选项.

What's happening above is we attach a background View to the content, because a background is always the same size as the content it's attached to. The background View is a GeometryReader, which (via the proxy) provides access to its own size. We have to give the GeometryReader its own content. Since we don't actually want to show a background behind the original content, we use Color.clear as the GeometryReader's content. Finally, we use the preference modifier to store the width as the MaxLabelWidth preference.

现在可以定义equalSizedLabel(width:alignment:)storeMaxLabelWidth(in:)修饰符方法:

Now have can define the equalSizedLabel(width:alignment:) and storeMaxLabelWidth(in:) modifier methods:

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

结果如下:

这篇关于SwiftUI登录页面布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆