NSTrackingArea 是 macOS 的 AppKit 框架中的一个类,用于追踪鼠标在指定视图区域中的活动,例如进入、移出、移动等事件。它的功能是告诉系统:“请在这个区域内,告诉我鼠标有没有进来、有没有出去、有没有在里面移动。”
主要用途
1、追踪鼠标进入(mouseEntered);
2、追踪鼠标移出(mouseExited);
3、追踪鼠标在区域内移动(mouseMoved);
4、实现悬停效果(如按钮高亮、提示框);
5、用于绘图更新或动画触发;
基本用法
let trackingArea = NSTrackingArea(
rect: someRect,
options: [.mouseEnteredAndExited, .activeInKeyWindow],
owner: self,
userInfo: nil
)
view.addTrackingArea(trackingArea)
1、rect: 要追踪的区域(在 view 的坐标系中)。
常见设置方式:
self.bounds:追踪整个视图;
self.visibleRect:只追踪当前可见区域;
若配合 .inVisibleRect option,此参数可设置为任意值(甚至为 zero),系统会自动使用当前 visible rect。
2、options: 追踪行为选项,NSTrackingArea.Options 的集合(类型为 OptionSet),支持多个选项组合。
.mouseEnteredAndExited:追踪鼠标进入/离开 rect 区域;
.mouseMoved:鼠标在区域中移动时触发 mouseMoved(with:)(需启用窗口的 acceptsMouseMovedEvents = true);
.cursorUpdate:鼠标进入区域时调用 cursorUpdate(with:)(常用于设置光标);
.activeWhenFirstResponder:只有当 owner 是第一响应者时才有效;
.activeInKeyWindow:当窗口是 key window 时生效(最常用);
.activeInActiveApp:当应用激活时生效(窗口不必是 key);
.activeAlways:总是生效,无论窗口/应用是否活跃(适合浮动工具类窗口);
.inVisibleRect:自动将 rect 限定为 view 的可见区域(比如滚动视图中),避免手动更新区域;
.assumeInside:创建 trackingArea 时,系统假设鼠标已经在区域内(会立即触发 mouseEntered(with:));
.enabledDuringMouseDrag:鼠标拖拽时依然触发事件(默认拖拽不触发 mouseEntered 等)。
推荐组合示例(常用):
options: [.mouseEnteredAndExited, .mouseMoved, .activeInKeyWindow]
options: [.mouseEnteredAndExited, .inVisibleRect, .activeAlways]
options: [.mouseMoved, .cursorUpdate, .activeInKeyWindow]
3、owner: 当事件触发时调用谁的 mouseEntered、mouseExited 等,通常是 NSView 子类的 self。
owner: self // 让当前视图接收事件
也可以设置为其他对象,只要它实现了相关方法(例如 view controller 或控制类)。
4、userInfo: 可选的字典,传递的任意附加信息,例如标记这个区域的类型、ID、描述等。
userInfo: ["type": "screenshotRegion", "id": 123]
可以在事件方法中访问:
override func mouseEntered(with event: NSEvent) {
if let info = event.trackingArea?.userInfo as? [String: Any],
let type = info["type"] as? String {
print("鼠标进入区域类型:\(type)")
}
}
5、addTrackingArea(_:),用于注册一个 NSTrackingArea 到视图中,让该视图开始追踪鼠标事件(如鼠标进入、移出、移动等)。
func addTrackingArea(_ trackingArea: NSTrackingArea)
使用方法:
let trackingArea = NSTrackingArea(
rect: someRect,
options: [.mouseEnteredAndExited, .activeInKeyWindow],
owner: self,
userInfo: nil
)
self.addTrackingArea(trackingArea)
一旦调用 addTrackingArea,系统就会开始追踪区域内的鼠标活动。
使用场景
1、悬停变色:
class HoverView: NSView {
var trackingArea: NSTrackingArea?
override func updateTrackingAreas() {
super.updateTrackingAreas()
if let trackingArea = trackingArea {
removeTrackingArea(trackingArea)
}
let options: NSTrackingArea.Options = [
.mouseEnteredAndExited,
.activeInKeyWindow,
.inVisibleRect
]
trackingArea = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: ["hover": true])
addTrackingArea(trackingArea!)
}
override func mouseEntered(with event: NSEvent) {
self.layer?.backgroundColor = NSColor.red.cgColor
}
override func mouseExited(with event: NSEvent) {
self.layer?.backgroundColor = NSColor.clear.cgColor
}
}
2、根据视图大小或移动调整addTrackingArea:
override func updateTrackingAreas() {
super.updateTrackingAreas()
if let trackingArea = self.trackingArea {
self.removeTrackingArea(trackingArea)
}
let newArea = NSTrackingArea(
rect: self.bounds,
options: [.mouseEnteredAndExited, .activeInKeyWindow],
owner: self,
userInfo: nil
)
self.addTrackingArea(newArea)
self.trackingArea = newArea
}
注意事项
1、如果视图尺寸或可见区域变化,要更新 tracking area(通常在 updateTrackingAreas() 中处理)。
2、添加 trackingArea 后必须确保不泄漏内存,适时移除。
3、mouseMoved 要求 acceptsMouseMovedEvents = true(窗口层级设置)。
4、当视图调整大小或移动时,应该在updateTrackingAreas() 方法中重新添加 tracking area。如果没有调用 addTrackingArea(…),系统是不会追踪视图区域,哪怕创建了 NSTrackingArea 对象。
5、在实际应用中,通常配合 removeTrackingArea(_:) 移除已经添加的tracking area,避免重复追踪或内存泄漏,通常和 addTrackingArea 成对使用。
总结
NSTrackingArea 用于追踪鼠标在指定视图区域中的活动,例如进入、移出、移动等事件。
NSTrackingArea是 AppKit(基于 NSView)世界的工具, SwiftUI无法直接实现NSTrackingArea的支持,但是可以通过NSView嵌入SwiftUI。
在SwiftUI的简单场景下,onHover可以替代NSTrackingArea的鼠标追踪功能。
相关文章
1、macOS视图NSView:https://fangjunyu.com/2025/07/01/macos%e8%a7%86%e5%9b%bensview/
2、SwiftUI显示AppKit视图的NSViewRepresentable协议:https://fangjunyu.com/2025/07/02/swiftui%e6%98%be%e7%a4%baappkit%e8%a7%86%e5%9b%be%e7%9a%84nsviewrepresentable%e5%8d%8f%e8%ae%ae/
3、SwiftUI鼠标悬停onHover:https://fangjunyu.com/2025/07/09/swiftui%e9%bc%a0%e6%a0%87%e6%82%ac%e5%81%9conhover/
扩展知识
在SwiftUI中使用NSTrackingArea
1、创建NSView子类,并添加NSTrackingArea
import AppKit
class HoverTrackingView: NSView {
var onMouseEnter: (() -> Void)?
var onMouseExit: (() -> Void)?
private var trackingArea: NSTrackingArea?
override func updateTrackingAreas() {
super.updateTrackingAreas()
if let trackingArea = trackingArea {
removeTrackingArea(trackingArea)
}
trackingArea = NSTrackingArea(
rect: self.bounds,
options: [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect],
owner: self,
userInfo: nil
)
addTrackingArea(trackingArea!)
}
override func mouseEntered(with event: NSEvent) {
onMouseEnter?()
}
override func mouseExited(with event: NSEvent) {
onMouseExit?()
}
}
2、使用NSViewRepresentable 将NSView嵌入SwiftUI
import SwiftUI
struct TrackingView: NSViewRepresentable {
var onHoverChanged: (Bool) -> Void
func makeNSView(context: Context) -> HoverTrackingView {
let view = HoverTrackingView()
view.onMouseEnter = {
onHoverChanged(true)
}
view.onMouseExit = {
onHoverChanged(false)
}
return view
}
func updateNSView(_ nsView: HoverTrackingView, context: Context) {}
}
3、在SwiftUI中使用:
struct ContentView: View {
@State private var isHovering = false
var body: some View {
ZStack {
Text(isHovering ? "鼠标悬停中" : "移开了")
.padding()
.background(isHovering ? Color.red : Color.blue)
.cornerRadius(8)
// 透明的追踪区域
TrackingView { hovering in
isHovering = hovering
}
.allowsHitTesting(false) // 不拦截点击
}
.frame(width: 200, height: 100)
}
}
当鼠标移入TrackingView时,变成红色:

当鼠标移出TrackingView时,变成蓝色:

和onHover相比,NSTrackingArea可以设置任意区域,检测进入、离开、移动、光标、拖动等行为,支持多个追踪区域、动态更新。
onHover支持整个View的悬停检测,不支持多区域追踪。