在 SwiftUI 中使用 NSEvent 监听应用级事件(如 ⌘V 剪贴板粘贴),虽然不属于 SwiftUI 的默认机制,但可以通过 AppKit 的 NSEvent监听键盘事件,包括快捷键、剪贴板相关操作等。
SwiftUI使用NSEvent监听代码
1、创建监听器管理类(单例)
import AppKit
import Combine
class KeyboardMonitor: ObservableObject {
static let shared = KeyboardMonitor()
private var monitor: Any?
let pastePublisher = PassthroughSubject<Void, Never>()
func startMonitoring() {
guard monitor == nil else { return } // 防止重复添加
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
if event.modifierFlags.contains(.command),
event.charactersIgnoringModifiers == "v" {
self?.pastePublisher.send()
return nil // 拦截系统 ⌘V,如果你想保留系统粘贴,就 return event
}
return event
}
}
deinit {
if let monitor = monitor {
NSEvent.removeMonitor(monitor)
}
}
}
2、在 SwiftUI 中监听发布器
import SwiftUI
struct PasteListenerView: View {
@State private var pastedText: String = ""
@State private var pastedImage: NSImage?
var body: some View {
VStack(spacing: 20) {
Text("按下 ⌘V 来粘贴内容")
if let image = pastedImage {
Image(nsImage: image)
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
} else {
Text("粘贴内容:\(pastedText)")
}
}
.padding()
.onAppear {
KeyboardMonitor.shared.startMonitoring()
}
.onReceive(KeyboardMonitor.shared.pastePublisher) { _ in
let pb = NSPasteboard.general
if let imageData = pb.data(forType: .tiff),
let image = NSImage(data: imageData) {
pastedImage = image
} else if let str = pb.string(forType: .string) {
pastedText = str
} else {
pastedText = "剪贴板中无可识别内容"
pastedImage = nil
}
}
}
}
当用户按下 ⌘V:
如果剪贴板有图片(如在 Finder 复制了一个图片文件或从浏览器复制图片),会展示图片;
如果剪贴板是文字(如网页文本),会显示为字符串;
所有逻辑都运行在 SwiftUI,使用主线程安全更新。
监听器管理类代码解析
1、单例模式KeyboardMonitor
class KeyboardMonitor: ObservableObject {
static let shared = KeyboardMonitor()
保证应用只创建一次监听器
2、保存NSEvent返回的监听器对象
private var monitor: Any?
添加监听器时 NSEvent.addLocalMonitor… 会返回一个值;最后可以使用这个变量移除监听器(在 deinit 中)。
3、Combine发布者
let pastePublisher = PassthroughSubject<Void, Never>()
这是 Combine 框架中的一个“发布者”;任何人都可以通过 .sink 或 .onReceive 来订阅它;当监听到 ⌘V 时调用 .send(),从而通知订阅者。
4、startMonitoring方法
guard monitor == nil else { return }
防止重复添加监听器。
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
使用NSEvent的addLocalMonitorForEvents方法,监听应用内的键盘事件,只处理 .keyDown 类型,使用使用 [weak self] 防止闭包导致内存泄漏。
if event.modifierFlags.contains(.command),
event.charactersIgnoringModifiers == "v" {
self?.pastePublisher.send()
return nil
}
判断是否是 Command + V,modifierFlags.contains(.command) 检查是否按了 ⌘;charactersIgnoringModifiers == “v” 检查实际按的是字母 v。
如果是,就 .send() 粘贴事件;return nil 表示拦截系统默认的粘贴行为。
也可以改成 return event,这样就不会拦截。
5、取消初始化
if let monitor = monitor {
NSEvent.removeMonitor(monitor)
}
当 KeyboardMonitor 被释放时自动移除监听器;
这是资源清理的好习惯,防止监听器残留导致潜在 bug。
SwiftUI代码解析
1、初始化键盘事件监听器
.onAppear {
KeyboardMonitor.shared.startMonitoring()
}
在 .onAppear 中触发,初始化键盘事件监听器。
这一步也可以移到入口文件中:
@main
struct ImageSlimApp: App {
init() {
KeyboardMonitor.shared.startMonitoring()
}
var body: some Scene {
// 空 Scene,窗口由 AppDelegate 管理
Settings {} // 占位,不弹出任何窗口
}
}
2、.onReceive(…) 监听事件流
.onReceive(KeyboardMonitor.shared.pastePublisher) { _ in
// 从剪贴板读取数据
}
一旦监听器发出事件,SwiftUI 会自动响应并执行粘贴逻辑。
3、读取剪贴板内容
let pb = NSPasteboard.general
if let imageData = pb.data(forType: .tiff),
let image = NSImage(data: imageData) {
pastedImage = image
} else if let str = pb.string(forType: .string) {
pastedText = str
}
优先读取图像(.tiff 是 macOS 图像通用格式),如果没有图像,再读取纯文本,最后 fallback 到提示文本。
NSEvent监听剪贴板时发挥的作用
虽然 PasteListenerView 中没有直接用 NSEvent,但背后的监听逻辑是:
1、KeyboardMonitor 管理类封装了 NSEvent 的监听行为;
2、一旦 ⌘V 被按下,NSEvent 的回调会触发 .send();
3、PassthroughSubject 发出信号;
4、SwiftUI View .onReceive(…) 接收到事件并响应。
相关文章
1、macOS剪贴板NSPasteboard:https://fangjunyu.com/2025/07/15/macos%e5%89%aa%e8%b4%b4%e6%9d%bfnspasteboard/
2、macOS用户输入事件NSEvent:https://fangjunyu.com/2025/07/04/macos%e7%94%a8%e6%88%b7%e8%be%93%e5%85%a5%e4%ba%8b%e4%bb%b6nsevent/