iosswiftswiftuipicker

How to set a SceneStorage property initial value in SwiftUI


I have a SwiftUI view with a Picker that lists a set of objects, whose id are of type UUIDs. For the purpose of reducing the example to the minimum code, I've represented the whole object as UUIDs. In the real application, the array of objects comes from the environment, but it is not important to the question.

When rendering the view, I get the following warning:

"Picker: the selection "nil" is invalid and does not have an associated tag, this will give undefined results."

How can I initialize the selected value for the picker, which is saved to SceneStorage, so that I get rid of the warning. In the example the saving to SceneStorage is useless, but in the real application is a requirement, so I cannot get rid of it.

Here is my code:

struct ContentView: View {
    
    private var values: [UUID] = [ UUID(), UUID()]
    
    @SceneStorage("VALUE") private var selectedValue: UUID?
        
    var body: some View {
        Form {
            Section("Test") {
                Picker("Select a value", selection: $selectedValue) {
                    //Text(values[0].uuidString).tag(values[0])
                    //Text(values[1].uuidString).tag(values[1])
                    ForEach(values, id: \.self) { value in
                        Text(value.uuidString).tag(value)
                    }
                }
                //.pickerStyle(.inline)
                .pickerStyle(.menu)
            }
        }
        .onAppear() {
            self.selectedValue = values[1] as UUID?
        }
    }
}

extension UUID: RawRepresentable {
    
    public typealias RawValue = String

    public init?(rawValue: RawValue) {
        self.init(uuidString: rawValue)
    }
    
    public var rawValue: String {
        self.uuidString
    }

}

My understanding is that the problem arises from the fact that the property selectedValue is Optional and SwiftUI is trying to render the view when its value has not yet been set. How can I get rid of the warning? Is there any way to initialize a SeceneStorage property value before onAppear, like on init?

I want to note also that such warning is not shown if the picker style is changed to .inline.

Thanks!


Solution

  • You can wrap the Picker in an if selectedValue != nil { ... }. This prevents the whole picker from appearing when the selected value is nil, so obviously that warning will not appear.

    if selectedValue != nil {
        Picker("Select a value", selection: $selectedValue) {
            // ...
        }
    }
    

    You can also get a non-optional Binding to the selected value, and use that instead,

    if let binding = Binding($selectedValue) {
        Picker("Select a value", selection: $binding) {
            // ...
        }
    }
    

    For the toy example with UUIDs shown in the question, the warning will still appear on subsequent launches, because every time you are generating 2 new UUIDs, which will most definitely be different from the previous one stored in scene storage.