macOS追踪鼠标NSTrackingArea
macOS追踪鼠标NSTrackingArea

macOS追踪鼠标NSTrackingArea

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的悬停检测,不支持多区域追踪。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注