Set default picker value

Hi,


I have this picker


Picker("From status", selection: $fromStatusSelectorIndex) {
  ForEach(0 ..< orderStatusFromArray.count) { index in
  Text(self.orderStatusFromArray[index]).tag(index)
  }
  }


that is using data from this array:


private var orderStatusFromArray: [String] = ["005", "010", "022", "025", "030", "035", "040", "045", "046", "047", "060"]

When saving my values, I store the picker selection in UserDefaults

self.settingStore.defaults.set(self.orderStatusFromArray[self.fromStatusSelectorIndex], forKey: "view.orderHeaderFilter.fromStatus")


What I don't understand: where in my code do I have to place it to set the picker to the saved default value? Let's say user saved the settings with status 025 (orderStatusFromArray[3]) - where do I explain to SwiftUI that the picker should be set to 3 when the view is loaded?


Max

The index/count work you're doing should already by handled by the ForEach view. In general, rather than having the Picker operate on indices, have it operate on the values themselves:


@State var orderStatus: String
private static var orderStatusFromArray = ["005", "010", "022", "025", "030", "035", "040", "045", "046", "060"]

var body: some View {
    Picker("From status", selection: $orderStatus) {
        ForEach(Self.orderStatusFromArray, id: \.self) { status in
            Text(status)
        }
    }
}

private func saveStatus() {
    UserDefaults.standard.set(orderStatus, forKey: "Ordeview.orderHeaderFilter.fromStatusStatus")
}


The tag behavior and the indices are all internal and up to the Picker to manage, because they technically only matter to the generation of the picker view itself. As far as you're concerned, you have a list of values and the user should pick one. Ultimately there's something like an implicit tag added to your Text view by the ForEach: it'll be tagged with the value of 'status' in this case.


This is a common enough situation that you'll often see enumerations used for this purpose. Consider:


enum OrderStatus: String, CaseIterable, Identifiable {
    case s05 = "005"
    case s10 = "010"
    case s22 = "022"
    case s25 = "025"
    case s30 = "030"
    case s35 = "035"
    case s40 = "040"
    case s45 = "045"
    case s46 = "046"
    case s60 = "060"

    var id: String { rawValue }
}

@State var orderStatus: OrderStatus

init() {
    // Restore from defaults
    if let saved = UserDefaults.standard.string(forKey: "view.orderHeaderFilter.fromStatus") {
        self.orderStatus = OrderStatus(rawValue: saved) ?? .v05
    } else {
        self.orderStatus = .v05
    }
}

var body: some View {
    Picker("From Status", selection: $orderStatus) {
        ForEach(OrderStatus.allCases) { status in
            Text(status.rawValue)
        }
    }
}

private func saveStatus() {
    UserDefaults.standard.set(orderStatus.rawValue, forKey: "view.orderHeaderFilter.fromStatus")
}


In this instance, you have a nice representation for your code to use (the OrderStatus enum) and a handy textual variant for reading/writing to the user defaults.


In fact, why not go a little further? Wouldn't it be nice if your @State variable was simply backed by UserDefaults directly, and vended an appropriate Binding when needed? Look no further:


@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    
    init(_ key: String, defaultValue: Value) {
        self.key = key
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: Value {
        get { Self.getWrapped(forKey: key, defaultValue: defaultValue) }
        mutating set { Self.setWrapped(value: newValue, forKey: key) }
    }
    
    var projectedValue: Binding<Value> {
        Binding(get: { Self.getWrapped(forKey: self.key, defaultValue: self.defaultValue) },
                set: { Self.setWrapped(value: $0, forKey: self.key) })
    }
    
    static private func getWrapped(forKey key: String, defaultValue: Value) -> Value {
        UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
    }
    static private func setWrapped(value: Value, forKey key: String) {
        UserDefaults.standard.set(value, forKey: key)
    }
}


With this, you'll have a significantly simpler implementation:


@UserDefault("view.orderHeaderFilter.fromStatus", OrderStatus.v05)
private var orderStatus: OrderStatus

var body: some View {
    Picker("From Status", selection: $orderStatus) {
        ForEach(OrderStatus.allCases) { status in
            Text(status.rawValue)
        }
    }
}


Saving to and loading from UserDefaults is now built into the property wrapper.

Thanks! I have changed my picker to


Picker("From status", selection: $fromStatus2) {
  ForEach(Self.orderStatusFromArray, id: \.self) { status in
  Text(status)
  }
  }


with $statusFrom2 being


@State private var fromStatus2 = UserDefaults.standard.string(forKey: "view.orderHeaderFilter.fromStatus")


To check the value in view.orderHeaderFilter.fromStatus on runtime I have added


.onAppear{
  print("^^^^^^^^^^^^^^^^^^^^^^^")
  print(UserDefaults.standard.string(forKey: "view.orderHeaderFilter.direction")!)
  print(UserDefaults.standard.string(forKey: "view.orderHeaderFilter.fromStatus")!)
  print(UserDefaults.standard.string(forKey: "view.orderHeaderFilter.toStatus")!)
  print("^^^^^^^^^^^^^^^^^^^^^^^")
  }


and that produces


^^^^^^^^^^^^^^^^^^^^^^^

025
040
^^^^^^^^^^^^^^^^^^^^^^^


but the picker is still not set to a default value of 025. When I change it to


@State private var fromStatus2 = "025"


it works fine. What am I missing here now?

And by the way, is there a way to see the value of $fromStatus2 when I set a breakpoint on the picker? I tried but it did not show anything when hovering with the cursor over it.


Max

Set default picker value
 
 
Q