1

I am given an optional date, and would like the ability to display a date picker for it.

import SwiftUI


struct ContentView: View {
    @State var someDate: Date?

    var body: some View {
        OptionalDate(date: $someDate)
    }
}

struct OptionalDate: View {
    @Binding var date: Date?

    var body: some View {

        if date == nil {
            return Text("No date").onTapGesture {
                self.date = Date()   // start picking!
            }
        } else {
            return DatePicker(selection: $date, displayedComponents: .date) {
                Text("Due Date")
            }
        }
    }
}

Cannot convert value of type 'Binding<Date?>' to expected argument type 'Binding<Date>'

Not to mention I'm going to have to figure out the opaque view...

speg
  • 1,959
  • 6
  • 24
  • 34
  • Does this answer your question? [How to assign an optional Binding parameter in SwiftUI?](https://stackoverflow.com/questions/57163055/how-to-assign-an-optional-binding-parameter-in-swiftui) – Chris May 25 '20 at 02:21

1 Answers1

5

iOS 13

Custom bindings are your friends! Also, for conditional views you can always use Group, that will make your life much easier and will resolve the Opaque issue as it will always be considered as a Group whatever you have as a subview.

struct OptionalDateView: View {
    
    @Binding var date: Date?

    var body: some View {
        Group {
            if date == nil {
                Text("No date").onTapGesture {
                    self.date = Date()
                }
            } else {
                DatePicker(selection: dateBinding, displayedComponents: .date) {
                    Text("Due Date")
                }
            }
        }
    }
    
    private var dateBinding: Binding<Date> {
        Binding(get: {
            self.date ?? Date()
        }, set: {
            self.date = $0
        })
    }
}

iOS 14

struct OptionalDateView: View {
    
    @Binding var date: Date?

    var body: some View {
        if date == nil {
            Text("No date").onTapGesture {
                date = Date()
            }
        } else {
            DatePicker(selection: dateBinding, displayedComponents: .date) {
                Text("Due Date")
            }
        }
    }
    
    private var dateBinding: Binding<Date> {
        Binding {
            date ?? Date()
        } set: {
            date = $0
        }
    }
}
Cuneyt
  • 931
  • 5
  • 11
  • Huh! Neat. So this basically forces the binding to unwrap the optional when it is `gotten`? Where can I learn more? – speg May 25 '20 at 03:07
  • This is just a wrapper around `Binding`. So, it basically creates a new `Binding` which returns `self.date!` when asked and calls `self.date = $0` when a new value is set. If we didn't create this binding and passed `$date`, it would work like this: `Binding(get: { self.date, set: { self.date = $0 })` which was creating the problem as `self.date` is optional. Have a look at here: https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-custom-bindings – Cuneyt May 25 '20 at 03:10
  • I notice you can't do `private var dateBinding = Binding(get: {...` because you can't reference other properties? But you can if you return it from a custom getter? Weird! – speg May 25 '20 at 13:58
  • @speg no, it's just you can't use `self` when you define a variable as `self` is not initialized yet. But when you use `computed property`, you can use `self` as computed properties are called after the object (`struct` in this case) is initialized. – Cuneyt May 25 '20 at 21:20