Render a SwiftUI view into an image with alpha

I need to render a SwiftUI view into an image with opacity, so that empty space of the view would be transparent when I layer the image above some background.

I use this code for conversion:

func convertViewToData<V>(view: V, size: CGSize)  -> UIImage? where V: View {
    guard let rootVC = UIApplication.shared.windows.first?.rootViewController else {
        return nil
    }
    let imageVC = UIHostingController(rootView: view.edgesIgnoringSafeArea(.all))
    imageVC.view.frame = CGRect(origin: .zero, size: size)
    rootVC.view.insertSubview(imageVC.view, at: 0)
    let uiImage = imageVC.view.asImage(size: size)
    imageVC.view.removeFromSuperview()
    return uiImage
}
extension UIView {
    func asImage(size: CGSize) -> UIImage {
        let format = UIGraphicsImageRendererFormat()
        format.opaque = false
        return UIGraphicsImageRenderer(bounds: bounds, format: format).image { context in
            layer.render(in: context.cgContext)
        }
    }
}
extension View{
    func convertToImage(size: CGSize) -> UIImage?{
        convertViewToData(view: self, size: size)
    }
}

And this code to test the resulting image:

struct ContentView: View {
    var view: some View{
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
        }
    }
    var body: some View {
        HStack{
            view
            Image(uiImage: view.convertToImage(size: .init(width: 200, height: 200))!)
        } 
        .background(LinearGradient(stops: [.init(color: .green, location: 0), .init(color: .red, location: 1)], startPoint: .bottom, endPoint: .top))
    }
}

This code produces two instances of the text: the one on the left is layered on the gradient background, and the one on the right is on the black background.

Clearly, the transparent parts are replaced by the black color in the image.

I figured out that the alpha channel is discarded somewhere in the convertViewToData. Is there any way to make it preserve the alpha channel?

This is what I get. No black background

Clearly, the transparent parts are replaced by the black color in the image. I figured out that the alpha channel is discarded somewhere in the convertViewToData. Is there any way to make it preserve the alpha channel?

What do you expect ? That means that transparent remain transparent, as on the left, isn't it ?

Did you try with

format.opaque = true

This means that you use a white theme, hence white instead of black.

The image on the right should be identical to the one on the left: the coloured gradient should be visible through the transparent parts of the image.

Yes, I tried

format.opaque = true

though, this would make no sense, since I want to work with a non-opaque view.

To get a sense of what I need you could use this function:

extension View{
    @MainActor func render(scale: CGFloat) -> UIImage?{
        let renderer = ImageRenderer(content: self)
        renderer.scale = scale
        return renderer.uiImage
    }
}

And use it like this:

struct ContentView: View {
    @Environment(\.displayScale) var scale
    var view: some View{
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
        }
    }
    var body: some View {
        HStack{
            view
            Image(uiImage: view.render(scale: scale)!)
        } 
        .background(LinearGradient(stops: [.init(color: .green, location: 0), .init(color: .red, location: 1)], startPoint: .bottom, endPoint: .top))
    }
}

This should work perfectly, but only on iOS16. I need to support earlier iOS versions.

Render a SwiftUI view into an image with alpha
 
 
Q