Home > database >  SwiftUI ForEach statement iterate the list twice
SwiftUI ForEach statement iterate the list twice

Time:02-10

I've got ForEach loop inside a picker widget, that suppose to iterate a list of items (handler.items).

However, in reality, I get that the tags where not properly set and $arg = $0 instead of $arg = $0.components(separatedBy: " ")[0]

after debugging the problem, it seems that although i see the right number of items according to the list size ... the ForEach iterate all the items twice, thus causing duplicated tags where they suppose to be unique.

here's the code, any idea how come the iteration repeat itself ?

VStack {
    Picker("items", selection: $arg) {
        ForEach(handler.items , id: \.self, content: {
            Text($0).tag($0.components(separatedBy: " ")[0])
        })
    }
}

and here's how handler.items are defined. it's actually a separated object that is visible from the view, and can be changed from swift native methods.

class myHandler: ObservableObject {
  @Published var items = [String]()
}

when setting a breakpoint in the ForEach statement, i could print this object

(lldb) p handler.items
([String]) $R1 = 2 values {
  [0] = "aaa bbb cc 1.1.1.1"
  [1] = "ddd eee ff 2.2.2.2"
}

When i put breakpoint inside the ForEach body, I could see that the breakpoint caught there 4 times, and printing the iterator value i Got this printouts:

aaa bbb cc 1.1.1.1
ddd eee ff 2.2.2.2
aaa bbb cc 1.1.1.1
ddd eee ff 2.2.2.2

CodePudding user response:

You need to have .pickerStyle(.wheel) to make it work on ios devices. Here is the code I used for testing:

(on macos 12.3-beta, using xcode 13.3-beta, targets ios 15 and macCatalyst 12)

class MyHandler: ObservableObject {
    @Published var items: [String] = ["aaa bbb cc 1.1.1.1", "ddd eee ff 2.2.2.2"]
}

struct ContentView: View {
    @State var handler = MyHandler()
    @State var arg = ""
    
    var body: some View {
        VStack {
            Text(arg) // <-- to display arg
            Picker("items", selection: $arg) {
                ForEach(handler.items , id: \.self) { str in
                    let _ = print(str.components(separatedBy: " ")[0]) // <-- to print tag values
                    Text(str).tag(str.components(separatedBy: " ")[0])
                }
            }.pickerStyle(.wheel)  // <--- here
        }
    }
}

CodePudding user response:

Well I suggest moving the strings to a custom enumeration instead of your ObservableObject. Setting the tag doesn't really need to be done then. And no duplicate entries

enum Items: String, CaseIterable, Identifiable {
    case a = "aaa bbb cc 1.1.1.1"
    case b = "ddd eee ff 2.2.2.2"
    var id: Self { self }
}

struct ContentView: View {
    
    @State var selection = Items.a

    var body: some View {
        
        Picker("Items", selection: $selection) {
            ForEach(Items.allCases) { item in
                Text(item.rawValue)
            }
        }

    }
}
  • Related