sbooth
sbooth

Reputation: 16986

Aligning views across form sections

I'm creating a form with multiple sections in SwiftUI. Each section contains several text fields, each with a label.

I'd like to align the leading edges of the text fields with each other and also with the center of the view.

I'm able to align the leading edges of the text fields within a section using the following:

extension HorizontalAlignment {
    private struct LeadingEdgeAlignment: AlignmentID {
        /// A custom alignment for leading edges.
        static func defaultValue(in context: ViewDimensions) -> CGFloat {
            context[.leading]
        }
    }

    /// A guide for aligning leading edges.
    static let leadingEdge = HorizontalAlignment(LeadingEdgeAlignment.self)
}

struct ContentView: View {
    var body: some View {
        Form {
            Section(header: Text("Section One")) {
                VStack(alignment: .leadingEdge) {
                    HStack {
                        Text("Primary")
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                    HStack {
                        Text("Secondary")
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                }
            }
            Section(header: Text("Section Two")) {
                VStack(alignment: .leadingEdge) {
                    HStack {
                        Text("Milk")
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                    HStack {
                        Text("Cheesecake")
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                }
            }
        }
    }
}

However, so far all my attempts to create alignment guides that span sections have failed.

How does one align views across form sections?

Upvotes: 3

Views: 481

Answers (2)

sbooth
sbooth

Reputation: 16986

Based on the answer from lorem ipsum I was able to achieve what I wanted using LazyVGrid.

struct ContentView: View {
    let columns = [GridItem(.flexible(), alignment: .trailing), GridItem(.flexible(), alignment: .leading)]
    var body: some View {
        Form {
            Section(header: Text("Section One")) {
                LazyVGrid(columns: columns) {
                    Text("Primary")
                    TextField("", text: .constant(""))
                        .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    Text("Secondary")
                    TextField("", text: .constant(""))
                        .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
            }
            Section(header: Text("Section Two")) {
                LazyVGrid(columns: columns) {
                    Text("Milk")
                    TextField("", text: .constant(""))
                        .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    Text("Cheesecake")
                    TextField("", text: .constant(""))
                        .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
            }
        }
    }
}

Upvotes: 0

lorem ipsum
lorem ipsum

Reputation: 29676

Since you are splitting in half one way to do it is to have even columns. But you have to handle the significant difference in text length

struct AlignedSectionView: View {
    let columns = [GridItem(), GridItem()]
    var body: some View {
        Form {
            Section(header: Text("Section One")) {
                //Divide the section in half
                LazyVGrid(columns: columns, alignment: .leadingEdge) {
                    HStack {
                        Text("Primary")
                        //Push the TextField to the Halfway point
                        Spacer()
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                    //Fill in the column
                    Spacer()
                    HStack {
                        Text("Secondary")
                        Spacer()
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                }
            }
            Section(header: Text("Section Two")) {
                //Divide the section in half
                LazyVGrid(columns: columns, alignment: .leadingEdge) {
                    HStack {
                        Text("Milk")
                        //Push the TextField to the Halfway point
                        Spacer()
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                    //Fill in the column
                    Spacer()
                    HStack {
                        Text("Cheesecake")
                            //So it doesnt go to the next line
                            .lineLimit(1)
                        Spacer()
                        TextField("", text: .constant(""))
                            .frame(minWidth: nil, idealWidth: nil, maxWidth: 75, minHeight: nil, maxHeight: nil)
                            .alignmentGuide(.leadingEdge) { $0[HorizontalAlignment.leading] }
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                    }
                }
            }
        }
        //One way of dealing with the difference in text sizes is to allow the text to get smaller to fit
        .minimumScaleFactor(0.9)
    }
}

Upvotes: 2

Related Questions