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
Related
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.
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
}
}
}
In the below code the Text contents are translated, but the DatePicker title is not. Both keys exist inside the localization file.
Any idea why this happens?
import SwiftUI
struct TestView: View {
#ObservedObject var viewModel: ViewModel
private let dateRange: ClosedRange<Date> = {
let calendar = Calendar.current
let now = Date()
let past = calendar.date(byAdding: DateComponents(day: 1), to: calendar.date(byAdding: DateComponents(year: -1), to: now)!)!
return past
...
now
}()
var body: some View {
Form {
Text("estimated_monthly_price")
DatePicker("purchase_date", selection: $viewModel.purchaseDate, in: dateRange, displayedComponents: [.date])
}
}
}
this is in Xcode 12.4
If you check DataPicker initialiser its titleKey parameter accepts type as LocalizedStringKey. So try passing it as LocalizedStringKey("purchase_date”) in DataPicker.
public init(_ titleKey: LocalizedStringKey, selection: Binding<Date>, in range: PartialRangeThrough<Date>, displayedComponents: DatePicker<Label>.Components = [.hourAndMinute, .date])
I want to fetch and set date from DatePicker, but my date is not updating. SwiftUI is new to me and I am confused with what type of property wrapper to use. Please help in this and advice when and where to use #State, #Binding, #Published I read some articles but still concept is not clear to me.
Here I used MVVM and SwiftUI and my code as follows.
class MyViewModel:ObservableObject {
#Published var selectedDate : Date = Date()
#Published var selectedDateStr : String = Date().convertDateToString(date: Date())
}
struct DatePickerView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#ObservedObject var viewModel : MyViewModel
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
#State private var selectedDate = Date()
var body: some View {
VStack {
//Title
HStack{
Text("SELECT A DATE")
.foregroundColor(.white)
.font(.system(size: 20))
}
.frame(width:UIScreen.main.bounds.width,height: 60)
.background(Color.red)
//Date Picker
DatePicker(selection: $selectedDate, in: Date()-15...Date(), displayedComponents: .date) {
Text("")
}.padding(30)
Text("Date is \(selectedDate, formatter: dateFormatter)")
Spacer()
//Bottom buttons
Text("DONE")
.fontWeight(.semibold)
.frame(width:UIScreen.main.bounds.width/2,height: 60)
.onTapGesture {
self.viewModel.selectedDate = self.selectedDate
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
//calling:
DatePickerView(viewModel: self.viewModel)
Reply against your second question about wrapper properties used in SwiftUI i.e #State, #Binding, #Published.
The most common #Things used in SwiftUI are:
• #State - Binding<Value>
• #Binding - Binding<Value>
• #ObservedObject - Binding<Value> (*)
• #EnvironmentObject - Binding<Value> (*)
• #Published - Publisher<Value, Never>
(*) technically, we get an intermediary value of type Wrapper, which turns a Binding once we specify the keyPath to the actual value inside the object.
So, as you can see, the majority of the property wrappers in SwiftUI, namely responsible for the view’s state, are being “projected” as Binding, which is used for passing the state between the views.
The only wrapper that diverges from the common course is #Published, but:
1. It’s declared in Combine framework, not in SwiftUI
2. It serves a different purpose: making the value observable
3. It is never used for a view’s variable declaration, only inside ObservableObject
Consider this pretty common scenario in SwiftUI, where we declare an ObservableObject and use it with #ObservedObject attribute in a view:
class ViewModel: ObservableObject {
#Published var value: Int = 0
}
struct MyView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View { ... }
}
MyView can refer to $viewModel.value and viewModel.$value - both expressions are correct. Quite confusing, isn’t it?
These two expressions ultimately represent values of different types: Binding and Publisher, respectively.
Both have a practical use:
var body: some View {
OtherView(binding: $viewModel.value) // Binding
.onReceive(viewModel.$value) { value // Publisher
// do something that does not
// require the view update
}
}
Hope it may help you.
You can calculate the current date - 15 days using this:
let previousDate = Calendar.current.date(byAdding: .day, value: -15, to: Date())!
Then use the previousDate in DatePicker`s range:
DatePicker(selection: $selectedDate, in: previousDate...Date(), displayedComponents: .date) { ...
Summing up, your code can look like this:
struct DatePickerView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var viewModel: MyViewModel
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
#State private var selectedDate = Date()
let previousDate = Calendar.current.date(byAdding: .day, value: -15, to: Date())!
var body: some View {
VStack {
//Title
HStack{
Text("SELECT A DATE")
.foregroundColor(.white)
.font(.system(size: 20))
}
.frame(width:UIScreen.main.bounds.width,height: 60)
.background(Color.red)
//Date Picker
DatePicker(selection: $selectedDate, in: previousDate...Date(), displayedComponents: .date) {
Text("")
}.padding(30)
Text("Date is \(selectedDate, formatter: dateFormatter)")
Spacer()
//Bottom buttons
Button(action: {
self.viewModel.selectedDate = self.selectedDate
self.presentationMode.wrappedValue.dismiss()
}) {
Text("DONE")
.fontWeight(.semibold)
}
}
}
}
Tested in Xcode 11.5, Swift 5.2.4.
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())
}