DatePicker ignore values which are not in closedRange - datepicker

How can I ignore/hide dates in a datePicker which are not in my closedRange?
The reason why I try to do this is because I think its more user friendly.
var dateClosedRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .day,
value: 0,
to: Date())!
let max = Calendar.current.date(byAdding: .hour,
value: 48,
to: Date())!
return min...max
}
DatePicker(selection: $placemark.time,
in: dateClosedRange,
displayedComponents: [.date, .hourAndMinute],
label: { Text("Reeve starts at: ") })
How to hide/ignore the marked area?

Related

Combine Date in swiftui to obtain only one Date

Hello I have two date picker one only for date and another for hour and minute
if(showStartDatePicker) {
DatePicker("Seleziona data", selection: $selectedStartDate,/* in: startingDate...endingDate,*/ displayedComponents: [.date])
.datePickerStyle(.graphical)
} else if(showStartTimePicker) {
DatePicker("Select a date", selection: $selectedStartTime, displayedComponents: [.hourAndMinute])
.datePickerStyle(.wheel)
}
I need to obtain a single date that is the merge of the two date picker
So how can I do?
Just share the variable instead of having 2
struct DoubleDatePickerView: View {
#State var showStartDatePicker: Bool = false
#State var selectedStartDate: Date = Date()
var body: some View {
VStack{
Text(selectedStartDate.description)
if(showStartDatePicker) {
DatePicker("Seleziona data", selection: $selectedStartDate,/* in: startingDate...endingDate,*/ displayedComponents: [.date])
.datePickerStyle(.graphical)
Button("show time picker", action: {
showStartDatePicker.toggle()
})
} else {
DatePicker("Select a date", selection: $selectedStartDate, displayedComponents: [.hourAndMinute])
.datePickerStyle(.wheel)
Button("show date picker", action: {
showStartDatePicker.toggle()
})
}
}
}
}
The other option is to combine the desired components from each variable
var combined: Date{
let timeComponents: DateComponents = Calendar.current.dateComponents([.hour,.minute,.second,.timeZone], from: selectedStartTime)
let dateComponents: DateComponents = Calendar.current.dateComponents([.year,.month,.day], from: selectedStartDate)
let combined: DateComponents = .init(calendar: .current, timeZone: timeComponents.timeZone, year: dateComponents.year, month: dateComponents.month, day: dateComponents.day, hour: timeComponents.hour, minute: timeComponents.minute, second: timeComponents.second)
return Calendar.current.date(from: combined) ?? Date()
}

How to make a circular timer with Dates in SwiftUI?

I need some help. I figured out how to make a Circular Timer, but instead of that, I want to make it with Dates, and my example is using Int values. For example, I'm trying to countdown from a Future Date ( Example : February 4 11:00 PM ) to Date ( February 4 9:00 PM ).
This is the code that I wrote :
import SwiftUI
let timer = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
struct CircularTimer: View {
#State var counter: Int = 0
var countTo: Int = 120
var nowDate = Date()
var futureDate = Date.distantFuture
var body: some View {
VStack{
ZStack{
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().stroke(Color.green, lineWidth: 25)
)
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().trim(from:0, to: progress())
.stroke(
style: StrokeStyle(
lineWidth: 25,
lineCap: .round,
lineJoin:.round
)
)
.foregroundColor(
(completed() ? Color.orange : Color.red)
).animation(
.easeInOut(duration: 0.2)
)
)
}
}.onReceive(timer) { time in
if (self.counter < self.countTo) {
self.counter += 1
}
}
}
func completed() -> Bool {
return progress() == 1
}
func progress() -> CGFloat {
return (CGFloat(counter) / CGFloat(countTo))
}
}
I am also a novice in SwiftUI programming, but I tried to rewrite your solution to get a desired output. All you need is to work with
DateComponents and then with TimeInterval as those two allow you to represent Date() variables in seconds, so you can easily use it for your timer. Here it is:
import SwiftUI
let timer = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
struct CircularTimer: View {
// MARK: Changed type to TIMEINTERVAL
#State var counter: TimeInterval = 0
// MARK: New DATE variables using DATECOMPONENTS
#State var startDate: Date = Calendar.current.date(from: DateComponents(year: 2022, month: 2, day: 4, hour: 11, minute: 00, second: 10)) ?? Date.now
var endDate: Date = Calendar.current.date(from: DateComponents(year: 2022, month: 2, day: 4, hour: 11, minute: 00, second: 00)) ?? Date.now
// MARK: Calculated TIMEINTERVAL instead of original COUNTTO variable
var timeInterval: TimeInterval {
let end = endDate
let start = startDate
return start.timeIntervalSince(end)
}
var body: some View {
NavigationView {
VStack{
ZStack{
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().stroke(Color.green, lineWidth: 25)
)
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().trim(from:0, to: progress())
.stroke(
style: StrokeStyle(
lineWidth: 25,
lineCap: .round,
lineJoin:.round
)
)
.foregroundColor(
(completed() ? Color.orange : Color.red)
).animation(
.easeInOut(duration: 0.2)
)
)
// MARK: Text view for DATE just to show you that timer is working
Text("\(startDate.formatted(date: .long, time: .standard))")
.font(.system(size: 10))
.foregroundColor((timeInterval == 0) ? .orange : .black)
}
}
// MARK: Changed logic for timer calculations
.onReceive(timer) { time in
if (timeInterval != 0) {
counter += 1
startDate -= 1
}
}
// MARK: A few changes to the layout
.navigationTitle("Timer")
.toolbar {
Button("Start again", action: startAgain)
}
}
}
// MARK: Function for a START AGAIN button
func startAgain() {
counter = 0
startDate = Calendar.current.date(from: DateComponents(year: 2022, month: 2, day: 4, hour: 11, minute: 00, second: 10)) ?? Date.now
return
}
func completed() -> Bool {
return progress() == 1
}
func progress() -> CGFloat {
return (CGFloat(counter) / CGFloat(timeInterval + counter))
}
}

SwiftUI - Date handling

maybe you can help me with following question:
I am programming a UI with two date pickers (startDatePicker and endDatePicker).
The startDatePicker should be updated itself to the date of endDatePicker in UI when the date of startEndPicker is smaller than the date of endDatePicker.
Do you habe any idea how I can realise that?
content.swift
import SwiftUI
struct ContentView: View {
#State var startDate = Date()
#State var endDate = Date()
#ObservedObject var dateModel = Period.shared
var body: some View {
VStack{
startDatePicker
endDatePicker
}
.onAppear {
// print("VStack")
// dateModel.startDate = startDate
// dateModel.endDate = endDate
}
}
var startDatePicker: some View{
DatePicker("Start", selection: $startDate, displayedComponents: [.date])
.datePickerStyle(CompactDatePickerStyle())
.frame(width: 250, height: 50, alignment: .center)
.onAppear(perform: {
print("StartDate.onAppear")
dateModel.startDate = dateModel.toLocalTime(date: startDate, type: true)
print(dateModel.startDate)
})
.onChange(of: startDate, perform: { startDate in
print("StartDate.onChange")
dateModel.startDate = dateModel.toLocalTime(date: startDate, type: true)
print(dateModel.startDate)
})
}
var endDatePicker: some View{
DatePicker("End", selection: $endDate, displayedComponents: [.date])
.datePickerStyle(CompactDatePickerStyle())
.frame(width: 250, height: 50, alignment: .center)
.onAppear(perform: {
print("EndDate.onAppear")
dateModel.endDate = dateModel.toLocalTime(date: endDate, type: false)
print(dateModel.endDate)
})
.onChange(of: endDate, perform: { endDate in
print("EndDate.onChange")
dateModel.endDate = dateModel.toLocalTime(date: endDate, type: false)
if dateModel.endDate < dateModel.startDate{
print("Error")
dateModel.startDate = dateModel.endDate
}
print(dateModel.endDate)
})
}
}
Datahandler.swift
class Period : ObservableObject{
static let shared = Period()
#Published var startDate: Date = Date()
#Published var endDate: Date = Date()
func toLocalTime(date : Date, type: Bool) -> Date {
var startDate : Date?
var endDate : Date?
var dateLocalTimezone : Date?
//Auswahl der aktuellen Kalender
let calendar = Calendar.current
//Auswahl der Zeitzone
let timezone = TimeZone.current
//Bestimmen Anzahl Sekunden zwischen Zeitzone und GMT
let seconds = TimeInterval(timezone.secondsFromGMT(for: date))
//Anpassen des eingelesenen Werts
if type == true {
startDate = calendar.date(bySettingHour: 00, minute: 00, second: 00, of: date)
dateLocalTimezone = Date(timeInterval: seconds, since: startDate!)
}else{
endDate = calendar.date(bySettingHour: 23, minute: 59, second: 00, of: date)
dateLocalTimezone = Date(timeInterval: seconds, since: endDate!)
}
return dateLocalTimezone!
}
}
Is there a better way for the code? The idea is to separate the part for dates from the UI.
You're almost there. You can move logic from onAppeat to init(), and from .onChange to didSet, like this:
struct ContentView: View {
#ObservedObject var dateModel = Period.shared
var body: some View {
VStack{
startDatePicker
endDatePicker
}
}
var startDatePicker: some View{
DatePicker("Start", selection: $dateModel.startDate, displayedComponents: [.date])
.datePickerStyle(CompactDatePickerStyle())
.frame(width: 250, height: 50, alignment: .center)
}
var endDatePicker: some View{
DatePicker("End", selection: $dateModel.endDate, displayedComponents: [.date])
.datePickerStyle(CompactDatePickerStyle())
.frame(width: 250, height: 50, alignment: .center)
}
}
class Period : ObservableObject{
static let shared = Period()
#Published var startDate: Date {
didSet {
let localTime = Self.toLocalTime(date: startDate, type: true)
if startDate != localTime {
startDate = localTime
}
}
}
#Published var endDate: Date {
didSet {
let localTime = Self.toLocalTime(date: endDate, type: false)
if endDate != localTime {
endDate = localTime
}
if endDate < startDate{
print("Error")
startDate = endDate
}
print(endDate)
}
}
init() {
startDate = Self.toLocalTime(date: Date(), type: true)
endDate = Self.toLocalTime(date: Date(), type: false)
}
static private func toLocalTime(date : Date, type: Bool) -> Date {
var startDate : Date?
var endDate : Date?
var dateLocalTimezone : Date?
//Auswahl der aktuellen Kalender
let calendar = Calendar.current
//Auswahl der Zeitzone
let timezone = TimeZone.current
//Bestimmen Anzahl Sekunden zwischen Zeitzone und GMT
let seconds = TimeInterval(timezone.secondsFromGMT(for: date))
//Anpassen des eingelesenen Werts
if type == true {
startDate = calendar.date(bySettingHour: 00, minute: 00, second: 00, of: date)
dateLocalTimezone = Date(timeInterval: seconds, since: startDate!)
}else{
endDate = calendar.date(bySettingHour: 23, minute: 59, second: 00, of: date)
dateLocalTimezone = Date(timeInterval: seconds, since: endDate!)
}
return dateLocalTimezone!
}
}
Your toLocalTime returns wrong value for end date, like I pass 2021-08-18 23:59:00 +0000 and the result is 2021-08-19 23:59:00 +0000 which is the next day. This proceeds to recursion of didSet, but I'll leave fixing this logic to your
It's not a good fit for SO to ask for "a better way", since every solution has its pros and cons and thus there are a lot of opinions. But the latter I guess, is pretty much consolidated (which is in itself highly opinionated ;) )
I would like to add some refactoring suggestions:
You can make the DatePickerView a reusable component:
struct DatePickerView: View {
let title: String
let date: Date
let setDateAction: (Date) -> Void
var body: some View {
let binding: Binding<Date> = .init {
return self.date
} set: { newValue in
self.setDateAction(newValue)
}
DatePicker(title, selection: binding, displayedComponents: [.date])
.datePickerStyle(CompactDatePickerStyle())
}
}
Then, it can be used in the ContentView as follows:
struct ContentView: View {
let state: TwoDatePickers.ViewModel.ViewState
let setStartDate: (Date) -> ()
let setEndDate: (Date) -> ()
var body: some View {
VStack{
Text("Start: \(state.startDate)")
.padding()
Text("End: \(state.endDate)")
.padding()
DatePickerView(
title: "Start",
date: state.startDate,
setDateAction: self.setStartDate)
.padding()
DatePickerView(
title: "End",
date: state.endDate,
setDateAction: self.setEndDate)
.padding()
}
}
}
You might notice that ContentView has no coupling to any certain kind of view model or model, and no logic what so ever. That's by intention to make it reusable as well and let the logic be done elsewhere.
Now, you can implement your view model plus logic as a separated component as well. I am cheating here a bit, since it internally uses a reusable "store" component ;)
extension TwoDatePickers {
static let store = Oak.Store(state: .init(),
update: update,
scheduler: DispatchQueue.main)
final class ViewModel: ObservableObject {
typealias ViewState = TwoDatePickers.State
init() {
cancellableState = store.sink { state in
self.viewState = self.view(state)
}
}
#Published
private(set) var viewState: ViewState = .init()
private var cancellableState: AnyCancellable!
private func view(_ state: State) -> ViewState { state }
func setStartDate(_ date: Date) {
store.input.send(.setStartDate(date))
}
func setEndDate(_ date: Date) {
store.input.send(.setEndDate(date))
}
}
}
Note, that the ViewModel is just a thin wrapper which connects the store and provides a view function which returns a ViewState from the store's state. The ViewState is the thing that completely and unambiguously defines what the view should render, while the store's State is there to perform the logic and may contain additional data. That is, ViewState is a function of State.
The "store" is implemented much like in Redux or Elm, which can be done in a few lines of code.
As mentioned, this is also a reusable component. It is event driven, unidirectional and uses internally a Finite State Machine to change state and generate outputs.
So, you have to implement an update function, which is basically the heart of your logic:
extension TwoDatePickers {
struct State {
var startDate: Date = Date()
var endDate: Date = Date()
}
enum Event {
case setStartDate(Date)
case setEndDate(Date)
}
typealias Command = Void
static func update(state: State, setter: (State) -> Void, event: Event) -> Void {
switch (state, event) {
case (_, .setStartDate(let date)):
let adjustedEndDate = Date(
timeIntervalSinceReferenceDate: max(
date.timeIntervalSinceReferenceDate,
state.endDate.timeIntervalSinceReferenceDate))
setter(State(startDate: date, endDate: adjustedEndDate))
case (_, .setEndDate(let date)):
let adjustedStartDate = Date(
timeIntervalSinceReferenceDate: min(
date.timeIntervalSinceReferenceDate,
state.startDate.timeIntervalSinceReferenceDate))
setter(State(startDate: adjustedStartDate, endDate: date))
default:
print("not handled: \(state), \(event)")
break
}
}
}
To wire all the things together, we may use a "root view". Here you can see how eventually the view model gets connected to the views:
struct TwoDatePickersSceneView: View {
#StateObject var viewModel = TwoDatePickers.ViewModel()
var body: some View {
TwoDatePickers.ContentView(
state: viewModel.viewState,
setStartDate: viewModel.setStartDate,
setEndDate: viewModel.setEndDate
)
}
}
What's left is the reusable implementation of the store. I post it here as a bonus:
https://gist.github.com/couchdeveloper/4100f1ec8470980c5c49adc119240de1
Final note:
This is an example how we can decompose a typical SwiftUI feature and separate it into several reusable components. You don't have to do this extra effort when the feature is small and tidy. However, this solution, especially using a Finite State Machine to solve UI problems scales much better for more complex problems.

Change selected cell Baground Color in SwiftUI

I write the following code and brought this kind of output.
By using the .onTapGesture method I find the selected day from HStack
But I want to change the background color based on cell selection in Hstack.
Anyone give some idea or suggestions for changing the selected cell Background color on HStack.
Or share any references with me.
struct CalendarDay: Identifiable {
let id = UUID()
var number: String
var weekday: String
var isToday: Bool
}
struct ContentView: View {
#State var days = [CalendarDay]()
var body: some View {
ZStack{
VStack {
//MARK: CALENDAR
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 20) {
ForEach(days) { day in
CalendarView(
number: day.number,
days: day.weekday,
color: day.isToday ? #colorLiteral(red: 0.9060331583, green: 0.2547450066, blue: 0.3359550834, alpha: 1) : #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
textcolor: day.isToday ? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) : #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
)
.onTapGesture{
print(day)
}
}
}
.padding(.leading,10)
.padding(.bottom, 10)
.shadow(radius: 3, x: 3, y: 3)
}
}
}
.onAppear {
getCurrentWeekdays()
}
}
func getCurrentWeekdays() {
/// from https://stackoverflow.com/a/62355272/14351818
let dateComponents = Calendar(identifier: .gregorian).dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date())
let startOfWeek = Calendar(identifier: .gregorian).date(from: dateComponents)!
let startOfWeekNoon = Calendar(identifier: .gregorian).date(bySettingHour: 12, minute: 0, second: 0, of: startOfWeek)!
days = (0...6).map {
let calendar = Calendar(identifier: .gregorian)
let date = calendar.date(byAdding: .day, value: $0, to: startOfWeekNoon)!
let numberDateFormatter = DateFormatter()
numberDateFormatter.dateFormat = "d"
let number = numberDateFormatter.string(from: date)
let weekdayDateFormatter = DateFormatter()
weekdayDateFormatter.dateFormat = "E"
let weekday = weekdayDateFormatter.string(from: date)
let calendarDay = CalendarDay(
number: number,
weekday: weekday,
isToday: calendar.component(.day, from: Date()) == calendar.component(.day, from: date)
)
return calendarDay
}
}
}
struct CalendarView: View {
var number : String
var days : String
var color : UIColor
var textcolor : UIColor
var body: some View {
VStack{
Text(self.number)
.font(.system(size: 20, weight: .bold, design: .rounded))
.foregroundColor(Color(self.textcolor))
Text(self.days)
.font(.headline)
.foregroundColor(Color(self.textcolor))
}.padding([.top,.bottom], 10)
.padding([.leading,.trailing],10)
.background(Color(self.color))
.cornerRadius(30)
}
}
try something like this:
import SwiftUI
struct CalendarDay: Identifiable {
let id = UUID()
var number: String
var weekday: String
var isToday: Bool
}
struct ContentView: View {
#State var days = [CalendarDay]()
var body: some View {
ZStack{
VStack {
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 20) {
// <---
ForEach(days.indices, id: \.self) { i in
CalendarView(
number: days[i].number,
days: days[i].weekday,
color: days[i].isToday ? #colorLiteral(red: 0.9060331583, green: 0.2547450066, blue: 0.3359550834, alpha: 1) : #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
textcolor: days[i].isToday ? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) : #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
)
.onTapGesture{
print(days[i])
// this is just for replacing the current selection
for j in days.indices { days[j].isToday = false }
days[i].isToday = true
}
}
// <---
}
.padding(.leading,10)
.padding(.bottom, 10)
.shadow(radius: 3, x: 3, y: 3)
}
}
}
.onAppear {
getCurrentWeekdays()
}
}
func getCurrentWeekdays() {
/// from https://stackoverflow.com/a/62355272/14351818
let dateComponents = Calendar(identifier: .gregorian).dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date())
let startOfWeek = Calendar(identifier: .gregorian).date(from: dateComponents)!
let startOfWeekNoon = Calendar(identifier: .gregorian).date(bySettingHour: 12, minute: 0, second: 0, of: startOfWeek)!
days = (0...6).map {
let calendar = Calendar(identifier: .gregorian)
let date = calendar.date(byAdding: .day, value: $0, to: startOfWeekNoon)!
let numberDateFormatter = DateFormatter()
numberDateFormatter.dateFormat = "d"
let number = numberDateFormatter.string(from: date)
let weekdayDateFormatter = DateFormatter()
weekdayDateFormatter.dateFormat = "E"
let weekday = weekdayDateFormatter.string(from: date)
let calendarDay = CalendarDay(
number: number,
weekday: weekday,
isToday: calendar.component(.day, from: Date()) == calendar.component(.day, from: date)
)
return calendarDay
}
}
}
struct CalendarView: View {
var number : String
var days : String
var color : UIColor
var textcolor : UIColor
var body: some View {
VStack{
Text(self.number)
.font(.system(size: 20, weight: .bold, design: .rounded))
.foregroundColor(Color(self.textcolor))
Text(self.days)
.font(.headline)
.foregroundColor(Color(self.textcolor))
}.padding([.top,.bottom], 10)
.padding([.leading,.trailing],10)
.background(Color(self.color))
.cornerRadius(30)
}
}

swiftUI - Date Comparison

I need to compare 2 dates and if it is the same date, do something... (dd/MM to dd/MM only). I generate today's date, then set my own date and when compared, the same dates do not match. (When displaying each date (as Text), the result is the same. E.g. 10-04 and 10-04.)
struct eddEntryView : View { // ContentView
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM"
return formatter
}()
#State private var today = Date()
let specificDate: Date = {
var components = DateComponents()
components.day = 14
components.month = 04
let newDate = Calendar.current.date(from: components) ?? Date()
return newDate
}()
var entry: Provider.Entry
var body: some View {
HStack{
VStack(alignment: .leading){
if Calendar.current.isDateInToday(specificDate) // today is 14-04, specific date is set for 14-04, but it doesn't work
{
Text("THE SAME: \(specificDate, formatter: dateFormatter)")
.font(.title)
.foregroundColor(Color(.label))
.bold()
Spacer()
} else {
Text("Not the same day... \(specificDate, formatter: dateFormatter)")
.font(.title)
.foregroundColor(Color(.label))
.bold()
Spacer()
}
}
}
There is a function isDateInToday(Date) on Calendar. You could use it like this:
if Calendar.current.isDateInToday(dueDate) {
//...
}
There is also a function that compares whether to date fall on the same day:
Calendar.current.isDate(today, inSameDayAs: dueDate)
Solution:
let today = Date() // Actual date
func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM"
return formatter.string(from: date)
}
and then:
if ("\(formatDate(date: today))") .elementsEqual("14-04") // specific date {
Text("the same: \(formatDate(date: today))")
// ... do something for the same day
Spacer()
} else {
// ...
}