1

I'm trying to achieve a right-aligned label layout. Here's an example:

Right-aligned example

Note that the labels on the left are right-aligned. It seems simple, but I haven't found a way to do it in SwiftUI in a way that can handle dynamic changes (localized labels, dynamic text, etc).

I've tried having each row as an HStack and using a custom alignment on the labels:

struct RightAligned: View {
    @State var name = ""
    @State var email = ""
    @State var phone = ""

    var body: some View {
        VStack(alignment: .labelAlign) {
            Row(name: "Name", value: $name)
            Row(name: "Email", value: $email)
            Row(name: "Phone", value: $phone)
        }
        .border(Color.green)
        .padding(30)
    }
}

struct Row: View {
    let name: String
    @Binding var value: String

    var body: some View {
        HStack {
            Text("\(name):")
                .alignmentGuide(.labelAlign, computeValue: { $0[.trailing] })
            TextField(name, text: $value)
        }
    }
}

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

This will align the labels, but it just shifts the HStacks so they expand outside the containing VStack:

enter image description here

Also tried using preferences to pass the widths of the labels up to the parent so I can make all the label frames the same size:

struct PrefsAlignment: View {
    @State var labelWidth: CGFloat = 100
    @State var name = ""
    @State var email = ""
    @State var phone = ""

    var body: some View {
        VStack(alignment: .leading) {
            PrefsRow(name: "Name", labelWidth: $labelWidth, value: $name)
            PrefsRow(name: "Email", labelWidth: $labelWidth, value: $email)
            PrefsRow(name: "Phone", labelWidth: $labelWidth, value: $phone)
        }
        .onPreferenceChange(LabelPreferenceKey.self) { width in
            self.labelWidth = width
        }
    }
}

struct PrefsRow: View {
    let name: String
    @Binding var labelWidth: CGFloat
    @Binding var value: String

    var body: some View {
        HStack {
            HStack {
                Spacer()
                Text("\(name):")
                    .background(PreferenceSetterView())
            }
            .frame(width: labelWidth)
            TextField(name, text: $value)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }
}

struct PreferenceSetterView: View {
    var body: some View {
        GeometryReader { proxy in
            Rectangle()
                .fill(Color.clear)
                .preference(key: LabelPreferenceKey.self, value: proxy.size.width)
        }
    }
}


struct LabelPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())
    }
}

This puts the views in a feedback loop where the preferences drive a change to the frames of the label HStacks which recursively changes the preferences and around we go until the width is driven to 0.

You could manually set the frames of the labels to the widest one, but this is fragile and wouldn't work if they change due to things like localization or dynamic type.

How in the world do you do this simple layout?

smr
  • 890
  • 7
  • 25
  • Did you try two `VStack`s: one for labels and another for text fields; then place these in a `HStack`? – meaning-matters Oct 07 '19 at 20:01
  • Yes. They don't line up. Also, if you wanted to have, say, two checkboxes stacked vertically that are associated with a single label, then there must be some code that aligns them – smr Oct 07 '19 at 20:33
  • 1
    https://stackoverflow.com/a/56673501/77567 – rob mayoff Oct 08 '19 at 00:15
  • So close! Instead of wrapping the `Text` in an `HStack`, just apply the frame directly with trailing alignment. Upvoted your answer in the other thread. Thanks! – smr Oct 08 '19 at 00:52
  • Possible duplicate of [SwiftUI Login Page Layout](https://stackoverflow.com/questions/56623310/swiftui-login-page-layout) – gotnull Oct 09 '19 at 04:40
  • Does this answer your question? [How to right-align item labels in a custom SwiftUI form on AppKit?](https://stackoverflow.com/questions/58463568/how-to-right-align-item-labels-in-a-custom-swiftui-form-on-appkit) – Damiaan Dufaux Feb 14 '20 at 14:17
  • 1
    Does this answer your question? [How to make TextField align right (trailing)](https://stackoverflow.com/questions/56552087/how-to-make-textfield-align-right-trailing) – Josh Correia May 30 '20 at 18:22

0 Answers0