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
相关产品推荐
相关产品推荐

