6

I'm facing an issue where the displayed Context Menu shows the wrong data, even though the List underneath displays the correct one. The issue is that once triggering the action on the context menu of the first item, you'll see how the List re-renders and shows the correct data but if you trigger the context menu again for the first item, it won't show the correct state. If you open the context menu for the second item, it will display the correct state, but if you now select "Two", and open the same context menu, the State will be wrong (it'll display only 1 selected when it should show 1 & 2, like the List displays it).

It feels like it's off by one (like presenting the previous state instead of the latest one) and I'm not sure if it's just a bug or I'm using it wrong.

Here's a snippet of code to reproduce the issue:

@main
struct ContextMenuBugApp: App {
    
    let availableItems = ["One", "Two", "Three", "Four", "Five"]
    @State var selectedItems: [String] = []
    
    var body: some Scene {
        WindowGroup {
            List {
                ForEach(availableItems, id: \.self) { item in
                    HStack {
                        let isAlreadySelected = selectedItems.contains(item)
                        Text("Row \(item), selected: \(isAlreadySelected ? "true" : "false")")
                    }.contextMenu {
                        ForEach(availableItems, id: \.self) { item in
                            let isAlreadySelected = selectedItems.contains(item)
                            Button {
                                isAlreadySelected ? selectedItems.removeAll(where: { $0 == item }) : selectedItems.append(item)
                            } label: {
                                Label(item, systemImage: isAlreadySelected ? "checkmark.circle.fill" : "")
                            }
                        }
                    }
                }
            }
        }
    }
}

Video demonstrating the issue: https://twitter.com/xmollv/status/1412397838319898637

Thanks!

Edit:

It seems to be an iOS 15 regression (at least on Release Candidate), it works fine on iOS 14.6.

Xavi Moll
  • 247
  • 2
  • 14
  • I wasn't able to reproduce the described issue with Xcode 12.5.1 and iOS 14.4. – Mokkun Jul 11 '21 at 08:08
  • 1
    @Mokkun It seems to be an iOS 15 regression, I've just tested it on 14.6 and works as expected. I'll update my Radar and hope that it's fixed before iOS 15 lands. – Xavi Moll Jul 12 '21 at 09:43

1 Answers1

7

you can force the contextMenu to redraw with background view with arbitrary id. i.e.:

@main
struct ContextMenuBugApp: App {
    let availableItems = ["One", "Two", "Three", "Four", "Five"]
    @State var selectedItems: [String] = []
    
    func isAlreadySelected(_ item: String) -> Bool {
        selectedItems.contains(item)
    }
    
    var body: some Scene {
        WindowGroup {
            List {
                ForEach(availableItems, id: \.self) { item in
                    HStack {
                        Text("Row \(item), selected: \(isAlreadySelected(item) ? "true" : "false")")
                    }
                    .background(
                        Color.clear
                            .contextMenu {
                                ForEach(availableItems, id: \.self) { item in
                                    Button {
                                        isAlreadySelected(item) ? selectedItems.removeAll(where: { $0 == item }) : selectedItems.append(item)
                                    } label: {
                                        Label(item, systemImage: isAlreadySelected(item) ? "checkmark.circle.fill" : "")
                                    }
                                }
                            }.id(selectedItems.count)
                    )
                }
            }
        }
    }
}

If it doesn’t work, you can try just putting id to contextMenu without the background (this could be based on iOS version, it didn’t work before, so be careful and test prior iOS)

cluelessCoder
  • 948
  • 6
  • 19
  • You're right, adding it on the background with the id seems to fix the issue. I've accepted it as it seems to be the only workaround possible at the moment. Thank you! – Xavi Moll Sep 20 '21 at 13:59
  • in my case this workaround did not work – charelf Oct 06 '21 at 20:41
  • Cannot help without code – cluelessCoder Oct 11 '21 at 00:24
  • 1
    In my case, put the .contextMenu in .background didn't work ( The contextMenu didn't show up ). So I moved it out of .background and it works well. It looks like the .background and Color.clear is not necessary. Thank you very much for the idea of adding an id for the .contextMenu. It updates beautifully. – zephyr Nov 17 '21 at 23:19
  • @zephyr could be based on Swift or iOS version - it didn’t work for me earlier when I just provided the id. – cluelessCoder Nov 18 '21 at 18:37
  • @zephyr's comment worked for me. My context menu had stopped appearing, so I removed the .background and Color.clear, so the only change was adding the id modifier to the .contextMenu. – aryanm Nov 28 '21 at 11:52
  • 1
    Thank you, I updated my post. – cluelessCoder Dec 02 '21 at 15:51