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

自定义缩放比例下UIView含SCNView的高清截图异常问题

自定义缩放截图含SCNView的UIView且保持高清

问题背景

我有一个包含SCNView子视图的UIView,需要实现自定义缩放比例的截图,且不能改变原有UI的布局显示。

  • 若通过修改布局约束手动放大UIView后调用常规截图方法,SCNView能呈现高清效果,对应代码如下:
extension UIView {
    func captureScreenshot() -> UIImage? {
    
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale)
        drawHierarchy(in: self.bounds, afterScreenUpdates: true)
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
            UIGraphicsEndImageContext()
            return nil
        }
        UIGraphicsEndImageContext()
        
        return image
    }
}
  • 但尝试通过递归设置contentScaleFactor实现无UI变更的自定义缩放截图时,SCNView无法保持高清,代码如下:
extension UIView {
    
    func scale(by scale: CGFloat) {
        self.contentScaleFactor = scale
        for subview in self.subviews {
            subview.scale(by: scale)
        }
    }
    
    func takeScreenshot(with scale: CGFloat? = nil) -> UIImage {
        let newScale = scale ?? UIScreen.main.scale
        self.scale(by: newScale)
        let format = UIGraphicsImageRendererFormat()
        format.scale = newScale
        let renderer = UIGraphicsImageRenderer(size: self.bounds.size, format: format)
        return renderer.image { _ in
            self.drawHierarchy(in: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height), afterScreenUpdates: true)
        }
    }

}

调用方式:

let image = myView.takeScreenshot(with: 5.0)

问题原因

SCNView的渲染逻辑和普通UIView存在差异:

  • 普通UIView设置contentScaleFactor可以影响绘制分辨率,但SCNView的3D内容渲染依赖自身渲染循环、场景参数和视图尺寸,仅修改contentScaleFactor不会触发它按新比例重新渲染内容。
  • 手动修改布局约束放大视图时,SCNView的bounds发生变化,会触发重新渲染流程,生成对应高分辨率的3D内容,因此截图清晰。

解决方案

通过创建临时视图容器模拟缩放后的状态,为SCNView单独创建副本并配置对应参数,确保其渲染出高分辨率内容,最后对临时容器截图——既不修改原UI,又能得到高清截图。

实现代码

extension UIView {
    func takeScreenshot(with scale: CGFloat? = nil) -> UIImage? {
        let targetScale = scale ?? UIScreen.main.scale
        let targetSize = CGSize(width: bounds.width * targetScale, height: bounds.height * targetScale)
        
        // 创建临时容器,承载缩放后的所有视图
        let tempContainer = UIView(frame: CGRect(origin: .zero, size: targetSize))
        tempContainer.backgroundColor = backgroundColor
        
        for subview in subviews {
            if let scnView = subview as? SCNView {
                // 为SCNView创建临时副本,配置缩放后的尺寸和原场景参数
                let tempSCNFrame = CGRect(
                    x: subview.frame.origin.x * targetScale,
                    y: subview.frame.origin.y * targetScale,
                    width: subview.frame.width * targetScale,
                    height: subview.frame.height * targetScale
                )
                let tempSCNView = SCNView(frame: tempSCNFrame)
                tempSCNView.scene = scnView.scene
                tempSCNView.pointOfView = scnView.pointOfView
                tempSCNView.backgroundColor = scnView.backgroundColor
                tempSCNView.contentScaleFactor = targetScale
                // 强制触发一次渲染,确保3D内容生成
                tempSCNView.renderIfNeeded()
                
                tempContainer.addSubview(tempSCNView)
            } else {
                // 普通子视图先截图,再缩放添加到临时容器
                guard let subSnapshot = subview.captureScreenshot() else { continue }
                let scaledImageView = UIImageView(image: subSnapshot)
                scaledImageView.frame = CGRect(
                    x: subview.frame.origin.x * targetScale,
                    y: subview.frame.origin.y * targetScale,
                    width: subview.frame.width * targetScale,
                    height: subview.frame.height * targetScale
                )
                scaledImageView.contentMode = .scaleToFill
                
                tempContainer.addSubview(scaledImageView)
            }
        }
        
        // 对临时容器进行截图,这里scale设为1,因为容器已经是缩放后的实际尺寸
        let format = UIGraphicsImageRendererFormat()
        format.scale = 1.0
        let renderer = UIGraphicsImageRenderer(size: targetSize, format: format)
        let finalImage = renderer.image { context in
            tempContainer.drawHierarchy(in: tempContainer.bounds, afterScreenUpdates: true)
        }
        
        return finalImage
    }
    
    // 保留原有的基础截图方法
    func captureScreenshot() -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale)
        drawHierarchy(in: self.bounds, afterScreenUpdates: true)
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
            UIGraphicsEndImageContext()
            return nil
        }
        UIGraphicsEndImageContext()
        return image
    }
}

调用方式

if let highResImage = myView.takeScreenshot(with: 5.0) {
    // 使用高清截图
}

说明

  • 该方法不会修改原视图的任何布局、约束或属性,完全基于临时视图完成截图流程。
  • 针对SCNView单独处理,确保其按照目标缩放比例渲染3D内容,保证截图清晰度。
  • 普通子视图通过先截图再缩放的方式,保持原有显示效果的同时适配缩放尺寸。

内容的提问来源于stack exchange,提问作者Hassan Taleb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:45:55