SwiftUI中具有水平和垂直对齐的布局

时间:2019-06-16 00:03:56

标签: alignment swiftui

我正在尝试完成此布局

desired layout

如果我尝试将HStack包裹在VStack中,则会得到以下信息:

HStack in VStack

如果我尝试将VStack封装在HStack中,则会得到以下信息:

VStack in HStack

有没有一种方法可以使文本与文本字段对齐,并从最长标签到对齐的文本字段的开始获取标准间距?

6 个答案:

答案 0 :(得分:9)

这里不是专家,但我设法通过(1)选择2-VStacks-in-a-HStack的替代方案来实现所需的布局,(2)构筑外部标签, (3)通过分配maxHeight = .infinity使它们脱离默认的垂直扩展约束,并且(4)固定HStack

的高度
struct ContentView: View {
    @State var text = ""
    let labels = ["Username", "Email", "Password"]

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                ForEach(labels, id: \.self) { label in
                    Text(label)
                        .frame(maxHeight: .infinity)
                        .padding(.bottom, 4)
                }
            }

            VStack {
                ForEach(labels, id: \.self) { label in
                    TextField(label, text: self.$text)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
            }
            .padding(.leading)
        }
        .padding(.horizontal)
        .fixedSize(horizontal: false, vertical: true)
    }
}

以下是预览结果:

enter image description here

为了解决外部标签和内部标签的基线未对齐(与该特定布局无关的附带问题,请参见this discussion),我手动添加了填充

this website致谢,以启发我理解的道路 SwiftUI布局技巧

答案 1 :(得分:0)

    var body: some View {

    VStack {
        HStack {
            Text("Username")
            Spacer()
            TextField($username)
                .textFieldStyle(.roundedBorder)
                .frame(maxWidth: 200)
                .foregroundColor(.gray)
                .accentColor(.red)
        }
        .padding(.horizontal, 20)
        HStack {
            Text("Email")
            Spacer()
            TextField($email)
                .textFieldStyle(.roundedBorder)
                .frame(maxWidth: 200)
                .foregroundColor(.gray)
            }
            .padding(.horizontal, 20)
        HStack {
            Text("Password")
            Spacer()
            TextField($password)
                .textFieldStyle(.roundedBorder)
                .frame(maxWidth: 200)
                .foregroundColor(.gray)
            }
            .padding(.horizontal, 20)
    }
}

This is the results of the above code. It use an HStack wrapped in a VStack and has the alignment you are trying to achieve

答案 2 :(得分:0)

您可以为此使用kontikigeometry reader hack

SwiftUI live preview

struct Column: View {
    @State private var height: CGFloat = 0
    @State var text = ""
    let spacing: CGFloat = 8

    var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: spacing) {
                Group {
                    Text("Hello world")
                    Text("Hello Two")
                    Text("Hello Three")
                }.frame(height: height)
            }.fixedSize(horizontal: true, vertical: false)
            VStack(spacing: spacing) {
                TextField("label", text: $text).bindHeight(to: $height)
                TextField("label 2", text: $text)
                TextField("label 3", text: $text)
            }.textFieldStyle(RoundedBorderTextFieldStyle())
        }.fixedSize().padding()
    }
}

extension View {
    func bindHeight(to binding: Binding<CGFloat>) -> some View {
        func spacer(with geometry: GeometryProxy) -> some View {
            DispatchQueue.main.async { binding.value = geometry.size.height }
            return Spacer()
        }
        return background(GeometryReader(content: spacer))
    }
}

我们仅在这里读取第一个TextField的高度,并将其在三个不同的Text View上应用三次,假设所有TextField的高度均相同。如果您的三个TextField的高度不同或具有影响各个高度的出现/消失的验证标签,则可以使用相同的技术,但是使用三个不同的高度绑定。

为什么这有点骇人听闻?

因为此解决方案将始终首先呈现不带标签的TextField。在此渲染阶段,它将设置文本标签的高度并触发另一个渲染。最好在一个布局阶段中呈现所有内容。

答案 3 :(得分:0)

您需要添加固定宽度和前导对齐。我已经在Xcode 11.1中测试过,没关系。

struct TextInputWithLabelDemo: View {
    @State var text = ""
    let labels = ["Username", "Email", "Password"]

    var body: some View {
        VStack {
            ForEach(labels, id: \.self) { label in
                HStack {
                    Text(label).frame(width: 100, alignment: .leading)
                    TextField(label, text: self.$text)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
            }
            .padding(.horizontal)
            .fixedSize(horizontal: false, vertical: true)
        }
    }
}

下面您可以看到当我们为VStackText使用不同的TextField时出现的问题。查看更多信息here enter image description here

更新于2019年10月16日

仔细检查TextsTextFields,您会发现它们的高度不同,并且会影响Texts相对于TextFields的位置,如您在屏幕截图的右侧,相对于Password TextPassword TextField比相对于Username Text的{​​{1}}高。 我提供了三种解决此问题的方法here

enter image description here

答案 4 :(得分:-1)

看起来像这样:

extension HorizontalAlignment {
    private enum MyAlignment: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> Length {
            context[.trailing]
        }
    }
    static let myAlignmentGuide = HorizontalAlignment(MyAlignment.self)
}

struct ContentView : View {
    @State var username: String = ""
    @State var email: String = ""
    @State var password: String = ""

    var body: some View {
        VStack(alignment: .myAlignmentGuide) {
            HStack {
                Text("Username").alignmentGuide(.myAlignmentGuide, computeValue: { d in d[.trailing] })
                TextField($username)
                    .textFieldStyle(.roundedBorder)
                    .frame(maxWidth: 200)
            }
            HStack {
                Text("Email")
                    .alignmentGuide(.myAlignmentGuide, computeValue: { d in d[.trailing] })
                TextField($email)
                    .textFieldStyle(.roundedBorder)
                    .frame(maxWidth: 200)
            }
            HStack {
                Text("Password")
                    .alignmentGuide(.myAlignmentGuide, computeValue: { d in d[.trailing] })
                TextField($password)
                    .textFieldStyle(.roundedBorder)
                    .frame(maxWidth: 200)
            }
        }
    }
}

使用该代码,我可以实现以下布局:

Aligned layout

这里的警告是,我必须为TextField指定最大宽度。不受限制,我在评论中链接的WWDC演讲中描述的布局系统检索TextField 之前的大小以进行对齐,从而导致TextField的电子邮件扩展到其他两个的结尾。我不确定如何解决这个问题,以使TextField s可以扩展到包含视图的大小而无需继续...

答案 5 :(得分:-1)

    HStack{
        Image(model.image)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 10, alignment: .leading)
        VStack(alignment: .leading) {
            Text("Second column ")
            Text("Second column -")
        }
        Spacer()
        Text("3rd column")
    }

1- 第一列 - 图片

2- 第二列 - 两个文本

3- 浮点值

Spacer() - 使用 Spacer() -> 上面的示例图像和 vstack 保持在一起垂直对齐所有行,只需在另一个垂直对齐/列中为您想要做的视图放置间隔器 VStack(alignment: .leading. - 这对于开始对齐很重要

相关问题