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

use*_*955 12 uikit swiftui

假设我有一个简单的 SwiftUI 视图,它不是像这样的 ContentView:

struct Test: View {        
    var body: some View {
        VStack {
            Text("Test 1")
            Text("Test 2")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如何将此视图呈现为 UIImage?

我研究了解决方案,例如:

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Asp*_*eri 16

这是对我有用的方法,因为我需要将图像与其他图像放在一起时的大小完全相同。希望对其他人有帮助。

演示:上面的分隔线是 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Asperi显然,如果“TestableView”包含使用“@Binding”传递给它的动态数据,它就不起作用。在 UIView 扩展的 `layer.render` 行中获取 `Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)`。 (2认同)
  • 我能够通过用“DispatchQueue.main.async {}”包装“self.layer.render(in: rendererContext.cgContext)”来防止它崩溃 (2认同)
  • 我使用 DispatchQueue.main.async { [weak self] in layer.render } 得到空图像,但没有正确的图像 (2认同)