Search code examples
iosswiftswiftuigridviewdatepicker

Aligning a date picker within a grid


I have a SwiftUI view that presents a number of GridRows in a Grid. Each row has 2 columns, the left column is aligned right, and the right column is aligned left. Each row contains a title (left column) and data field (right column).

TextFields, and Pickers behave as I expect, aligning to the left side of their column. But the DatePicker will only align to the right side of its column.

Things I've tried that didn't work:

  • Placing a Spacer() after the DatePicker
  • Putting the DatePicker inside VStack(alignment: .leading) within its GridRow

Anyone know how to move the DatePicker to align with the rest of the rows on the left?

Below is a view to illustrate

enter image description here

import SwiftUI

struct ContentView: View {
    
    @State var name = ""
    @State var date = Date()
    @State var house = Int()
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(alignment: .leading) {
                    Grid(alignment: .leading) {
                        GridRow {
                            Text("Name \(Image(systemName: "person")):")
                                .font(.headline)
                                .gridColumnAlignment(.trailing)
                            TextField("Full name", text: $name)
                                .font(.body)
                                .padding(.horizontal, 10)
                                .padding(.vertical, 5)
                                .background(Color(UIColor.systemBackground))
                                .cornerRadius(8)
                                .gridColumnAlignment(.leading)
                        }
                        GridRow {
                            Text("Birthdate \(Image(systemName: "calendar")):")
                                .font(.headline)
                            DatePicker("", selection: $date, displayedComponents: .date)
                        }
                        GridRow {
                            Text("House \(Image(systemName: "house")):")
                                .font(.headline)
                            Picker("", selection: $house) {
                                Text("Gryffindor").tag(1)
                                Text("Slytherin").tag(2)
                                Text("Ravenclaw").tag(3)
                                Text("Hufflepuff").tag(4)
                            }
                        }
                    }
                }
                .padding(10)
                .background(RoundedRectangle(cornerRadius: 10)
                    .fill(Color.gray
                        .gradient
                        .opacity(0.15)))
            }
            .padding(.horizontal)
            .navigationTitle("Add Wizard Info")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

Solution

  • By inspecting the UI hierarchy, SwiftUI seems to put the date picker inside an HStack, consisting of the label (showing an empty string in your case), a Spacer, and a UIViewRepresentable representing a UIDatePicker.

    enter image description here

    (Red is the HStack, blue is the Spacer, black is the UIViewRepresentable wrapping a UIDatePicker. The label shows "" so is not visible.)

    The Spacer makes the whole thing "expand" horizontally, pushing the date picker to the right.

    Just add .fixedSize() to the DatePicker, so that it is at its ideal size.

    DatePicker("", selection: $date, displayedComponents: .date)
        .fixedSize()
    

    Now the Spacer won't expand, but note that there is still a bit of spacing:

    enter image description here

    That is the default spacing that HStack adds between the label and the date picker. If you want to remove that as well, you can take advantage of the fact that DatePicker counts as a "labeled content" (not sure if this is guaranteed), and apply your own .labeledContentStyle:

    DatePicker("", selection: $date, displayedComponents: .date)
        .labeledContentStyle(ContentOnlyStyle())
    
    // this removes the label, and only shows the content (in this case, the date picker)
    struct ContentOnlyStyle: LabeledContentStyle {
        func makeBody(configuration: Configuration) -> some View {
            configuration.content
        }
    }
    

    Now you don't even need fixedSize.