如何在macOS的SwiftUI中于桌面/设备上下文绘制线条
在macOS桌面顶层绘制不影响交互的线条(SwiftUI/AppKit方案)
为什么NSGraphicsContext.current无法实现需求
NSGraphicsContext.current仅能在当前App的视图绘制上下文中生效,无法直接操作系统桌面窗口的绘图上下文- 默认App窗口处于普通层级,既无法覆盖在桌面之上,还会拦截鼠标交互,完全不符合你的需求
解决方案:创建透明穿透式覆盖窗口
macOS的桌面本身是一个系统窗口,要在其顶层绘制且不影响交互,需创建一个全屏透明、忽略鼠标事件、层级高于桌面的自定义窗口,再在窗口内绘制线条。
步骤1:实现自定义Overlay窗口
创建NSWindow子类,配置核心属性:
import AppKit import SwiftUI class DesktopOverlayWindow: NSWindow { init() { guard let mainScreen = NSScreen.main else { fatalError("无法获取主屏幕") } let screenFrame = mainScreen.frame super.init( contentRect: screenFrame, styleMask: .borderless, // 无边框,消除窗口本身的视觉干扰 backing: .buffered, defer: false ) // 核心配置 self.level = .init(CGWindowLevelForKey(.desktopWindowLevelKey) + 1) // 层级高于桌面窗口 self.backgroundColor = .clear // 透明背景,仅显示绘制内容 self.ignoresMouseEvents = true // 忽略鼠标事件,让操作完全穿透到下层窗口/桌面 self.hasShadow = false self.isOpaque = false self.collectionBehavior = .canJoinAllSpaces // 让线条在所有桌面空间显示 } }
步骤2:实现绘制内容
提供两种绘制方式,可根据习惯选择:
方式A:SwiftUI绘制
用SwiftUI的Path快速实现线条:
struct OverlayDrawingView: View { var body: some View { // 绘制一条从(100,100)到(500,500)的红色粗线条 Path { path in path.move(to: CGPoint(x: 100, y: 100)) path.addLine(to: CGPoint(x: 500, y: 500)) } .stroke(Color.red, lineWidth: 3) .ignoresSafeArea() } }
方式B:Core Graphics绘制(贴近WinAPI GDI逻辑)
如果你更习惯CGContext的操作,可创建NSView子类:
class CGDrawingView: NSView { override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) guard let context = NSGraphicsContext.current?.cgContext else { return } // 配置线条样式 context.setStrokeColor(NSColor.red.cgColor) context.setLineWidth(3) context.setLineCap(.round) // 可选:设置线条端点为圆角 // 绘制线条,逻辑和GDI高度相似 context.move(to: CGPoint(x: 100, y: 100)) context.addLine(to: CGPoint(x: 500, y: 500)) context.strokePath() } }
步骤3:在App中启动覆盖窗口
在SwiftUI App中通过NSApplicationDelegate初始化并显示窗口:
@main struct DesktopLineOverlayApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { Settings { EmptyView() // 空设置界面,可按需自定义 } } } class AppDelegate: NSObject, NSApplicationDelegate { private var overlayWindow: DesktopOverlayWindow? func applicationDidFinishLaunching(_ notification: Notification) { overlayWindow = DesktopOverlayWindow() // 二选一:使用SwiftUI视图或Core Graphics视图 // overlayWindow?.contentView = NSHostingView(rootView: OverlayDrawingView()) overlayWindow?.contentView = CGDrawingView() overlayWindow?.makeKeyAndOrderFront(nil) } }
关键注意事项
- 沙盒设置:如果开启App沙盒,需在
Info.plist中添加对应权限,测试阶段建议直接关闭沙盒避免限制 - 层级调整:若需要线条显示在所有窗口之上,可将层级改为
CGWindowLevelForKey(.mainMenuWindowLevelKey) + 1 - 多屏幕支持:上述代码仅适配主屏幕,如需支持多屏幕,需遍历所有
NSScreen创建对应窗口
内容的提问来源于stack exchange,提问作者Khang
相关产品推荐
相关产品推荐

