如何实现类原生通知的TableCellView悬停清除按钮持久显示效果
解决方案:表格单元格悬停与清除按钮交互优化
问题分析
当前实现仅通过单元格的mouseEntered/mouseExited控制清除按钮的显示,但清除按钮是独立的浮动视图,不属于单元格层级。当鼠标离开单元格边界时,会直接触发单元格的mouseExited事件隐藏按钮,完全忽略了鼠标停留在按钮上的场景,不符合需求。
修改思路
要实现鼠标在清除按钮上(即使超出单元格范围)时保持单元格悬停状态,需要同时监听单元格和清除按钮的鼠标事件,通过状态计数来判断是否需要维持悬停:
- 给清除按钮添加鼠标进入/离开的监听
- 维护一个悬停计数:进入单元格或按钮时计数+1,离开时-1,仅当计数归0时才隐藏按钮并取消单元格悬停状态
具体代码修改
1. 新增状态跟踪变量
在类中添加两个变量,用于跟踪当前悬停行和悬停计数:
// 记录当前悬停的行索引 private var hoveredRowIndex: Int = -1 // 悬停计数:单元格进入+1,按钮进入+1;任一离开则-1 private var hoverCount: Int = 0
2. 给清除按钮添加鼠标事件监听
修改floatingView的初始化代码,添加跟踪矩形以捕获鼠标事件:
var floatingView: NSView = { let view = NSView(frame: NSRect(x: 0, y: 0, width: 16, height: 16)) let shadow = NSShadow() shadow.shadowColor = .black.withAlphaComponent(0.3) shadow.shadowBlurRadius = 1.5 shadow.shadowOffset = NSMakeSize(0, -0.5) view.shadow = shadow // 添加跟踪矩形,启用鼠标事件监听 view.wantsLayer = true view.addTrackingRect(view.bounds, owner: self, userData: nil, assumeInside: false) return view }()
3. 重写鼠标事件处理逻辑
同时处理单元格和清除按钮的鼠标进入/离开事件:
// 处理单元格的鼠标进入 public func mouseEntered(in rowView: NSTableRowView) { let rowIndex = tableView.row(for: rowView) if hoveredRowIndex == -1 || hoveredRowIndex == rowIndex { hoverCount += 1 hoveredRowIndex = rowIndex showCellFrame(rowView: rowView) } } // 处理单元格的鼠标离开 public func mouseExited(with event: NSEvent, in rowView: NSTableRowView) { let rowIndex = tableView.row(for: rowView) if hoveredRowIndex == rowIndex { hoverCount -= 1 updateHoverState() } } // 处理清除按钮的鼠标进入 override public func mouseEntered(with event: NSEvent) { let point = floatingView.convert(event.locationInWindow, from: nil) if floatingView.bounds.contains(point) && hoveredRowIndex != -1 { hoverCount += 1 } } // 处理清除按钮的鼠标离开 override public func mouseExited(with event: NSEvent) { let point = floatingView.convert(event.locationInWindow, from: nil) if floatingView.bounds.contains(point) && hoveredRowIndex != -1 { hoverCount -= 1 updateHoverState() } } // 根据计数更新悬停状态 private func updateHoverState() { if hoverCount <= 0 { floatingView.isHidden = true // 此处添加取消单元格悬停样式的代码(比如还原背景色等) hoveredRowIndex = -1 hoverCount = 0 // 重置计数,避免出现负数 } }
4. 调整原有显示逻辑
修正showCellFrame中的行索引引用错误:
func showCellFrame(rowView: NSTableRowView) { let rowIndex = tableView.row(for: rowView) let cellFrame = tableView.frameOfCell(atColumn: 0, row: rowIndex) let convertedFrame = tableView.convert(cellFrame, to: self) if !isRowPartiallyVisible(rowIndex) || convertedFrame.minY < 72, (convertedFrame.minY + convertedFrame.height) < (scrollView.contentView.frameHeight + 10) { showFloatingView(xValue: convertedFrame.minX + convertedFrame.width, yValue: (convertedFrame.minY + convertedFrame.height) - 8) } } func showFloatingView(xValue : CGFloat, yValue: CGFloat) { floatingView.frame.origin = CGPoint(x: xValue, y: yValue) floatingView.isHidden = false }
关键说明
- 通过
hoverCount统一管理鼠标在单元格或按钮上的状态,只有当两者都离开时才结束悬停 - 给清除按钮添加
trackingRect是捕获其鼠标事件的核心,确保按钮能独立响应鼠标进出 - 严格匹配
hoveredRowIndex,避免不同行的悬停状态互相干扰
内容的提问来源于stack exchange,提问作者RRSK01_11
相关产品推荐
相关产品推荐

