Reputation: 807
How would you detect a change of value of a Datepicker while using SwiftUI and Combine? I need to invoke a method whenever the datepicker wheel is moved, to update a Text and a Slider.
I have looked for specific methods to identify the value change (using UIKit it was possible to associate an action to an event), but apparently I haven't found anything useful in the documentation (I've tried the onTapGesture methods, but that's not what I want, since it forces the user to tap the picker to update the other views, whereas I would like to have an automatic update whenever the user moves the wheel).
import SwiftUI
struct ContentView: View {
private var calendar = Calendar.current
@State private var date = Date()
@State private var weekOfYear = Double(Calendar.current.component(.weekOfYear, from: Date()) )
@State private var lastWeekOfThisYear = 53.0
@State private var weekDay: String = { () -> String in
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE"
let weekDay = dateFormatter.string(from: Date())
return weekDay
}()
var body: some View {
VStack {
// Date Picker
DatePicker(selection: $date, displayedComponents: .date, label:{ Text("Please enter a date") }
)
.labelsHidden()
.datePickerStyle(WheelDatePickerStyle())
.onTapGesture {
self.updateWeekAndDayFromDate()
}
// Week number and day
Text("Week \(Int(weekOfYear.rounded()))")
Text("\(weekDay)")
// Slider
Slider(value: $weekOfYear, in: 1...lastWeekOfThisYear, onEditingChanged: { _ in
self.updateDateFromWeek()
})
}
}
func updateWeekAndDayFromDate() {
// To do
}
func updateDateFromWeek() {
// To do
}
func setToday() {
// To do
}
func getWeekDay(_ date: Date) -> String {
//To do
}
}
I guess this could be solved using Combine (observableobject, published, sink, etc.), but I'm not experienced yet with Combine, therefore I'd like to ask for some help... any ideas? :)
Thanks a lot!
Upvotes: 15
Views: 16980
Reputation: 257869
Please find below one possible approach (with a bit modified your code). Here is an idea to have source state transparent to activate dependent within it. Hope it will be helpful somehow.
Here is demo
Here is code
struct TestDatePicker: View {
private static func weekOfYear(for date: Date) -> Double {
Double(Calendar.current.component(.weekOfYear, from: date))
}
private static func weekDay(for date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE"
let weekDay = dateFormatter.string(from: date)
return weekDay
}
@State private var date: Date
@State private var weekOfYear: Double
@State private var weekDay: String
@State private var lastWeekOfThisYear = 53.0
private var dateProxy:Binding<Date> {
Binding<Date>(get: {self.date }, set: {
self.date = $0
self.updateWeekAndDayFromDate()
})
}
init() {
let now = Date()
self._date = State<Date>(initialValue: now)
self._weekOfYear = State<Double>(initialValue: Self.weekOfYear(for: now))
self._weekDay = State<String>(initialValue: Self.weekDay(for: now))
}
var body: some View {
VStack {
// Date Picker
DatePicker(selection: dateProxy, displayedComponents: .date, label:{ Text("Please enter a date") }
)
.labelsHidden()
.datePickerStyle(WheelDatePickerStyle())
// Week number and day
Text("Week \(Int(weekOfYear.rounded()))")
Text("\(weekDay)")
// Slider
Slider(value: $weekOfYear, in: 1...lastWeekOfThisYear, onEditingChanged: { _ in
self.updateDateFromWeek()
})
}
}
func updateWeekAndDayFromDate() {
self.weekOfYear = Self.weekOfYear(for: self.date)
self.weekDay = Self.weekDay(for: self.date)
}
func updateDateFromWeek() {
// To do
}
func setToday() {
// To do
}
func getWeekDay(_ date: Date) -> String {
""
}
}
struct TestDatePicker_Previews: PreviewProvider {
static var previews: some View {
TestDatePicker()
}
}
Upvotes: 16
Reputation: 329
An easier way is using .onChange on DatePicker:
DatePicker(
"Datum",
selection: $date,
displayedComponents: [.date]
).onChange(of: date, perform: { value in
// Do what you want with "date", like array.timeStamp = date
});
Upvotes: 20
Reputation: 1140
Another approach would be, where observer in viewModel is called whenever date is changed
class TestPikerModel: ObservableObject {
@Published var expireDate: Date = Date()
init() {
setupObserver()
}
private func setupObserver() {
$expireDate
.sink { date in
// do some networking call
}
.store(in: &cancellables)
}
}
struct TestPikerView: View {
@ObservedObject var testPikerModel = TestPikerModel()
@State private var weekOfYear = Double(Calendar.current.component(.weekOfYear, from: Date()) )
var body: some View {
VStack {
Text("\(Int(weekOfYear))")
DatePicker("", selection: $testPikerModel.expireDate, displayedComponents: .date)
.datePickerStyle(CompactDatePickerStyle())
.clipped()
.labelsHidden()
}
}
}
Upvotes: 1
Reputation: 2842
A possible approach using ObservableObject
class TestPikerModel: ObservableObject {
@Published var expireDate: Date = Date()
}
struct TestPikerView: View {
@ObservedObject var testPikerModel = TestPikerModel()
@State private var weekOfYear = Double(Calendar.current.component(.weekOfYear, from: Date()) )
var body: some View {
VStack {
Text("\(Int(weekOfYear))")
DatePicker("", selection: $testPikerModel.expireDate, displayedComponents: .date)
.datePickerStyle(CompactDatePickerStyle())
.clipped()
.labelsHidden()
}
.onReceive(testPikerModel.$expireDate) { date in
weekOfYear = Double(Calendar.current.component(.weekOfYear, from: date))
updateWeekAndDayFromDate()
}
}
func updateWeekAndDayFromDate() {
print("updateWeekAndDayFromDate performed")
}
}
struct TestPikerView_Previews: PreviewProvider {
static var previews: some View {
TestPikerView()
}
}
Upvotes: 1