Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

uikit

swiftui

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

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

How can I render this view as a UIImage?

I've looked into solutions such as :

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

But it seems that solutions like that only work on UIView, not a SwiftUI View.

like image 601
user12533955 Avatar asked Dec 13 '19 22:12

user12533955


People also ask

How to convert SwiftUI view to image?

SwiftUI's views don't have a built-in function to render a view as an image, but we can write one ourselves in very little time. The key here is to wrap the view using UIHostingController , then render its view hierarchy into a UIGraphicsImageRenderer .

How do I take a screenshot in SwiftUI view?

First, we will convert any view into the UIImage then we will extend the same logic to screenshot the entire Scroll view content. Create an extension on SwiftUI's view, which takes the position of part of the view we need to convert into an image. We will create a dummy UIHostingViewController with the given frame.

What is a view SwiftUI?

The SwiftUI framework defines a broad range of primitive views. Text , for example, doesn't return a view. It draws the string it is given to the screen. Those primitive views are the building blocks you can use in the custom views you create to build your application's user interface.


1 Answers

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()
    }
}
like image 118
Asperi Avatar answered Oct 11 '22 20:10

Asperi