如何将不在根层次结构中的 SwiftUI 视图呈现为 UIImage?

Posted

技术标签:

【中文标题】如何将不在根层次结构中的 SwiftUI 视图呈现为 UIImage?【英文标题】:How do I render a SwiftUI View that is not at the root hierarchy as a UIImage? 【发布时间】:2019-12-13 22:58:39 【问题描述】:

假设我有一个不是 ContentView 的简单 SwiftUI 视图,例如:

struct Test: View         
    var body: some View 
        VStack 
            Text("Test 1")
            Text("Test 2")
        
    

如何将此视图呈现为 UIImage?

我已经研究过以下解决方案:

extension UIView 
    func asImage() -> UIImage 
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image  rendererContext in
            layer.render(in: rendererContext.cgContext)
        
    

但似乎这样的解决方案只适用于 UIView,而不适用于 SwiftUI 视图。

【问题讨论】:

ericasadun.com/2019/06/20/… 【参考方案1】:

这是适合我的方法,因为我需要让图像与其他图像并排放置时的大小完全相同。希望对其他人有所帮助。

演示:上面的分隔线是 SwiftUI 渲染的,下面是图片(在边框中显示大小)

extension View 
    func asImage() -> UIImage 
        let controller = UIHostingController(rootView: self)

        // locate far out of screen
        controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
        UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)

        let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
        controller.view.bounds = CGRect(origin: .zero, size: size)
        controller.view.sizeToFit()

        let image = controller.view.asImage()
        controller.view.removeFromSuperview()
        return image
    


extension UIView 
    func asImage() -> UIImage 
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image  rendererContext in
// [!!] Uncomment to clip resulting image
//             rendererContext.cgContext.addPath(
//                UIBezierPath(roundedRect: bounds, cornerRadius: 20).cgPath)
//            rendererContext.cgContext.clip()

// As commented by @MaxIsom below in some cases might be needed
// to make this asynchronously, so uncomment below DispatchQueue
// if you'd same met crash
//            DispatchQueue.main.async 
                 layer.render(in: rendererContext.cgContext)
//            
        
    



// TESTING
struct TestableView: View 
    var body: some View 
        VStack 
            Text("Test 1")
            Text("Test 2")
        
    


struct TestBackgroundRendering: View 
    var body: some View 
        VStack 
            TestableView()
            Divider()
            Image(uiImage: render())
                .border(Color.black)
        
    
    
    private func render() -> UIImage 
        TestableView().asImage()
    


struct TestBackgroundRendering_Previews: PreviewProvider 
    static var previews: some View 
        TestBackgroundRendering()
    

【讨论】:

有效!现在,如果我需要专门在我的视图中使用 UIImage 类型而不是 Image 那可能吗?由于在此处的代码中我们有Image(uiImage: render()),因此是否存在与渲染等效的 UIImage 表达式?我们甚至可以在 SwiftUI 视图中使用 UIImage 吗? @Asperi 显然,如果TestableView 包含使用@Binding 传递给它的动态数据,则它不起作用。在 UIView 扩展的layer.render 行中获取Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) 我在动态数据方面遇到了同样的问题。 @AnaghSharma 你找到解决方法了吗? 我可以通过用self.layer.render(in: rendererContext.cgContext) 包裹DispatchQueue.main.async 来防止它崩溃 我使用 DispatchQueue.main.async [weak self] in layer.render 得到空图像,但没有正确图像【参考方案2】:

Asperi 的解决方案有效,但是如果您需要没有白色背景的图像,则必须添加以下行:

controller.view.backgroundColor = .clear

您的视图扩展将是:

extension View 
    func asImage() -> UIImage 
        let controller = UIHostingController(rootView: self)
        
        // locate far out of screen
        controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
        UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
        
        let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
        controller.view.bounds = CGRect(origin: .zero, size: size)
        controller.view.sizeToFit()
        controller.view.backgroundColor = .clear
        let image = controller.view.asImage()
        controller.view.removeFromSuperview()
        return image
    

【讨论】:

如果将修饰符作为 .backgroundColor() 添加到 TestableView() 将呈现该颜色。

以上是关于如何将不在根层次结构中的 SwiftUI 视图呈现为 UIImage?的主要内容,如果未能解决你的问题,请参考以下文章

如何解决此错误“警告:尝试呈现其视图不在窗口层次结构中”?

尝试呈现其视图不在 Windows 层次结构中的 ViewController

尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController

尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController

尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController

尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController