4

我正在探索 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()
    }
}

因为为了使用户名和密码文本在文本字段的中间完全对齐,我不得不在其中放置我不喜欢的文字间距值,20因为VStack它很可能不适用于不同的设备尺寸。

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

谢谢

4

3 回答 3

10

我们将实现两个新的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)两件事:

  1. 它将widthandalignment应用于其内容(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())
    }
}

preference修饰符函数设置一个偏好,所以我们可以说设置我们的.preference(key: MaxLabelWidth.self, value: width)偏好,但我们必须知道width要设置什么。我们需要使用 aGeometryReader来获取宽度,并且要正确执行它有点棘手,所以我们将它包装成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是 a GeometryReader,它(通过proxy)提供对其自身大小的访问。我们要给GeometryReader它自己的内容。由于我们实际上并不想在原始内容后面显示背景,因此我们将Color.clear其用作GeometryReader's 内容。最后,我们使用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
        }
    }
}

结果如下:

结果截图

于 2019-06-19T18:05:33.750 回答
4

您可以将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不会直接影响它的高度,但它只会设置它的内容文本高度的高度。

于 2019-06-17T11:10:49.370 回答
3

如果您希望在 macOS 上使用 s 做类似的事情Button,请注意,从 Xcode 11.3 开始,您将在运行时得到以下结果:

在此处输入图像描述

代替:

在此处输入图像描述

我已经在这篇博文中写下了我的解决方案。它与@rob-mayoff 的答案非常相似,适用于标签和按钮。

于 2020-01-21T14:54:53.073 回答