Mac Catalyst Swift应用创建动态系统菜单的报错问题咨询
问题与解决方案
问题说明
- 将
buildMenu函数放入自定义类时出现两个错误:Method does not override any method from its superclassCannot find type 'UIMenuBuilder' in scope
- 该函数放在AppDelegate中可正常运行,尝试让自定义类继承AppDelegate,代码如下:
class MidiMenu: AppDelegate { override func buildMenu(with builder: UIMenuBuilder) { super.buildMenu(with: builder) builder.remove(menu: .file) } }
需求目标
实现动态菜单,流程如下:
- 添加"Profiles"系统菜单选项;
- 点击"Create New Profile"弹出带输入框的弹窗;
- 输入新配置文件名并确认后,在"Profiles"菜单下生成新的子菜单项。
错误原因
Cannot find type 'UIMenuBuilder' in scope:自定义类未导入对应框架(iOS/macOS Catalyst需导入UIKit,原生macOS需用NSMenuBuilder并导入AppKit),导致无法识别该类型。Method does not override any method from its superclass:继承AppDelegate的做法不符合设计规范,AppDelegate是应用委托类,不应被继承来实现菜单逻辑;且AppDelegate的buildMenu方法继承自UIResponder,自定义类的继承方式无法正确重写该方法。
正确实现方案
步骤1:创建菜单管理类
封装菜单构建、弹窗展示和菜单更新逻辑:
import UIKit // 原生macOS替换为import AppKit,UIMenuBuilder改为NSMenuBuilder class MidiMenuManager { static let shared = MidiMenuManager() // 存储配置文件名称 private var profileNames: [String] = [] // 构建Profiles菜单 func buildProfilesMenu() -> UIMenu { var menuActions: [UIAction] = [] // 添加已存在的配置选项 for name in profileNames { let action = UIAction(title: name) { _ in // 处理配置文件选中逻辑 print("Selected profile: \(name)") } menuActions.append(action) } // 添加创建新配置的选项 let createAction = UIAction(title: "Create New Profile") { [weak self] _ in self?.showCreateProfileAlert() } menuActions.append(createAction) return UIMenu(title: "Profiles", children: menuActions) } // 弹出创建配置的弹窗 private func showCreateProfileAlert() { let alert = UIAlertController(title: "Create New Profile", message: "Enter profile name", preferredStyle: .alert) alert.addTextField { textField in textField.placeholder = "Profile name" } let confirmAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in guard let name = alert.textFields?.first?.text, !name.isEmpty else { return } self?.profileNames.append(name) // 触发菜单更新 self?.updateMenu() } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) alert.addAction(confirmAction) alert.addAction(cancelAction) // 获取根控制器展示弹窗 if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let rootVC = windowScene.windows.first?.rootViewController { rootVC.present(alert, animated: true) } } // 更新应用菜单 private func updateMenu() { UIApplication.shared.keyWindow?.windowScene?.menuBuilder?.rebuild() } }
步骤2:在AppDelegate中集成菜单逻辑
在AppDelegate的buildMenu方法中调用管理类构建菜单:
import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { } override func buildMenu(with builder: UIMenuBuilder) { super.buildMenu(with: builder) // 移除不需要的系统菜单 builder.remove(menu: .file) builder.remove(menu: .edit) builder.remove(menu: .format) // 添加自定义Profiles菜单 let profilesMenu = MidiMenuManager.shared.buildProfilesMenu() builder.insertSibling(profilesMenu, afterMenu: .application) } }
方案说明
- 采用单例模式的菜单管理类,实现职责分离,避免污染AppDelegate的代码;
- 新增配置后调用
menuBuilder?.rebuild()触发菜单重新构建,自动更新子菜单项; - 确保导入对应框架,解决类型找不到的问题;
- 摒弃继承AppDelegate的错误做法,遵循iOS/macOS应用的设计规范。
内容的提问来源于stack exchange,提问作者jerry seigle
相关产品推荐
相关产品推荐

