1 year ago

#152460

test-img

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

Accepted video resources