【问题标题】:How do I render a SwiftUI View that is not at the root hierarchy as a UIImage?如何将不在根层次结构中的 SwiftUI 视图呈现为 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 视图。

【问题讨论】:

标签: uikit swiftui


【解决方案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() 将呈现该颜色。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-05
相关资源
最近更新 更多