You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:55:00