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?