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.