How do I render a SwiftUI View that is not at the root hierarchy as a UIImage?

后端 未结 2 813
无人及你
无人及你 2020-12-01 09:02

Suppose I have a simple SwiftUI View that is not the ContentView such as this:

struct Test: View {        
    var body: some View {
        VStack {
                


        
相关标签:
2条回答
  • 2020-12-01 09:05

    Here is the approach that works for me, as I needed to get image exactly sized as it is when placed alongside others. Hope it would be helpful for some else.

    Demo: above divider is SwiftUI rendered, below is image (in border to show size)

    enter image description here

    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()
        }
    }
    
    0 讨论(0)
  • 2020-12-01 09:16

    Solution of Asperi works, but if you need image without white background you have to add this line:

    controller.view.backgroundColor = .clear
    

    And your View extension will be:

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题