Hi Stack Overflow community, I have a problem. I want to create a date picker based on a ScrollView where the starting date is today, and the range is 15 days forward and backward. For some reason, SwiftUI doesn't navigate to values that are off-screen, and also, during initialization, it doesn't scroll to today's date, which is currently March 25th. Is this a known issue? I tried various methods like using DispatchQueue delays or Task.sleep, but nothing works. I would appreciate any help.
PS: When I changed the values to Int, everything works, so something is wrong with handling dates.
Code example:
import SwiftUI
struct ContentView: View {
private let dates: [Date] = {
let today = Date()
let calendar = Calendar.current
return (0...30)
.map {
calendar.date(byAdding: .day,
value: $0 - 15,
to: today)!
}
}()
@State var selectedDate: Date = Date()
var body: some View {
GeometryReader { geometry in
ScrollViewReader { scrollValue in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(dates, id: \.self) { date in
DateCell(date: date, isSelected: Calendar.current.isDate(date, inSameDayAs: selectedDate))
.id(date)
.onTapGesture {
withAnimation {
selectedDate = date
}
}
}
}
.padding(.horizontal, geometry.size.width / 2)
}
.onChange(of: selectedDate, initial: true) { _, newValue in
withAnimation {
scrollValue.scrollTo(newValue, anchor: .center)
}
}
}
}
}
}
struct DateCell: View {
let date: Date
var isSelected: Bool
var body: some View {
VStack {
Text(date, format: .dateTime.weekday(.short))
Text(date, format: .dateTime.day())
.bold()
}
.frame(width: 50, height: 50)
.background(isSelected ? Color.red : Color.gray.opacity(0.3))
.clipShape(Circle())
}
}
#Preview {
ContentView()
}
This problem is probably happening because Date()
gives you a date that is accurate to some fraction of a second (nanoseconds?). So the date you use to initialize the collection is going to be different to the date that you use to initialize selectedDate
. In other words, the initial value of selectedDate
will not be present in the array of dates.
To fix, try truncating the dates to start of day. Calendar
has a function that does this:
private let dates: [Date] = {
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date()) // 👈 get today from Calendar
return (0...30)
.map {
calendar.date(byAdding: .day,
value: $0 - 15,
to: today)!
}
}()
@State var selectedDate: Date = Calendar.current.startOfDay(for: Date()) // 👈 here too