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

Swift macOS开发:修改坐标后NSRect无法在新位置绘制如何解决

问题根源

你的代码有几个违反AppKit绘制规则的错误,直接导致矩形位置无法正常更新:

  • 错误在draw(_:)方法内调用setNeedsDisplay(_:)。这个方法的作用是标记区域待重绘、触发后续绘制流程,在正在执行的绘制周期里调用它会打乱系统的绘制调度,甚至触发无意义的重复重绘,把你刚画好的镂空区域重新用白色覆盖。
  • 把视图状态更新逻辑(修改myRect坐标)放在了draw(_:)里。draw(_:)是纯绘制方法,会被系统在各种时机自动调用,绝对不要在这里修改视图属性、调整业务状态,否则会出现状态和绘制不同步的问题。
  • 本地键盘事件监视器没有保存强引用,注册后会被系统自动回收,后续按键事件无法稳定触发。
  • 两处窗口背景色设置冲突:你在viewDidLoad里把窗口背景设为白色,会覆盖AppDelegate里设置的透明背景,直接导致镂空效果失效。
修正方案

1. 重写MyView类

将坐标属性和矩形的绑定逻辑移到属性监听器里,属性变化时自动标记新旧矩形区域待重绘,draw(_:)方法只保留纯绘制逻辑,不做任何状态修改、不触发重绘请求:

class MyView: NSView {
    var xloc = 0.0 {
        didSet { updateRect() }
    }
    var yloc = 0.0 {
        didSet { updateRect() }
    }
    var width = 400.0 {
        didSet { updateRect() }
    }
    var height = 200.0 {
        didSet { updateRect() }
    }
    private(set) var myRect = NSRect(x: 0.0, y: 0.0, width: 400.0, height: 200.0)
    
    private func updateRect() {
        // 先标记旧矩形区域需要重绘(用于覆盖旧的镂空痕迹)
        setNeedsDisplay(myRect)
        // 更新矩形坐标
        myRect = NSRect(x: xloc, y: yloc, width: width, height: height)
        // 标记新矩形区域需要重绘(用于绘制新的镂空)
        setNeedsDisplay(myRect)
    }
    
    override func draw(_ dirtyRect: NSRect) {
        // 填充指定区域为白色,覆盖旧的绘制内容
        NSColor.white.setFill()
        dirtyRect.fill()
        // 对当前矩形区域做镂空
        myRect.fill(using: .clear)
    }
}

2. 修正视图控制器代码

保存键盘事件监视器的强引用,移除多余的手动display()调用(属性变化时已经自动标记了待重绘区域,系统会自动触发绘制),删除冲突的窗口背景色设置:

class ViewController: NSViewController {
    // 强引用事件监视器,防止被系统回收
    private var keyEventMonitor: Any?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 移除原来的view.window?.backgroundColor设置,避免覆盖AppDelegate里的透明配置
        
        keyEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
            self?.keyDown(with: event)
            return event
        }
    }
    
    override func keyDown(with event: NSEvent) {
        guard let theView = self.view as? MyView else { return }
        // 上方向键keyCode为126
        if event.keyCode == 126 {
            NSLog("Increasing Y coordinate")
            theView.yloc += 10
            // 不需要手动调用display,属性的didSet已经自动标记了重绘区域
        }
    }
    
    deinit {
        // 页面销毁时移除事件监听,避免内存泄漏
        if let monitor = keyEventMonitor {
            NSEvent.removeMonitor(monitor)
        }
    }
}
额外注意事项
  • 你在AppDelegate里手动创建MyView设为窗口的contentView时,不需要手动设置frame,contentView会自动跟随窗口大小调整。
  • ignoresMouseEvents = true会让窗口完全不响应鼠标操作,如果后续需要交互记得移除这个配置。
  • 永远记住原则:draw(_:)只做绘制,所有状态修改、重绘请求触发都要放在方法外面。

内容的提问来源于stack exchange,提问作者Suchindra Chandrahas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:57:25