如何实现点击MKAnnotationView弹出上下文菜单并同步触发UIMenu
问题原因
你的代码无法生效主要有三个问题:
- 存在基础笔误:定义变量时写的是
FirstAction,后续添加到菜单时用的是firstAction,大小写不匹配会直接导致编译报错。 calloutAccessoryControlTapped被调用时,系统已经拦截了该点击事件用来触发代理回调,此时主动调用sendActions(for: .primaryActionTriggered)无法正常唤起菜单的弹出交互。- 你在
didSelect代理方法中每次选中标注都重新创建按钮赋值给rightCalloutAccessoryView,属于重复冗余操作,也可能导致菜单关联异常。
可行解决方案
推荐使用系统原生的上下文菜单交互实现,可同时覆盖「点击标注视图本身」和「点击callout附属按钮」两个场景,逻辑更清晰也更稳定,最低支持iOS14+,实现代码如下:
1. 配置标注视图的基础属性和交互
不需要在didSelect里重复创建按钮,直接在生成复用标注视图的代理方法中统一配置:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // 过滤掉用户位置标注 guard !(annotation is MKUserLocation) else { return nil } let reuseId = "CustomAnnotation" var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) if annotationView == nil { annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseId) // 给标注视图本身添加上下文菜单交互,实现点击标注直接弹菜单 let interaction = UIContextMenuInteraction(delegate: self) annotationView?.addInteraction(interaction) // 配置callout右侧按钮的菜单 let button = UIButton(type: .detailDisclosure) button.menu = buildCustomMenu() button.showsMenuAsPrimaryAction = true annotationView?.canShowCallout = true annotationView?.rightCalloutAccessoryView = button } annotationView?.annotation = annotation return annotationView }
2. 封装统一的菜单生成方法
避免重复写菜单配置代码,保证点击标注和点击callout按钮弹出的菜单完全一致:
// 统一生成自定义菜单 private func buildCustomMenu() -> UIMenu { let firstAction = UIAction(title: "First Action") { _ in print("First action") } let secondAction = UIAction(title: "Second Action") { _ in print("Second action") } return UIMenu(title: "My Title", children: [firstAction, secondAction]) }
3. 实现上下文菜单交互代理
点击标注视图本身时,系统会调用该代理方法返回菜单并自动弹出:
extension YourViewController: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { // 校验当前交互对应的标注是否有效 guard let annotationView = interaction.view as? MKAnnotationView, let _ = annotationView.annotation else { return nil } // 返回统一配置的菜单 return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in return self?.buildCustomMenu() } } }
4. 删除冗余代码
不需要再实现calloutAccessoryControlTapped代理,因为你已经给右侧按钮设置了showsMenuAsPrimaryAction = true,点击按钮会自动弹出菜单,不需要额外处理。
如果你的项目需要兼容iOS16以下版本,又不想用上下文交互方案,也可以用以下代码在calloutAccessoryControlTapped中主动弹出按钮菜单:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { guard let button = control as? UIButton, let menu = button.menu else { return } if #available(iOS 16.0, *) { button.presentMenu(animated: true) } else { // iOS16以下版本无系统方法主动弹出菜单,建议使用上面的ContextMenuInteraction方案 } }
内容的提问来源于stack exchange,提问作者Peter Jacobs
相关产品推荐
相关产品推荐

