Saving Date/Time to UserDefaults through didSet on @Published var (from TimePicker component of SwiftUI)
The issue with how you store and annotate your view model. You should never be creating an @ObservedObject
in the View
itself, but rather injecting it. Whenever an @ObservedObject
's @Published
property changes, the View
which stores that object will be reloaded - this means that if you are initialising that @ObservedObject
inside the View
, a new instance of that object will be created.
You need to inject the object into your view to avoid recreating it every time your view is refreshed.
struct SettingsMain: View {
@ObservedObject var userSettings: UserSettings
@State private var showTimepickerEvening = false
...
And from wherever you create SettingsMain
(let's call it MainView
as an illustration), create UserSettings
there and annotate it as @State
(or if you are creating it from something other than a View
- say a ViewModel, then make it @Published
):
struct MainView: View {
@State var userSettings = UserSettings()
var body: some View {
SettingsMain(userSettings: userSettings)
}
}
Run code when date in DatePicker is changed in SwiftUI didSet function?
My question is, when I'm setting the selectedDate value in the DatePicker, why is there nothing printed to the console?
For now didSet
does not work for @State
property wrapper.
Here is an approach to have side effect on DatePicker selection changed
struct ViewName: View {
@State private var selectedDay = Date()
var body: some View {
let dateBinding = Binding(
get: { self.selectedDay },
set: {
print("Old value was \(self.selectedDay) and new date is \($0)")
self.selectedDay = $0
}
)
return VStack {
DatePicker(selection: dateBinding, in: Date()..., displayedComponents: .date) { Text("") }
}
}
}
UserDefaults insanity, take 2 with a DatePicker
here is my 2nd answer, if you want to update the text also...it is not "nice" and for sure not the best way, but it works (i have tested it)
struct ContentView: View {
let defaults = UserDefaults.standard
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
// formatter.dateStyle = .long
formatter.dateFormat = "dd MMM yy"
return formatter
}
@State var uiUpdate : Int = 0
@State var selectedDate : Date
@State var oldDate : Date = Date()
init() {
_selectedDate = State(initialValue: UserDefaults.standard.object(forKey: "MyDate") as! Date) // This should set "selectedDate" to the UserDefaults value
}
var body: some View {
VStack {
Text("The BOOL 1 value is : Bool 1 = \(String(defaults.bool(forKey: "MyBool 1")))")
Divider()
Text("My string says : \(String(defaults.string(forKey: "MyString")!))")
Divider()
Text("The date from UserDefaults is : \(dateFormatter.string(from: defaults.object(forKey: "MyDate") as! Date))")
.background(uiUpdate < 5 ? Color.yellow : Color.orange)
Divider()
DatePicker(selection: $selectedDate, label: { Text("") })
.labelsHidden()
.onReceive([self.selectedDate].publisher.first()) { (value) in
if self.oldDate != value {
self.oldDate = value
self.saveDate()
}
}
Divider()
Text("The chosen date is : \(selectedDate)")
}
}
func loadData() {
selectedDate = defaults.object(forKey: "MyDate") as! Date
print("----> selected date in \"init\" from UserDefaults: \(dateFormatter.string(from: selectedDate) )) ")
}
private func saveDate() { // This func is called whenever the Picker sends out a value
UserDefaults.standard.set(selectedDate, forKey: "MyDate")
print("Saving the date to User Defaults : \(dateFormatter.string(from: selectedDate) ) ")
uiUpdate = uiUpdate + 1
}
}
SwiftUI Initialise variables that will be stored in UserDefaults
true
in your example above is the value to fall back on if there is nothing in user defaults. If there is something stored in user defaults for "pullData"
, it will be used.
If you didn't want to use a fallback value then you'd have to have the property as a Bool?
instead of a Bool
:
@AppStorage("pullData") var pullData: Bool?
But optional booleans are a terrible idea, so don't do that.
Detect when user taps out of or saves on DatePicker
You could try rolling your own DatePickerView
, such as this approach:
struct ContentView: View {
@State private var birthdate = Date()
var body: some View {
DatePickerView(date: $birthdate)
}
}
struct DatePickerView: View {
@Binding var date: Date
@State var hasChanged = false
var body: some View {
ZStack {
Color.white.opacity(0.01).ignoresSafeArea()
.onTapGesture {
if hasChanged {
print("---> do a network call now with: \(date)")
hasChanged = false
}
}
DatePicker("Birth Date", selection: $date, displayedComponents: .hourAndMinute)
.pickerStyle(.wheel)
.onChange(of: date) { val in
hasChanged = true
}
}
}
}
Related Topics
How to Restrict Certain Characters in Uitextfield in Swift
Masking an Image in Swift Using Calayer and Uiimage
Filter Array of [Anyobject] in Swift
How to Set Collection View's Cell Size via the Auto Layout
How Does One Trap Arithmetic Overflow Errors in Swift
Check Whether Swift Object Is an Instance of a Given Metatype
Obervableobject Being Init Multiple Time, and Not Refreshing My View
How to Do If Pattern Matching with Multiple Cases
Intrinsiccontentsize() - Method Does Not Override Any Method from Its Superclass
How to Left Align the Title of a Navigation Bar in Xcode
Use Queue and Semaphore for Concurrency and Property Wrapper
Can't Programmatically Change Color Set in Storyboard as Color from Xcassets Catalog
Downloading and Caching Images from Url Asynchronously
How to Dispatch Functions in Swift the Right Way
Is There a Prefix Header (Or Something with This Functionality) in Swift