NSTrackingArea的enabledDuringMouseDrag属性无法正常工作的问题排查
问题描述
我正尝试实现右键拖拽操作来连接两个NSView,效果类似Xcode Interface Builder中创建约束的方式,希望目标NSView能在mouseEntered时响应拖拽操作。
我按如下方式设置跟踪区域:
override func updateTrackingAreas() { for trackingArea in self.trackingAreas { self.removeTrackingArea(trackingArea) } let tracking = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeInKeyWindow, .enabledDuringMouseDrag], owner: self, userInfo: nil) self.addTrackingArea(tracking) }
问题在于:拖拽时mouseEntered不会被调用,但非拖拽状态下功能正常。有什么办法可以解决这个问题吗?
[编辑] 我发现可以在rightMouseDragged调用期间,对任意视图调用updateTrackingAreas()来强制生效,但这似乎不太合理——我是不是遗漏或误解了什么?
[编辑2] 似乎自macOS 13起该功能就失效了?苹果开发者论坛上有相关讨论提到这个兼容性问题。
解决方案
1. 调整拖拽会话的事件传递设置
右键拖拽启动后,系统默认会将事件控制权转移给拖拽会话,普通跟踪区域的事件可能被拦截。你可以在拖拽发起时做以下处理:
- 在发起拖拽的视图的
rightMouseDown方法中,创建并启动NSDraggingSession后,设置其isSpringLoaded属性为true,这会让系统在拖拽过程中持续派发鼠标进入/离开事件给符合条件的视图。 - 同时可以调用
NSCursor.setHiddenUntilMouseMoves(true),确保鼠标位置的变化能被跟踪区域正确捕捉。
2. 优化跟踪区域的配置参数
修改跟踪区域的选项,替换激活策略并补充必要参数:
let tracking = NSTrackingArea(rect: bounds, options: [ .mouseEnteredAndExited, .activeAlways, // 替代.activeInKeyWindow,确保视图在任何激活状态下都能响应 .enabledDuringMouseDrag, .inVisibleRect // 避免视图边界变化导致跟踪区域失效 ], owner: self, userInfo: nil)
如果视图的bounds会动态变化,仍需要在updateTrackingAreas中更新跟踪区域,但不需要每次都移除所有旧区域,可以只更新边界变化的对应区域。
3. macOS 13+的兼容方案
macOS 13之后,系统对拖拽期间的跟踪区域事件派发逻辑做了调整,你可以通过手动监控鼠标位置来替代原生跟踪区域:
- 在拖拽开始后,启动一个
CADisplayLink或者定时器,定期获取NSEvent.mouseLocation,将其转换为目标视图坐标系内的点,判断是否处于视图bounds范围内,以此模拟mouseEntered和mouseExited的触发逻辑。 - 在目标视图中重写
acceptsFirstMouse(_:)方法并返回true,确保视图能优先接收拖拽相关的鼠标事件。
关于强制调用updateTrackingAreas()的说明
你发现的临时方案本质是强制刷新跟踪区域的注册状态,但这并非最佳实践。拖拽期间系统会暂停部分常规事件派发,强制刷新可能带来不必要的性能开销,甚至引发视图布局异常,建议用上述方案替代。
内容的提问来源于stack exchange,提问作者Gannet
相关产品推荐
相关产品推荐

