Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Context Menu display the old state even though the List has correctly been updated?

Tags:

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.

like image 585
Xavi Moll Avatar asked Jul 09 '21 08:07

Xavi Moll


People also ask

Which actions displays the Windows context menu?

In Microsoft Windows, pressing the Application key or Shift+F10 opens a context menu for the region that has focus.

When right clicking a chart what tool is available on the context menu?

The context menu allows you to perform various operations on the visual, such as analyzing, summarizing, or copying it. When you right-click anywhere inside a visual's viewport (or long-press for touch devices) the context menu displays. There are two modes of context menus for each visual.


1 Answers

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)

like image 115
cluelessCoder Avatar answered Oct 19 '22 11:10

cluelessCoder