skip to Main Content

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

EDIT : here’s a minimal reproducible example. notice the circled text that clearly shows that the arg didn’t get the tag expression but the whole item :
enter image description here

2

Answers


  1. 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
            }
        }
    }
    

    EDIT-1:
    Note, on macos (Monterey) only app, pickerStyle with radioGroup, segmented and inline, will work (i.e, no repeat of tags),
    however, menu, and automatic do not.

    On ios/mac Catalyst, wheel, inline, segmented and automatic work, but menu does not.

    Login or Signup to reply.
  2. 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

    Here’s an example from Apple

    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)
                }
            }
    
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search