1 year ago
#152460
graemek
What is causing a Swift UserDefault to not be set until the 2nd try?
I am trying to let the user control settings for camera hardware so there is a Settings view and a different view where they can choose presets to apply by the click of a button. When navigating back to the Settings view (via a TabView), I expect the values to be shown according to whichever preset button was clicked. However, I am finding that the some of the UserDefault-stored values are not being changed unless the button was clicked twice.
Differences between the correctly updated UserDefaults and the non-working ones:
-The non-working (until 2nd click) values are presented (and able to be changed) in the Settings view in a TextField whereas the correctly working values are presented (and able to be changed) by a Slider.
-The non-working values are Ints by default and updated to be Ints, while the others are Floats.
Things I have tried:
UserDefaults.standard.setValue(values[0], forKey: "exposureISO")
UserDefaults.standard.set(Int(values[0], forKey: "exposureISO")
UserDefaults.standard.synchronize()
Here is my code that hopefully demonstrates where the issue might be:\
import SwiftUI
import Combine
struct Settings: View {
let minISO = UserDefaults.standard.integer(forKey: "minISO")
let maxISO = UserDefaults.standard.integer(forKey: "maxISO")
let maxGain = UserDefaults.standard.integer(forKey: "maxGain")
@AppStorage("exposureISO") var exposureISO = DefaultSettings.exposureISO
@State private var enteredExposureISO: String = ""
@AppStorage("redGain") var redGain = DefaultSettings.redGain
var body: some View {
ScrollView {
VStack {
TextField("", text: self.$enteredExposureISO)
.onReceive(Just(enteredExposureISO)) { typedValue in
if let newValue = Int(typedValue) {
if (newValue >= Int(minISO)) && (newValue <= Int(maxISO)) {
exposureISO = newValue
}
}
}
.textFieldStyle(.roundedBorder)
.padding()
.onAppear(perform:{self.enteredExposureISO = "\(exposureISO)"})
Slider(
value: $redGain,
in: 1...Double(maxGain),
step: 0.1
) {
Text("red Gain")
} minimumValueLabel: {
Text("1")
} maximumValueLabel: {
Text("4")
}
.padding([.leading, .trailing, .bottom])
}
}
}
}
enum DefaultSettings {
static let exposureISO = 300
static let redGain = 2.0
}
struct JobView: View {
@ObservedObject var job: Job //does this need to be observed...?
var body: some View {
VStack {
Text("Settings").fontWeight(.medium)
Text(job.settings ?? "settings not found")
.multilineTextAlignment(.trailing)
Button(action: applySettings) {
Text("Apply these settings")
}
}
}
func applySettings() {
//parse job.settings and apply the values to the camera hardware
var value = ""
var values : [Float] = []
for char in job.settings! {
if Int(String(char)) != nil || char == "." {
value.append(char)
} else if char == "\n" {
values.append(Float(value)!)
value = ""
}
}
UserDefaults.standard.set(values[0], forKey: "exposureISO")
UserDefaults.standard.set(values[1], forKey: "redGain")
}
}
I find it most strange that the UserDefaults are being properly set if the button is tapped twice. I appreciate any help diagnosing the issue and how to solve it!
Edit
Like described in comments and as ChrisR suggested, I changed .onReceive
to .onSubmit
to hack/solve the problem, but I don't yet entirely understand why the Just(...)
publisher was emitting when the button was tapped. Here's the fix:\
TextField("", text: self.$enteredExposureISO)
.onSubmit {
if let newValue = Int(enteredExposureISO) {
if (newValue >= Int(minISO)) && (newValue <= Int(maxISO)) {
print("new value \(newValue)")
exposureISO = newValue
}
}
}
.textFieldStyle(.roundedBorder)
.padding()
.onAppear(perform:{self.enteredExposureISO = "\(exposureISO)"})
swift
swiftui
userdefaults
appstorage
0 Answers
Your Answer