You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 22:32:20