Shadowman
Shadowman

Reputation: 12079

SwiftUI DatePicker -- Allow no date at all?

I'm working on my first SwiftUI app, and have implemented a Form with one of the fields being a DatePicker. However, this value is completely optional. So, it is possible that the user doesn't enter a value at all. I'm unable to figure out, though, how I can make it so that the user doesn't need to enter the value. The code itself is rather simple:

    DatePicker(selection: $expirationDate,
               displayedComponents: .date) {
        Text("Expiration")
    }

Is there a way so that I can indicate that $expirationDate can have a Date or can be nil? The DatePicker by default seems to set the value to the current Date and I couldn't figure out a way to override that behavior. Thoughts?

Upvotes: 8

Views: 4527

Answers (2)

JPetric
JPetric

Reputation: 3918

My solution is similar to bdan's solution, but in his solution, once you set the date, you cannot remove it.

Date not set: enter image description here

Date set (with option to remove it): enter image description here

This is the code:

struct DatePickerOptional: View {

let label: String
let prompt: String
let range: PartialRangeThrough<Date>
@Binding var date: Date?
@State private var hidenDate: Date = Date()
@State private var showDate: Bool = false

init(_ label: String, prompt: String, in range: PartialRangeThrough<Date>, selection: Binding<Date?>) {
    self.label = label
    self.prompt = prompt
    self.range = range
    self._date = selection
}

var body: some View {
    ZStack {
        HStack {
            Text(label)
                .multilineTextAlignment(.leading)
            Spacer()
            if showDate {
                Button {
                    showDate = false
                    date = nil
                } label: {
                    Image(systemName: "xmark.circle")
                        .resizable()
                        .frame(width: 16, height: 16)
                        .tint(.textPrimary)
                }
                DatePicker(
                    label,
                    selection: $hidenDate,
                    in: range,
                    displayedComponents: .date
                )
                .labelsHidden()
                .onChange(of: hidenDate) { newDate in
                    date = newDate
                }

            } else {
                Button {
                    showDate = true
                    date = hidenDate
                } label: {
                    Text(prompt)
                        .multilineTextAlignment(.center)
                        .foregroundColor(.textPrimary)
                }
                .frame(width: 120, height: 34)
                .background(
                    RoundedRectangle(cornerRadius: 8)
                        .fill(Color.customPickerBackground)
                )
                .multilineTextAlignment(.trailing)
            }
        }
    }
}

}

Upvotes: 6

bdan
bdan

Reputation: 41

You can add Text on top of the DatePicker that does not receive touch events, then add a white background with a prompt. This will allow the date to still be clickable.

struct DatePickerNullable: View {
    
    let label:String
    @Binding var date:Date?
    @State private var hidenDate:Date = Date()
    
    init(_ label:String, selection:Binding<Date?>){
        self.label = label
        self._date = selection
    }
    
    
    var body: some View {
        GeometryReader{ proxy in
            ZStack{
                DatePicker(label, selection: $hidenDate, displayedComponents: .date)
                if date == nil{
                    Text(label)
                        .italic()
                        .foregroundColor(.gray)
                        .frame(width: proxy.size.width - CGFloat(label.count * 8), alignment: .trailing)
                        .background(Color.white)
                        .frame(maxWidth: .infinity, alignment: .trailing)
                        .allowsHitTesting(false)
                }
            }.onChange(of: hidenDate, perform: { _ in
                self.date = hidenDate
            })
        }
    }
}

Default state

Clicked state

Upvotes: 4

Related Questions