Context Menu Not Updating in Swiftui

Context Menu not updating in SwiftUI

It's a bug in SwiftUI, and it is still broken in the simulator of Xcode 13.2 beta 2.

I managed to work around it by duplicating the list item in both branches of an if item.active statement, like this:

struct ContentView: View {
@State var items: [Item] = [
Item(user: "Daniel",active: false),
Item(user: "Jack", active: true),
Item(user: "John", active: true)
]

var body: some View {
List {
ForEach(items) { item in
// work around SwiftUI bug where
// context menu doesn't update
if item.active { listItem(for: item) }
else { listItem(for: item) }
}
}
}

private func listItem(for item: Item) -> some View {
VStack {
Text("\(item.user)")

HStack {
Text("Active ? ")
Text(item.active ? "YES": "NO")
}
}
.contextMenu {
Button(item.active ? "Deactivate" : "Activate") {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index].active.toggle()
}
}
}
.id(item.id)
}
}

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

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)

Make a .contextMenu update with changes from PasteBoard

You can listen to UIPasteboard.changedNotification to detect changes and refresh the view:

struct ContentView: View {
@State private var pasteDisabled = false

var body: some View {
Text("Some Text")
.contextMenu {
Button(action: {}) {
Text("Paste")
Image(systemName: "doc.on.clipboard")
}
.disabled(pasteDisabled)
}
.onReceive(NotificationCenter.default.publisher(for: UIPasteboard.changedNotification)) { _ in
pasteDisabled = !UIPasteboard.general.contains(pasteboardTypes: [aPAsteBoardType])
}
}
}

(You may also want to use UIPasteboard.removedNotification).

SwiftUI: Problem with Picker inside ContextMenu of NavigationBarItem

Context menu is created once and cached, so we need to rebuild it once anything changed outside.

Here is a fix. Tested with Xcode 13.3 / iOS 15.4

.contextMenu {
Picker(selection: self.$isAutoRefresh, label: Text("")) {
Text("Manual refresh").tag(false)
Text("Auto refresh").tag(true)
}
.pickerStyle(InlinePickerStyle())
}.id(isAutoRefresh) // << here !!

Alternate: Just to use Menu instead of Button with context menu (if applicable by design), like

Menu {
Picker(selection: self.$isAutoRefresh, label: Text("")) {
Text("Manual refresh").tag(false)
Text("Auto refresh").tag(true)
}
.pickerStyle(InlinePickerStyle())
} label: {
Image(systemName: "arrow.clockwise")
}

SwiftUI context menu not passing correct variable

sheets should only be used on the top level. This causes unexpected behaviour as the warnings in your output should also say.

Here is a working example:

struct ContentView: View {

let items = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

@State var showing = false
@State var currentItem: String = ""

var body: some View {
NavigationView {
List {
ForEach(items, id: \.self) { item in
Text(item).font(.largeTitle)
.contextMenu {
Button(action: {
self.currentItem = item
self.showing.toggle()
}) {
Text("Edit")
Image(systemName: "circle")
}
}
}
}
}.sheet(isPresented: self.$showing) {
Text(self.currentItem)
}
}
}

I hope this helps

Greetings krjw



Related Topics



Leave a reply



Submit