如何禁用UITextView中Look up、Share等不需要的系统文本菜单选项
解决方案
拦截失效有两个核心原因:
- 你用Extension重写
UITextView的canPerformAction本身是错误实现:分类/扩展无法可靠替换系统类的原生方法,方法加载顺序不确定,大概率会被系统实现覆盖,不会执行你写的逻辑,还会全局污染所有UITextView实例。 - 拦截的Selector不全、名称不匹配:你只写了
_lookup:和_share:,漏了不同系统版本下这几个功能对应的变体Selector,加上系统菜单会沿响应链逐层判断是否可执行,只在当前层返回false不足以完全拦截。
具体实现步骤
- 创建
UITextView的自定义子类,不要用扩展重写系统方法 - 补全所有需要拦截的系统菜单对应的Selector,覆盖新旧版本系统的方法名
- iOS 17及以上版本优先用
UIMenuBuilder从构建层移除不需要的菜单,稳定性远高于Selector拦截
兼容低版本的Selector拦截代码
class MenuFilteredTextView: UITextView { override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { // 要禁用的菜单对应的Selector列表,覆盖全系统版本 let blockedActions = [ Selector(("_lookup:")), Selector(("_lookUp:")), Selector(("_define:")), Selector(("_share:")), Selector(("_showSpellingPanel:")), Selector(("_guess:")), Selector(("_checkSpelling:")), Selector(("_speak:")), Selector(("_pauseSpeaking:")), Selector(("_stopSpeaking:")) ] if blockedActions.contains(action) { return false } // 仅保留需要显示的菜单 let allowedActions = [ #selector(UIResponderStandardEditActions.copy(_:)), #selector(copyAll(_:)), #selector(lookUpWord(_:)) ] return allowedActions.contains(action) } }
iOS 17+ 更稳定的菜单构建拦截
override func buildMenu(with builder: UIMenuBuilder) { // 直接移除对应系统菜单组,不需要逐个判断Selector builder.remove(menu: .lookup) builder.remove(menu: .share) builder.remove(menu: .spelling) builder.remove(menu: .speech) super.buildMenu(with: builder) }
这个实现是在系统生成菜单阶段直接删除不需要的菜单项,不会出现返回false但选项依然显示的问题,没有响应链穿透的隐患,只要系统版本满足就优先使用。
内容的提问来源于stack exchange,提问作者user8460166
相关产品推荐
相关产品推荐

