Dismiss SwiftUI DatePicker when user taps on already selected date - ios

I want the user to select a date in the past by presenting a SwiftUI DatePicker. The date binding is set to today's date.
Now, I want the user to be able to select today's date. But when the user taps on the already selected date, the DatePicker is not dismissed. It only is dismissed when the user taps on a date that was not already selected. So in my case I want to dismiss the DatePicker when the user taps on 13.
Is there a way to dismiss the DatePicker when the user taps on the preselected date?
Here is how I use the DatePicker:
struct DatePickerView: View {
#Binding var date: Date
var body: some View {
DatePicker(
"Select Date",
selection: $date,
in: ...Date(),
displayedComponents: [.date]
)
.datePickerStyle(.graphical)
.background(Color.white)
.cornerRadius(8)
.padding(.horizontal, 40)
}
}

I had a similar problem and put a .graphical DatePicker in my own popover. The only downside is on iPhone popovers currently show as sheets which is an annoying inconsistency. Now you can control the popover presentation however you like and even have cancel/done actions.
struct DatePickerPopover: View {
#State var showingPicker = false
#State var oldDate = Date()
#Binding var date: Date
let doneAction: () -> ()
var body: some View {
Text(date, format:.dateTime.year())
.foregroundColor(.accentColor)
.onTapGesture {
showingPicker.toggle()
}
.popover(isPresented: $showingPicker, attachmentAnchor: .point(.center)) {
NavigationStack {
DatePicker(selection: $date
, displayedComponents: [.date]){
}
.datePickerStyle(.graphical)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
date = oldDate
showingPicker = false
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
doneAction()
showingPicker = false
}
}
}
}
}
.onAppear {
oldDate = date
}
}
}

Related

How to disable the future date selection from DatePicker in SwiftUI [duplicate]

This question already has an answer here:
Is it possible to set SwiftUI DatePicker minimumDate using bindings?
(1 answer)
Closed 7 months ago.
I am working on a Custom DatePicker in SwiftUI, for that I have written below code which is woking great -
struct DatePickerView: View {
#State var searchDate = Date()
let title = "Select a date"
let dateChanged: (_ date: Date) -> Void
var body: some View {
VStack {
DatePicker(selection: Binding(get: {
self.searchDate
}, set: { newVal in
self.searchDate = newVal
dateChanged(searchDate)
}), displayedComponents: .date) {
Text(title)
.font(.headline)
}
.background(.bar)
}
}
}
Now I want to disable the future date selection, can someone help me to do that?
It can be done with allowed range, like
DatePicker(selection: Binding(get: {
self.searchDate
}, set: { newVal in
self.searchDate = newVal
dateChanged(searchDate)
}), in: ...Date(), // << here !!
displayedComponents: .date) {
Text(title)
.font(.headline)
}

SwiftUI: Scrolling Sticking when using LazyVGrid inside a Scrollview:

The below code seems to work fine on an iPhone 13 mini but stutters or sticks when scrolling on an iPhone 13, not every time but often. The code for the Calendar is here. What could be causing this? 🤔
struct MasterCalendarWithDay: View {
#Environment(\.calendar) var calendar
#State private var selectedDate: Date?
#State private var selectedTabIndex = 0
private var month: DateInterval {
//calendar.dateInterval(of: .year, for: Date())!
DateInterval(start: Calendar.current.date(byAdding: .month, value: show6Months ? -6 : -1, to: Date())!, end: Date())
}
var body: some View {
LoadingView(isShowing: $isLoadingRecoveryData, text: show6Months ? "Loading past 6 months of data. This may take a few seconds." : "Loading...") {
NavigationView {
ScrollView {
ScrollViewReader { value in
VStack {
ZStack {
CalendarView(interval: month) { date in
if date <= Date() {
Button(action: { self.selectedDate = date }) {
//Omitted
}.buttonStyle(PlainButtonStyle())
}
} // end of calender
.onAppear {
value.scrollTo(Date().startOfDay())
}
} //end of Zstack
}
}
} //end of scroll view
.coordinateSpace(name: "pullToRefreshInRecoveryCalendar")
.navigate(using: $selectedDate, destination: makeDestination)
}
.navigationViewStyle(.stack) //This asks SwiftUI to only show one view at a time, regardless of what device or orientation is being used, and prevents constraint warnings in console log. From: https://www.hackingwithswift.com/articles/216/complete-guide-to-navigationview-in-swiftui
.onChange(of: selectedTabIndex, perform: { value in
})
}
}
I think your ScrollView should be nested inside the ScrollViewReader.
var body: some View {
ScrollViewReader { value in
ScrollView {
...
See
https://developer.apple.com/documentation/swiftui/scrollviewreader

Detect when user taps out of or saves on DatePicker

I'm trying to fetch some data on a network call when a user selects a time from DatePicker.
I originally tested using .onChange but this fires every time that changes, and not on the final tap off.
DatePicker("Title", selection: $currentDate, displayedComponents: .hourAndMinute)
.onChange(of: $currentDate) { value in
print(value)
}
I also tried using the didset{} but that also fired on every change too.
#Published var currentDate: Date = Date() {
didSet { print(currentDate) }
}
What I want to do is once the user selects a time, I fire off some functions. I wanted to not do it every cycle in the wheel.
Is there a way to do this in SwiftUI or UIKit importing into SwiftUI?
Please see attached of what I'm looking at:
You could try rolling your own DatePickerView, such as this approach:
struct ContentView: View {
#State private var birthdate = Date()
var body: some View {
DatePickerView(date: $birthdate)
}
}
struct DatePickerView: View {
#Binding var date: Date
#State var hasChanged = false
var body: some View {
ZStack {
Color.white.opacity(0.01).ignoresSafeArea()
.onTapGesture {
if hasChanged {
print("---> do a network call now with: \(date)")
hasChanged = false
}
}
DatePicker("Birth Date", selection: $date, displayedComponents: .hourAndMinute)
.pickerStyle(.wheel)
.onChange(of: date) { val in
hasChanged = true
}
}
}
}

How to automatically collapse DatePicker in a form when other field is being edited?

I got a simple SwiftUI view:
import SwiftUI
struct AddItemView: View {
#State private var title = ""
#State private var date = Date()
var body: some View {
Form {
Section {
TextField("Title", text: $title)
DatePicker(
selection: $date,
in: Date()...,
displayedComponents: .date,
label: { Text("Date") }
)
}
}
}
}
struct AddItemView_Previews: PreviewProvider {
static var previews: some View {
AddItemView()
}
}
I am trying to achieve the following:
If DatePicker is expanded (user tapped date picker, picker showing wheel to select date) and then starts typing text in TextField, DatePicker should automatically switch to initial, minimized mode (just showing label and selected date). Please take a look at screenshot. This is a behaviour in a stock Calendar.app, for example, when creating events.
Any help appreciated, thank you.
Here is possible approach. The idea is to reset DatePicker component for each of events result in editing.
Tested with Xcode 11.4 / iOS 13.4
struct AddItemView: View {
#State private var title = ""
#State private var date = Date()
#State private var pickerReset = UUID()
var body: some View {
Form {
Section {
TitleTextField()
DatePicker(
selection: $date,
in: Date()...,
displayedComponents: .date,
label: { Text("Date") }
).id(self.pickerReset)
}
}
}
private func TitleTextField() -> some View {
let boundText = Binding<String>(
get: { self.title },
set: { self.title = $0; self.pickerReset = UUID() }
)
return TextField("Title", text: boundText, onEditingChanged: { editing in
if editing {
self.pickerReset = UUID()
}
})
}
}

Change selected date format from DatePicker SwiftUI

Is there a way to format the date?(from the position indicated by the arrow in the picture) I know it is formatted based on the locale but is there a way to format it myself?
struct ContentView: View {
#State private var selectedDate = Date()
var body: some View {
Form {
DatePicker(selection: $selectedDate, in: ...Date(), displayedComponents: .date) {
Text("From*")
}
}
}
}
The only way I could figure to accomplish this is to create my own custom DatePicker view and use onAppear on the TextField to update an #State selectedDateText: String variable for displaying in the TextField. This feels like a hack and I’m almost embarrassed to post it but it works. I’m new at Swift and iOS programming in general so I’m sure someone will come along with a better answer so I’ll offer this for what it’s worth. My custom view is something like this:
struct CustomDatePicker: View {
#Binding var date: Date
#State private var showPicker: Bool = false
#State private var selectedDateText: String = "Date"
private var selectedDate: Binding<Date> {
Binding<Date>(get: { self.date}, set : {
self.date = $0
self.setDateString()
})
} // This private var I found… somewhere. I wish I could remember where
// To take the selected date and store it as a string for the text field
private func setDateString() {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM dd, yyyy"
self.selectedDateText = formatter.string(from: self.date)
}
var body: some View {
VStack {
HStack {
Text("Date:")
.frame(alignment: .leading)
TextField("", text: $selectedDateText)
.onAppear() {
self.setDateString()
}
.disabled(true)
.onTapGesture {
self.showPicker.toggle()
}
.multilineTextAlignment(.trailing)
}
if showPicker {
DatePicker("", selection: selectedDate,
displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle())
.labelsHidden()
}
}
}
}
EDIT: I figured out where I got the private var code. It was from this post: How to detect a value change of a Datepicker using SwiftUI and Combine?

Resources