2

I'm trying to build a DatePicker that can handle an optional date.

I've used this post as a jumping off point... but my code, at present, crashes after just a couple of toggles between nil and Date().

How can I make this code safe?

import SwiftUI

extension Date {
    public var m3d2y4: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM dd, yyyy"
        return formatter.string(from: self)
    }
}

struct OptionalDatePicker: View {
    @Binding var date: Date?
    var nilText = "Never"
    var nilDate = Date()
    
    var body: some View {
        if let _ = date {
            datePicker
        } else {
            nilTextButton
        }
    }
    
    private var dateBinding: Binding<Date> {
        Binding(get: { date ?? nilDate }, set: { date = $0 })
    }
    
    private var dateString: String {
        dateBinding.wrappedValue.m3d2y4
    }

    private var nilTextButton: some View {
        Button(action: toggleDate) {
            Text(nilText)
        }
    }
    
    private var datePicker: some View {
        ZStack {
            HStack {
                Text(dateString)
                    .foregroundColor(.blue)
                Button(action: { toggleDate() }) {
                    Image(systemName: "xmark.circle")
                }
            }
            DatePicker("", selection: dateBinding, displayedComponents: [.date])
                .opacity(0.02) // minimum opacity to still allow tapping
                .labelsHidden()
        }
    }
    
    private func toggleDate() {
        if let _ = date {
            date = nil
        } else {
            date = nilDate
        }
    }
}


struct OptionalDatePicker_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
    
    struct ContentView: View {
        @State var date: Date? = nil
        
        var body: some View {
            HStack {
                Text("Ends")
                Spacer()
                OptionalDatePicker(date: $date)
            }
            .padding()
        }
    }
}

Thanks in advance!

2
  • You do something weird here - .opacity(0.02) is a reason. Commented Sep 2, 2021 at 15:09
  • The Picker is in front of the Text label so that I can still bring up the calendar on tap. If opacity set to 0, it's no longer tappable (not sure why?)... With some trial-and-error I've determined that .opacity(0.02) is the lowest value that still allows and captures a tap. If DatePicker had an isPresented argument like NavigationLink I don't think I'd need to do it this way. Commented Sep 2, 2021 at 16:03

2 Answers 2

0

There is actually not native and nice solution to this. The component takes strictly Binding<Date> and there is no workaround for this.

Workaround would be to create your custom component which takes optional Binding.

Sign up to request clarification or add additional context in comments.

Comments

-1

Noting the SO post you link to in your question, I'll repeat my answer here...

I learned almost all I know about SwiftUI Bindings (with Core Data) by reading this blog by Jim Dovey. The remainder is a combination of some research and quite a few hours of making mistakes.

So when I use Jim's technique to create Extensions on SwiftUI Binding then we end up with something like this for a deselection to nil...

public extension Binding where Value: Equatable {
    init(_ source: Binding<Value>, deselectTo value: Value) {
        self.init(get: { source.wrappedValue },
                  set: { source.wrappedValue = $0 == source.wrappedValue ? value : $0 }
        )
    }
}

Which can then be used throughout your code like this...

Picker("", 
       selection: Binding($date, deselectTo: nil),
       displayedComponents: [.date]
) 

... where date is an optional, for example...

@State private var date: Date?

3 Comments

In your example you are using Picker instead of DatePicker. If you try to use that binding on a DatePicker you get: Cannot convert value of type 'Binding<Date?>' to expected argument type 'Binding<Date>'
@LluisGerard thanks for pointing this out... I'll do some testing, but on first look, the error message you've presented is a type mismatch, so perhaps that is unrelated to the type of picker that is being used? I'll have a closer look in the next day or so.
@DominikBucher, the question, quite explicitly, states "How can I make this code safe?". I provided a method for the OP to consider.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.