You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何检测ContextMenu的每次显示?解决onAppear仅触发一次的问题

Got it, I ran into this exact issue a while back! The problem with using onAppear inside the contextMenu content is that SwiftUI caches that menu view hierarchy after the first time it's shown—so onAppear only fires once, even if you open the menu multiple times.

Luckily, there are a couple of solid ways to detect every time the ContextMenu is about to appear. Here are the most reliable approaches:

1. Use UIKit's UIContextMenuInteraction (iOS 13+)

Since SwiftUI's ContextMenu is built on top of UIKit's UIContextMenuInteraction, we can create a custom ViewModifier that hooks into the interaction's delegate methods to detect when the menu is about to display. This works for all iOS versions that support ContextMenus (iOS 13+).

First, create the delegate and modifier:

import SwiftUI

class ContextMenuDelegate: NSObject, UIContextMenuInteractionDelegate {
    var willDisplayMenu: (() -> Void)?
    
    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willDisplayMenuFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
        // This gets called EVERY time the menu is about to show
        willDisplayMenu?()
    }
}

struct ContextMenuPreActionModifier: ViewModifier {
    let preAction: () -> Void
    
    func body(content: Content) -> some View {
        content
            .background(
                UIViewRepresentable { view in
                    let interaction = UIContextMenuInteraction(delegate: ContextMenuDelegate())
                    if let delegate = interaction.delegate as? ContextMenuDelegate {
                        delegate.willDisplayMenu = preAction
                    }
                    view.addInteraction(interaction)
                }
            )
    }
}

extension View {
    // Convenience extension to make usage cleaner
    func onContextMenuWillAppear(perform action: @escaping () -> Void) -> some View {
        self.modifier(ContextMenuPreActionModifier(preAction: action))
    }
}

Then use it in your view like this:

var body: some View {
    Text("Hello, world!")
        .contextMenu {
            VStack {
                Button(action: { }) {
                    Text("Normal Colors")
                }
                Button(action: { }) {
                    Text("Inverted Colors")
                }
            }
        }
        .onContextMenuWillAppear {
            // This code runs EVERY time the context menu is about to appear
            print("Context menu is opening—time to run my pre-action!")
        }
}

2. iOS 16+ Only: Use Menu with onPresented

If you're targeting iOS 16 or later, SwiftUI added much cleaner support for menu presentation callbacks. You can replace the contextMenu with a Menu set to trigger on right-click, then use the onPresented closure to run your pre-action:

var body: some View {
    Text("Hello, world!")
        .menu {
            Button(action: { }) {
                Text("Normal Colors")
            }
            Button(action: { }) {
                Text("Inverted Colors")
            }
        }
        .menuTrigger(.contextMenu) // Makes it open on right-click/long press
        .onPresented {
            // Runs every time the context menu is opened
            print("Context menu is appearing—pre-action time!")
        }
}

This approach is way simpler if you don't need to support older iOS versions. The onPresented closure is designed to fire every time the menu is displayed, so you don't have to deal with any view caching issues.

Key Takeaway

  • Use the UIKit-based modifier if you need iOS 13+ compatibility.
  • Use the iOS 16+ Menu approach for a cleaner, pure-SwiftUI solution.

内容的提问来源于stack exchange,提问作者jdleung

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 17:22:48