Builds but won't preview or run. UserDefaults implicitly unwrapped nil value

This short example builds without error, but complains about the implicitly unwrapped nil value used for UserDefaults. How to correct this, or how else to set the UserDefaults default.

import SwiftUI

@main
struct SettingsTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// Settings preference values
enum MyValues: String, CaseIterable, Identifiable {
    case Closed, Open, Disabled
    var id: Self {self}
}
let KeyMyValue = "MyValue"
let DefaultMyValue = MyValues.Disabled

struct ContentView: View {
    @State private var myValue: MyValues = MyValues(rawValue: UserDefaults.standard.string(forKey: KeyMyValue)!) ?? DefaultMyValue
    
    var body: some View {
        Form {
            Picker("MyValue", selection: $myValue) {
                ForEach(MyValues.allCases) {myValue in
                    Text(myValue.rawValue)
                }
            }
            .pickerStyle(.automatic)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

You're trying to use nil coalescing, but since you force the value out with !, it crashes rather than falling back. string(forKey:) returns an optional value unless you properly call register(defaults:) for the key at app launch, so you need to stop force unwrapping to fix the crash. Also, using it this way means that the updated value will not be synced back to UserDefaults, so you either want to use @AppStorage or a binding into your own wrapper around UserDefaults. If you insist, you can still safely use it as a one liner: UserDefaults.standard.string(forKey: key).map(MyValues.init(rawValue:)) ?? DefaultMyValue. (By the way, Swift uses lower case names for instances. Upper case is only for types.)

1 Like

Thanks @Jon_Shier . I am willing to use AppStorage instead. Obviously the first time the App is run there will not be a value for the Key, which is why I need a default value.
Can you point me to an online example, or modify mine to work with AppStorage. None of the examples I can find have String style defaults.

I think you are recommending that my two let instances should be

let keyMyValue = "MyValue"
let defaultMyValue = MyValues.Disabled

and then by replacing the @State line with

@State private var myValue: MyValues = (UserDefaults.standard.string(forKey: keyMyValue).map(MyValues.init(rawValue:)) ?? defaultMyValue)!

the example now previews. Thank you so much.

Much simpler:

enum MyValues: String, CaseIterable, Identifiable {
    case Closed, Open, Disabled
    var id: Self {self}
}

struct ContentView: View {
    @AppStorage("MyValue") private var myValue: MyValues = .Disabled

    var body: some View {
        Form {
            Picker("MyValue", selection: $myValue) {
                ForEach(MyValues.allCases) {myValue in
                    Text(myValue.rawValue)
                }
            }
        }
    }
}
1 Like

Thanks @sveinhal for the exemplar code. Even more concise.