Any date range in DatePicker for SwiftUI - swiftui

Normally we need a range, for example:
DatePicker(selection: $birthDate, in: ...Date(), displayedComponents: .date)
We can select a date for previous date. How can we create a DatePicker which is able to select any date?
There is a method to allow to select a date within a specific range:
var dateClosedRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .day, value: -10000, to: Date())!
let max = Calendar.current.date(byAdding: .day, value: 10000, to: Date())!
return min...max
}
Is there a better way?

Just leave the range out:
DatePicker("Date of birth", selection: $birthDate, displayedComponents: .date)
or:
DatePicker(selection: $birthDate, displayedComponents: .date) { Text("Date of birth") }

Related

Value of type 'DateComponents' has no member 'DateComponentsFormatter'

I am trying to use the DateComponentFormatter to remove the details in the NSDateComponent (see screenshot) but it throws me 3 errors:
Cannot convert return expression of type '()' to return type 'DateComponents'
Reference to member 'year' cannot be resolved without a contextual type
Value of type 'DateComponents' has no member 'DateComponentsFormatter'
I tried setting a lazy var formatter = DateComponentsFormatter like in the tutorials, but it more or less throw the same error. It also doesn't work attaching the formatter to the remainder but it throws the mutatable error.
Big thanks in advance!
import SwiftUI
import Foundation
struct ContentView: View {
#State private var birthDay: Date = Calendar.current.date(byAdding: DateComponents(year: -30), to: Date()) ?? Date()
lazy var remainder = Calendar.current.date(byAdding: DateComponents(year: 73), to: birthDay)
lazy var remainderComp = Calendar.current.dateComponents([.year], from: now, to: remainder!)
let now = Date()
var body: some View {
VStack {
NavigationView {
VStack {
Text("Your birthday...?")
.font(.title)
DatePicker("", selection: $birthDay, in:...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
Text("""
The world's average life expectancy in 2022 is
73 years
""")
.multilineTextAlignment(.center)
Text("You still have: ")
Text("\(remainderCompValue()) years on earth")
Spacer()
}
}
}
}
func remainderCompValue() -> DateComponents {
var mutatableSelf = self
return mutatableSelf.remainderComp.DateComponentsFormatter().allowedUnits = [.year]
If you want to use a DateComponentsFormatter, there are a few issues:
The following line does not makes sense:
mutatableSelf.remainderComp.DateComponentsFormatter().allowedUnits = [.year]
You should create a DateComponentsFormatter property and set its allowedUnits (and presumably its unitStyle). E.g.:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.year]
formatter.unitsStyle = .full
return formatter
}()
The issue in the screen snapshot is that you are not using the DateComponentsFormatter that you should have created in the prior step. So, first, I would retire remainder, remainderComp, and now and just have a function to prepare the “number of years left” string:
func remainingYearsString(for date: Date) -> String? {
guard let expectancyEndOfLife = Calendar.current.date(byAdding: .year, value: 73, to: date) else {
return nil
}
return formatter.string(from: .now, to: expectancyEndOfLife)
}
Note that that the DateComponents object is now redundant and no longer needed.
Anyway, I would then have the View use that function:
var body: some View {
VStack {
NavigationView {
VStack {
Text("Your birthday...?")
.font(.title)
DatePicker("", selection: $birthDay, in:...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
Text("""
The world's average life expectancy in 2022 is
73 years
"""
).multilineTextAlignment(.center)
Text("You still have: ")
Text("\(remainingYearsString(for: birthDay) ?? "unknown time") on earth")
Spacer()
}
}
}
}
Note that the date components formatter will include the “years” string, so you will notice that I have removed the redundant “years” in the Text(...) string.

SwiftUI - How can I filter users of a list with a datepicker

I'm creating an app with SwiftUI. I have an array of data and a list showing them. The array contains various elements which contain a username and a date for each element. Something like that:
struct History: Identifiable {
let id = UUID()
var userName: String
var date:String
}
var itemsHistory = [
History(userName: "Liam", date: "2020-06-17" ),
History(userName: "Noah", date: "2019-04-14" ),
History(userName: "James", date: "2022-04-13" ),
]
List {
ForEach(itemsHistory) { item in
VStack(alignment: .leading) {
Text(item.userName)
.font(.headline)
Text(item.date)
.foregroundColor(.secondary)
}
}
}
}
My question is:
How can I use one or two datapickers to only show users in a certain date range? For example: from the datepicker I select the range between 2020-01-01 and 2020-02-12 and the list must show only the users who have the date that is in that range.
Thank you very much. It is the first time that I am writing here.
First declare date as Date, it's easier to compare
struct History: Identifiable {
let id = UUID()
var userName: String
var date: Date
}
Get start and end date from the pickers
#State private var startDate = Date()
#State private var endDate = Date()
DatePicker(
"Start Date",
selection: $startDate,
displayedComponents: [.date]
)
DatePicker(
"End Date",
selection: $endDate,
displayedComponents: [.date]
)
Then filter the items
let filteredItems = endDate < startDate ? [] : itemsHistory.filter { item in
(startDate...endDate).contains(item.date)
}

Is it possible to dismiss the DatePicker's calendar when you tap a date?

I have a basic SwiftUI date picker that shows a calendar widget when tapped:
DatePicker(
"Date",
selection: $date,
in: ...Date(),
displayedComponents: [.date]
)
When you select a date (8th October in the example above), the calendar remains on screen and in order to collapse it, you need to tap outside of it.
Is it possible to automatically collapse it when a date is selected?
I ended up with a rather hacky solution that seems to do the job:
Add a #State variable that holds the calendar ID:
#State private var calendarId: Int = 0
Chain the DatePicker call with .id, .onChange and .onTapGesture actions:
DatePicker(
"Date", selection: $date, in: ...Date(), displayedComponents: [.date]
)
.id(calendarId)
.onChange(of: date, perform: { _ in
calendarId += 1
})
.onTapGesture {
calendarId += 1
}
#chris.kobrzak provided a good direction, and I ended up solving this with:
struct ContentView: View {
#State var calendarId: UUID = UUID()
#State var someday: Date = Date()
var body: some View {
VStack {
DatePicker("Day", selection: $someday, displayedComponents: [.date])
.labelsHidden()
.id(calendarId)
.onChange(of: whatday) { _ in
calendarId = UUID()
}
AnotherView(someday)
}
}
}
This is just an updated answer following #Chris Kobrzak as above.
I am using XCode 14.1 and iOS 15+ and 16+ (iPad and iPhone) and it seems to work without error today in Nov 2022.
I have seen some folk using the same .id() method complain that it doesn’t work.
I haven’t tested this but note that I am using the CompactDatePickerStyle(), maybe it doesn’t work the same on other styles.
The reason this hack works is the .id() is for the ‘view’ (DatePicker being a view). When you change the id of a view you basically reset it (in this case closing the DatePicker).
There is a good explanation about .id() here: https://swiftui-lab.com/swiftui-id/
Why this isn’t built into the control seems rather a joke but hey…
Note I have ripped the following out of a real App. I've edited it in a dumb text editor to post on here so there may be some silly syntax errors and odd remnants of the original code.
import SwiftUI
struct FooView: View {
#Published var dateOfBirth: Date = Date()
#State private var datePickerId: Int = 0
private var dateOfBirthRange: ClosedRange<Date> {
let dateFrom = Calendar.current.date(byAdding: .year, value: -160, to: Date())!
let dateTo: Date = Date()
return dateFrom...dateTo
}
var body: some View {
Form {
ZStack(alignment: .leading) {
Text("Date of Birth")
.offset(y: -36)
.foregroundColor(Color.accentColor)
.scaleEffect(0.9, anchor: .leading)
DatePicker(
"",
selection: $dateOfBirth,
in: dateOfBirthRange,
displayedComponents: .date
)
.datePickerStyle(CompactDatePickerStyle())
.labelsHidden()
.id(datePickerId)
.onChange(of: dateOfBirth) { _ in
datePickerId += 1
}
}
.padding(.top, 24)
.animation(.default, value: "")
}
}
}
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 but that's ok.
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
}
}
}

How to modify datePicker style swiftui 2?

I have a calendar made with datePicker in swiftui 2, I am looking to customize this calendar I have already seen .datePickerStyle() and all these styles but it doesn't correspond to my expectations, I'm trying to reproduce that (below)
My current code
import SwiftUI
struct ExercisePageView: View {
#State var date = Date()
var dateFormat = DateFormat()
var dateStyle = GraphicalDatePickerStyle()
var closedRange: ClosedRange<Date> {
let currentDate = Calendar.current.date(byAdding: .day, value: 0, to: Date())!
let fiveDaysLater = Calendar.current.date(byAdding: .day, value: 5, to: Date())!
return currentDate...fiveDaysLater
}
var body: some View {
HStack {
VStack{
Text("\(dateFormat.dateToString("dd / MMMM", date))")
DatePicker("date picker test", selection: $date, in: closedRange , displayedComponents: .date)
.datePickerStyle(GraphicalDatePickerStyle())
.clipped()
.labelsHidden()
.accentColor(Color(UIColor.systemRed))
}
}
}
}
the result of my code
Ok so thank you for those who read this post we can not modify as it was said by Sweeper, you have to create your own component

Set default date on DatePicker in SwiftUI?

I have this DatePicker inside a VStack, working fine:
VStack {
DatePicker(selection: $birthDate, displayedComponents: .date) {
Text("")
}.padding(10)
.labelsHidden()
.datePickerStyle(WheelDatePickerStyle())
}
I'd like to be able to have the default date set to 40 years in the past so the user doesn't have to spin the year so far (most people aren't newborns). This can be done with DatePicker as shown in this SO answer. I don't know how to implement that in SwiftUI. Thanks for any help.
Why not just like this?
struct ContentView: View {
#State private var birthDate: Date = Calendar.current.date(byAdding: DateComponents(year: -40), to: Date()) ?? Date()
var body: some View {
VStack {
DatePicker(selection: $birthDate, displayedComponents: .date) {
Text("")
}
.padding(10)
.labelsHidden()
.datePickerStyle(WheelDatePickerStyle())
}
}
}
You should initialise birthDate in the following way (tested & works with Xcode 11.2 / iOS 13.2)
#State private var birthDate: Date
init() {
_birthDate = State<Date>(initialValue: Calendar.current.date(byAdding:
DateComponents(year: -40), to: Date()) ?? Date())
}