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

如何在NSView中无交互渲染VZVirtualMachineView生成预览?

实现无交互的VZVirtualMachineView网格预览方案

问题背景

需要基于VZVirtualMachineView制作网格形式的虚拟机预览,但直接使用该视图会出现以下问题:

  • 捕获鼠标光标并隐藏
  • 响应点击、鼠标移动等交互操作

尝试通过vmView.layer?.render(in: context.cgContext)在其他NSView中渲染,但现有代码效果不佳,需要解决:

  1. 如何正确实现无交互的预览效果
  2. 采用何种高效的画面刷新机制
  3. 是否需要将VZVirtualMachineView添加到视图层级
  4. 能否避免生成图片的方式实现(除非该方式高效)

现有尝试代码

class VMPreview: NSView {
    
    let vmView: VZVirtualMachineView
    var timer: Timer?
    
    init(virtualMachine: VZVirtualMachine) {
        self.vmView = .init()
        self.vmView.virtualMachine = virtualMachine
        super.init(frame: .zero)
        addSubview(self.vmView)
        dr()
    }
    
    func dr() {
        self.setNeedsDisplay(bounds)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.dr()
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override var frame: NSRect {
        didSet {
            self.vmView.frame = bounds.offsetBy(dx: 0, dy: bounds.height)
        }
    }
    
    override func draw(_ dirtyRect: NSRect) {
        guard let context = NSGraphicsContext.current else { return }
        vmView.layer?.render(in: context.cgContext)
        self.setNeedsDisplay(bounds)
    }
    
}

解决方案

1. 视图层级与交互屏蔽

VZVirtualMachineView必须保留在视图层级中才能正常渲染虚拟机画面(Virtualization框架依赖视图生命周期更新画面),但需要完全屏蔽其交互:

  • 自定义子类覆盖hitTest,让视图不响应任何点击
  • 禁用用户交互,拦截光标修改操作
  • 将视图移到可视区域外(而非设置hidden=true,避免系统暂停渲染)

2. 高效刷新机制:CADisplayLink替代Timer

用CADisplayLink替代定时任务,它能和屏幕刷新率同步,既保证画面流畅,又避免过度刷新消耗资源。

3. 优化渲染方式:缓存视图画面

使用bitmapImageRepForCachingDisplay缓存VZVirtualMachineView的画面,比直接渲染图层更高效,利用系统缓存机制减少重复绘制。

完整实现代码

import Cocoa
import Virtualization

// 自定义无交互的VZVirtualMachineView子类
class NonInteractiveVZVirtualMachineView: VZVirtualMachineView {
    override func hitTest(_ point: NSPoint) -> NSView? {
        // 完全屏蔽点击响应
        return nil
    }
    
    override func cursorUpdate(with event: NSEvent) {
        // 强制恢复默认光标,阻止视图修改光标
        NSCursor.arrow.set()
    }
}

class VMPreview: NSView {
    private let vmView: NonInteractiveVZVirtualMachineView
    private var displayLink: CADisplayLink?
    
    init(virtualMachine: VZVirtualMachine) {
        self.vmView = NonInteractiveVZVirtualMachineView()
        self.vmView.virtualMachine = virtualMachine
        self.vmView.isUserInteractionEnabled = false
        self.vmView.wantsLayer = true
        
        super.init(frame: .zero)
        self.wantsLayer = true
        
        // 添加到当前视图层级,移到可视区域外避免显示
        addSubview(vmView)
        
        // 初始化画面刷新机制
        setupDisplayLink()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupDisplayLink() {
        displayLink = CADisplayLink(target: self, selector: #selector(updatePreview))
        displayLink?.add(to: .main, forMode: .common)
    }
    
    @objc private func updatePreview() {
        // 标记需要重绘
        setNeedsDisplay(bounds)
    }
    
    override var frame: NSRect {
        didSet {
            // 保持vmView与预览视图同尺寸,移到可视区域外
            vmView.frame = bounds.offsetBy(dx: 0, dy: bounds.height * 2)
        }
    }
    
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        
        guard let context = NSGraphicsContext.current?.cgContext else { return }
        
        // 缓存vmView画面并绘制到当前视图
        if let rep = vmView.bitmapImageRepForCachingDisplay(in: vmView.bounds) {
            vmView.cacheDisplay(in: vmView.bounds, to: rep)
            // 只绘制需要更新的区域,优化性能
            rep.draw(in: dirtyRect,
                     from: vmView.bounds.intersection(dirtyRect.offsetBy(dx: 0, dy: -bounds.height * 2)),
                     operation: .copy,
                     fraction: 1.0)
        }
    }
    
    deinit {
        // 销毁刷新机制,清理视图
        displayLink?.invalidate()
        vmView.removeFromSuperview()
    }
}

关键注意事项

  • 必须保留VZVirtualMachineView在视图层级中,否则虚拟机画面会停止更新
  • 不要设置vmView.hidden = true,部分场景下隐藏视图会被系统暂停渲染
  • 网格预览场景下,可以根据窗口可见性暂停/恢复CADisplayLink,减少资源占用

内容的提问来源于stack exchange,提问作者Damian Dudycz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 03:01:01