I need to have a .contextMenu modifier that allows me to show a custom preview. While this is natively possible in SwiftUI from iOS 16.0+, I still need to provide support to iOS 15.0+ so I'm currently exploring possible workarounds.
I found this very useful blog that provided a good foundation for what I need. So I borrowed the code and made some adjustments like supporting .clear background, whatever.
So, the point is, currently any @State change from the containing SwiftUI view doesn't cause the menu to update itself, nor the preview. Here is a code demonstrating the issue:
struct TestOptions: OptionSet {
let rawValue: UInt
static let Test = TestOptions(rawValue: 1 << 0)
static let Test0 = TestOptions(rawValue: 1 << 1)
static let Test1 = TestOptions(rawValue: 1 << 2)
static let all: TestOptions = [.Test, .Test0, .Test1]
}
/// This is the ContentView.swift
@State private var option: TestOptions = .Test
var body: some View {
NavigationView {
Text("The selected test option: \(option.rawValue)")
.previewContextMenu(
preview: Text("\(option.rawValue)")
) {
PreviewContextAction(title: "Test", isSelected: self.option == .Test) {
self.option = .Test
}
PreviewContextAction(title: "Test 0", isSelected: self.option == .Test0) {
self.option = .Test0
}
PreviewContextAction(title: "Test 1", isSelected: self.option == .Test1) {
self.option = .Test1
}
}
}
}
Also for this to support the State tick I had to change the PreviewContextAction code to this.
I'm aware that iOS 15.0 introduced UIDeferredMenuElement.uncached via this topic, and I tried replacing the children parameter in UIMenu(title: "", children: self.view.actions) (PreviewContextView class) with it, but this produces the following error:
[UILog] Called -[UIContextMenuInteraction updateVisibleMenuWithBlock:] while no context menu is visible. This won't do anything
I couldn't find anything online about this issue, except for suggestions to ignore, but the menu actually doesn't work (the actions are performed correctly and state updates as per example) and I'm unsure about what causes the issue.
So now, how can I make it so that the menu and preview is recreated every time it gets activated?
I solved the problem myself following these steps:
In
PreviewContextView'scontextMenuInteractionI replaced thechildrenparameter with the following code:In
PreviewContextView'supdateUIView(_: UIView, context: Context)I added the following line of code:context.coordinator.view = selfNow the UIMenu updates correctly. Still, I'm getting two logs/warnings, that are the following:
[UILog] Called -[UIContextMenuInteraction updateVisibleMenuWithBlock:] while no context menu is visible. This won't do anything[LayoutConstraints] Changing the translatesAutoresizingMaskIntoConstraints property of a UICollectionReusableView that is managed by a UICollectionView is not supported, and will result in incorrect self-sizing. View: <_UIContextMenuTitleView: 0x15007b0a0; frame = (0 0; 250 43.5); layer = <CALayer: 0x2820d3fc0>>