2

I'm trying to create what I thought would be a simple bit of code to show the key of a dictionary item and implement a Stepper or a Picker to modify the key's value in a single line within a Form view.

At the end of the day, all I'm trying to accomplish is showing the user a list of words they've entered, along with an associated number value that they can change.

Almost every method to solution my problem resulted in absolutely blowing up (crashing) my canvas because evidently my attempts at solutioning this are just that wrong.

Anyway, below are some examples of ways I've been trying to solve my problem. Note that I've also tried many types of views, like Text with the word, and a picker for the associated Integer. I just figure for the sake of demonstrating the problem, using a Stepper for each example might be good enough.

struct Likenesses {
    let word: String
    var likeness: Int = 0
}

struct ContentView: View {
    
    @State private var words: [String] = []
    @State private var likeness: [Int] = []
    @State private var wordLikeness: [String:Int] = [:]
    @State private var StructLikeness: [Likenesses] = []
    
    var body: some View {
        Form {
            
            // Dict Method
            Section {
                ForEach(Array(wordLikeness.keys), id: \.self) { key in
                    Stepper("\(key)", value: $wordLikeness[key], in: 0...8, step: 1)
                }
            }
            
            // Arrays method
            Section {
                ForEach(0...words.count, id: \.self) { i in
                    Stepper("\(word[i])", value: $likeness[i], in: 0...8, step: 1)
                }
            }
            
            // Struct method
            Section {
                ForEach(StructLikeness, id: \.self) { s in
                    /*
                    Yes, I know structs are not references and pass copies around, this was another issue I was going to have to solve eventually, but for now, this gets the idea of what I was trying to solve with a struct
                    */
                    Stepper("\(s.word)", value: $s.likeness, in: 0...8, step: 1)
                }
            }
        }
    }
}

1 Answers1

0

Dictionary elements don't have a particular order, so you need to decide on an order first, before you use them in ForEach. For example, if you want to sort by the keys,

@State private var likenesses: [String: Int] = ["Foo": 3]
Form {
    Section {
        ForEach(likenesses.keys.sorted(), id: \.self) { key in
            Stepper("\(key): \(likenesses[key]!)", value: Binding($likenesses[key])!, in: 0...8, step: 1)
        }
    }
}

I would prefer passing an array of structs instead. Create the binding when creating the ForEach, not when creating the steppers.

@State private var likenesses: [Likenesses] = [...]
Form {
    Section {
        ForEach($likenesses) { s in
            Stepper("\(s.wrappedValue.word): \(s.wrappedValue.likeness)", value: s.likeness, in: 0...8, step: 1)
        }
    }
}

Note that the struct should conform to Identifiable:

struct Likenesses: Identifiable {
    let word: String
    var likeness: Int = 0
    
    // here I'm using word as the id
    var id: String { word }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • You're a damn genius. I've spent days trying to figure this out. No way in heck was I gonna figure out the "s.wrappedValue.word" thing. I supposed that's necessary because of how structs behave being passed around? Also, is that why the foreach uses the binding operator? It ensures not a copy, but the same struct instance? – tjmcbutters Jul 03 '23 at 03:25
  • `wrappedValue` is needed because `s` in the `ForEach` closure is a `Binding`. If you give the `ForEach` a `Binding`, you get a `Binding` of each of the elements in `ForEach` too. And a `Binding` is exactly what `Stepper` needs as its value. – Sweeper Jul 03 '23 at 03:28