How to control when a DatePicker "pops up"

I have a Date field that holds the scheduled start date for an activity.. However, activities can be unscheduled (i.e., waiting to be scheduled at some other time). I want to use Date.distantFuture to indicate that the activity is unscheduled. Therefore I am looking to implement logic in my UI that looks something like

@State private var showCalendar: Bool = false
if date == .distantFuture {
Button("unscheduled") {
showCalendar.toggle()
}.buttonStyle(.bordered)
} else {
DatePicker(section: $date)
}
.popover(isPresented: $showCalendar) {
<use DatePicker Calendar view>
}

But this approach requires that I access the DataPicker's Calendar view and I don't know how to do that (and I don't ever what my users to see "Dec 31, 4000"). Any ideas?

(BTW, I have a UIKit Calendar control I could use, but I'd prefer to use the standard control if possible.)

Answered by darkpaw in 832059022

The result of tapping the "unscheduled" button is not to set the date to .now, but to allow the user to schedule the activity, presumably sometime in the future

But that would involve more than one tap anyway, wouldn't it?

My example of .now was just an example so that the DatePicker would be displayed with a valid date, and not the year 4000 as you required. You could choose to craft a date for one week today at noon instead.

If you want a cleaner UI then you should maybe do as I suggested and have the DatePicker disabled if it's unscheduled, and have a toggle that enables the picker?

import SwiftUI
struct ContentView: View {
@State private var scheduled: Bool = false
@State private var date: Date = .now.advanced(by: 24 * 7 * 60 * 60)
var body: some View {
VStack {
Toggle("Scheduled", isOn: $scheduled)
.padding(.horizontal)
Group {
HStack {
DatePicker("Date", selection: $date)
.disabled(!scheduled)
.opacity(scheduled ? 1 : 0.4)
}
.padding()
}
.border(.black.opacity(scheduled ? 1 : 0.2), width: 2)
.padding(.horizontal)
}
}
}
#Preview {
ContentView()
}

Unscheduled:

Scheduled:

Or the HStack in the middle (lines 13-15) could be changed to this so you only ever see a date when the toggle is on:

if(scheduled) {
DatePicker("Date", selection: $date)
.disabled(!scheduled)
.opacity(scheduled ? 1 : 0.4)
} else {
Text("Toggle switch to schedule")
.frame(maxWidth: .infinity)
.opacity(scheduled ? 1 : 0.4)
}

Can you not simply change the date so that the DatePicker appears?

import SwiftUI
struct ContentView: View {
@State private var date: Date = .distantFuture
var body: some View {
HStack {
if(date == .distantFuture) {
Text("Date")
Button("unscheduled") {
date = .now // <<-- This is the important bit
}
.buttonStyle(.bordered)
} else {
DatePicker("Date", selection: $date)
}
}
.padding()
}
}
#Preview {
ContentView()
}

The only thing with this is that the button can never be re-displayed, i.e. once you click it to change date to .now you cannot change it back to .distantFuture. If you need to do that, you'll have to add some extra UI for that to happen.

If I needed to do that, I'd actually put this control in its own section, and have a check box showing it's scheduled when checked (and the DatePicker is then enabled), and unscheduled when not checked (no DatePicker, maybe just some text saying it's unscheduled).

Unfortunately, my situation is a bit more complicated. The result of tapping the "unscheduled" button is not to set the date to .now, but to allow the user to schedule the activity, presumably sometime in the future (but not Dec 31, 4000). Your solution requires two taps to set a correct date — first one to change "unscheduled" to .now, then the second to change .now to the date the user wants. While this will work (with your proviso about not being able to change the date back to "unscheduled", I'm still hoping for a cleaner interface.

Accepted Answer

The result of tapping the "unscheduled" button is not to set the date to .now, but to allow the user to schedule the activity, presumably sometime in the future

But that would involve more than one tap anyway, wouldn't it?

My example of .now was just an example so that the DatePicker would be displayed with a valid date, and not the year 4000 as you required. You could choose to craft a date for one week today at noon instead.

If you want a cleaner UI then you should maybe do as I suggested and have the DatePicker disabled if it's unscheduled, and have a toggle that enables the picker?

import SwiftUI
struct ContentView: View {
@State private var scheduled: Bool = false
@State private var date: Date = .now.advanced(by: 24 * 7 * 60 * 60)
var body: some View {
VStack {
Toggle("Scheduled", isOn: $scheduled)
.padding(.horizontal)
Group {
HStack {
DatePicker("Date", selection: $date)
.disabled(!scheduled)
.opacity(scheduled ? 1 : 0.4)
}
.padding()
}
.border(.black.opacity(scheduled ? 1 : 0.2), width: 2)
.padding(.horizontal)
}
}
}
#Preview {
ContentView()
}

Unscheduled:

Scheduled:

Or the HStack in the middle (lines 13-15) could be changed to this so you only ever see a date when the toggle is on:

if(scheduled) {
DatePicker("Date", selection: $date)
.disabled(!scheduled)
.opacity(scheduled ? 1 : 0.4)
} else {
Text("Toggle switch to schedule")
.frame(maxWidth: .infinity)
.opacity(scheduled ? 1 : 0.4)
}

These are really very nice solutions. Thank you so much for sharing the insights. (I was thinking about a different [and more complex] approach. You have made it clear that i was, as usual, overthinking things.

How to control when a DatePicker "pops up"
 
 
Q