问题
Progressing on UserDefaults insanity in SwiftUI, after a previous post on basic UserDefaults which basically exposed the need to use a String() wrapper around UserDefaults values...
I am now stomped by the data flow :
The idea is to present a DatePicker, set to a UserDefaults value registered in AppDelegate on first launch. Subsequently, the user picks another date that is set to the UserDefaults. But every time I launch the app after having "killed" it (i.e swiped up from app switcher), the Picker displays the present date and NOT the one last saved in UserDefaults.
Also, I display some texts above and below the picker to try and make sense of the data flow, but it seems that there is a one step lag in the displaying of the dates, if anyone has the time to give it a try, here is the code :
1- In AppDelegate, I register my initial UserDefaults (like I always did in UIKit) :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch
UserDefaults.standard.register(defaults: [
"MyBool 1": true,
"MyString": "Soo",
"MyDate": Date()
])
return true
}
2- in ContentView, I try to display them :
import SwiftUI
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 selectedDate = Date()
init() {
self.loadData() // 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))")
Divider()
DatePicker(selection: $selectedDate, label: { Text("") })
.labelsHidden()
.onReceive([self.selectedDate].publisher.first()) { (value) in
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) ) ")
}
}
Any help would be appreciated !
回答1:
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
}
}
回答2:
try this:
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 selectedDate : 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))")
Divider()
DatePicker(selection: $selectedDate, label: { Text("") })
.labelsHidden()
.onReceive([self.selectedDate].publisher.first()) { (value) in
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) ) ")
}
}
回答3:
Every time you start the App, it re-register the defaults. You could use this:
if !UserDefaults.standard.bool(forKey: "first time only") {
UserDefaults.standard.register(defaults: [
"first time only": true,
"MyBool 1": true,
"MyString": "Soo",
"MyDate": Date()
])
}
来源:https://stackoverflow.com/questions/61285454/userdefaults-insanity-take-2-with-a-datepicker