Search code examples
iosswiftswiftuiswiftui-list

Popover is not opening from a list of items in SwiftUI


I have a list of items and when user click on it, I need to show some content in .popover().

Model:

struct Item: Identifiable {
  var id: String {
     UUID().uuidString
  }
  var name: String?
  var addr: String?

  init(name: String? = nil, addr: String? = nil) {
    self.name = name
    self.addr = addr
  }
}

View:

struct ContentView: View {
  let arr = [
    Item(name: "Roman", addr: "Address of Roman"),
    Item(name: "Alexa", addr: "Address of Alexa"),
  ]
  @State private var selection: Item?

  var body: some View {
    VStack {
        List {
            ForEach(arr) { item in
                Text(item.name!)
                    .id(item.name!)
                    .onTapGesture {
                        selection = item
                        print("selection: \(item.name ?? "")")
                    }
                    .popover(item: $selection) { item in
                        Text(item.addr ?? "")
                    }
                
            }
        }
     }
  }

When user click on the name. The popover is not showing up.

If there is only one item in the list, the popover is showing as expected(like below image).

enter image description here

Why the popover is not working as expected with List of items?

Is this a bug in SwiftUI?


Solution

  • The issue is because you are assigning a single binding to multiple popovers. You should differentiate the binding for each row, for example by making a different binding per row:

    ForEach(arr) { item in
        let binding = Binding<Item?>(
            get: { selection == item ? item : nil }, // 👈 returns the item only for the corresponding row
            set: { selection = $0 }
        )
        
        Text(item.name!)
            .id(item.name!)
            .onTapGesture {
                selection = item
                print("selection: \(item.name ?? "")")
            }
            .popover(item: binding) { item in // 👈 Use the specified binding here
                Text(item.addr ?? "")
            }
    }
    

    Don't forget to make the Item: Equatable.