如何在NSTextAttachmentCell上实现鼠标追踪与点击事件?
不可编辑NSTextField中自定义令牌的鼠标追踪失效问题
问题背景
我在一个不可编辑的NSTextField中,使用带自定义样式的NSTextAttachment实现了令牌效果,目标是实现:鼠标悬停在令牌上时显示取消按钮,点击按钮可将令牌从文本字段中移除。但目前卡在第一步——鼠标追踪完全无效。
NSTextAttachmentCell遵循的NSTextAttachmentCellProtocol声明了wantsToTrackMouse()方法,我已实现并返回true(文档表明默认实现也返回true),但该方法从未被调用;同时实现的trackMouse(with:in:of:untilMouseUp:)也未触发。
参考旧方案后,我尝试重写hitTest方法:
override func hitTest(for event: NSEvent, in cellFrame: NSRect, of controlView: NSView) -> NSCell.HitResult { return NSCell.HitResult.trackableArea }
但该方法同样没有被调用。
简化代码示例
TokenCellTest 实现
import Cocoa class TokenCellTest: NSTextAttachmentCell { static let titleMargin: CGFloat = 0 override func cellSize() -> NSSize { let attribs = [NSAttributedString.Key.font: font ?? NSFont.systemFont(ofSize: 13)] let titleSize = (stringValue as NSString).size(withAttributes: attribs) return cellSize(forTitleSize: titleSize) } override func titleRect(forBounds rect: NSRect) -> NSRect { var bounds = rect bounds.size.width = max(bounds.size.width, TokenCellTest.titleMargin * 2 + bounds.size.height) bounds = bounds.insetBy(dx: TokenCellTest.titleMargin + bounds.size.height / 2, dy: 0) return bounds } override func draw(withFrame cellFrame: NSRect, in controlView: NSView?, characterIndex charIndex: Int) { guard let controlView = controlView else { return } if charIndex >= 0, let textView = controlView as? NSTextView { for rangeValue in textView.selectedRanges { let range = rangeValue.rangeValue guard NSLocationInRange(charIndex, range) else { continue } if textView.window?.isKeyWindow ?? false { break } } } drawToken(with: cellFrame, in: controlView) } // MARK: - private methods private func cellSize(forTitleSize titleSize: NSSize) -> NSSize { var size = titleSize size.height += 1 size.width += size.height + TokenCellTest.titleMargin * 2 let rect = NSRect(origin: .zero, size: size) return NSIntegralRect(rect).size; } private func drawToken(with rect: NSRect, in view: NSView) { NSGraphicsContext.current?.saveGraphicsState() drawTitle(with: titleRect(forBounds: rect), in: view) NSGraphicsContext.current?.restoreGraphicsState() } private func drawTitle(with rect: NSRect, in view: NSView) { (stringValue as NSString).draw(in: rect) } // MARK: - Mouse tracking override func trackMouse(with theEvent: NSEvent, in cellFrame: NSRect, of controlView: NSView?, untilMouseUp flag: Bool) -> Bool { print("trackMouse") return true } override func wantsToTrackMouse() -> Bool { print("wantsToTrackMouse") return true } override func hitTest(for event: NSEvent, in cellFrame: NSRect, of controlView: NSView) -> NSCell.HitResult { print("hit test") return NSCell.HitResult.trackableArea } }
令牌添加代码
let attachment = NSTextAttachment() let cell = TokenCellTest(textCell: "Blue") attachment.attachmentCell = cell let attStr = NSAttributedString(attachment:attachment) field.attributedStringValue = attStr
补充说明
- 令牌的显示效果正常,但鼠标交互完全无响应
- 未使用NSTokenField的原因是需要高度自定义令牌绘制逻辑,这部分已实现,但交互逻辑受阻
- 后续计划在附件单元格内嵌入按钮单元格,仅在鼠标悬停时显示,但当前无法感知鼠标是否在令牌范围内,也不确定如何传递点击事件给按钮单元格
- 曾参考ACBTokenField(无法在新版Xcode中打开)、OEXTokenAttachmentCell(不处理鼠标事件),均未解决问题
内容的提问来源于stack exchange,提问作者Nick K9
相关产品推荐
相关产品推荐

