如何设置NSTextView仅可点击链接、不可选中其他普通文本
NSTextView实现链接可点击、普通文本不可选中的解决方案
直接设置isSelectable = false会拦截所有鼠标事件包括链接点击,官方推荐的选择变更委托方法会出现先选中再取消的闪烁问题,可通过以下两种方案实现需求:
方案1:属性字符串分段配置(优先推荐)
AppKit 从OS X 10.11开始支持NSAttributedString.Key.selectable属性,可针对富文本的不同片段单独设置是否可选,无需修改NSTextView全局属性:
- 解析HTML生成
NSMutableAttributedString后,遍历整个文本范围 - 对没有携带
NSAttributedString.Key.link属性的普通文本片段,添加.selectable属性值为false - 对携带链接属性的片段,保留
.selectable属性值为true - 保持NSTextView的
isSelectable属性为默认的true即可
该方案代码量最低,完全不会出现选中闪烁的问题。
方案2:自定义NSTextView子类(兼容低版本系统)
如果需要适配10.11以下的系统版本,可以通过自定义子类拦截选中逻辑实现需求:
class LinkOnlyTextView: NSTextView { // 重载鼠标按下事件,拦截非链接区域的点击 override func mouseDown(with event: NSEvent) { let clickLocation = convert(event.locationInWindow, from: nil) let characterIndex = characterIndexForInsertion(at: clickLocation) guard characterIndex < textStorage?.length ?? 0, textStorage?.attribute(.link, at: characterIndex, effectiveRange: nil) != nil else { return } super.mouseDown(with: event) } // 重载选中范围变更方法,彻底拦截非链接区域的选中 override func setSelectedRanges(_ ranges: [NSValue], affinity: NSSelectionAffinity, stillSelecting stillSelectingFlag: Bool) { let validRanges = ranges.filter { range in guard let textStorage = textStorage else { return false } var hasLink = false textStorage.enumerateAttribute(.link, in: range.rangeValue) { value, _, stop in if value != nil { hasLink = true stop.pointee = true } } return hasLink } super.setSelectedRanges(validRanges, affinity: affinity, stillSelecting: stillSelectingFlag) } }
使用说明
- 替换原有的NSTextView实例为自定义的
LinkOnlyTextView类型 - 保持
isSelectable属性为默认的true即可 - 不需要实现官方的选择变更委托方法,彻底避免选中后再取消的闪烁问题
- 鼠标点击非链接区域时会直接拦截事件,不会触发任何选中逻辑,点击链接区域时保留默认的跳转响应
如需自定义链接点击后的处理逻辑,可以实现NSTextViewDelegate的如下方法:
func textView(_ textView: NSTextView, clickedOnLink link: Any, at charIndex: Int) -> Bool { guard let url = link as? URL else { return false } // 自定义URL处理逻辑 NSWorkspace.shared.open(url) return true }
内容的提问来源于stack exchange,提问作者Антон Озерянский
相关产品推荐
相关产品推荐

