36

How I can convert a SwiftUI Image to a UIImage?

let image = Image(systemName: "circle.fill")
let UIImage = image as UIImage
pkamb
  • 33,281
  • 23
  • 160
  • 191
nOk
  • 2,725
  • 4
  • 13
  • 26
  • hi @nOK , why you need that? and what you want to achieve? – Giuseppe Sapienza Jul 14 '19 at 15:13
  • `UIImage` is part of `UIKit`, not `SwiftUI`. What *exactly are you trying to do? without SwiftUI, you can easily - via things very documented - *convert* between `UIImage, `CGImage`, and `CIImage`. If you consider that `Image` is *much* more like a `UIImageView`, again, what are you really looking for? –  Jul 14 '19 at 15:24
  • 1
    I was trying to save an image in CoreData and with UIImage I could easily just use image.pngData() but haven't found a possibility for images in swiftUI – nOk Jul 14 '19 at 18:07
  • Also variables of type "Image" don't seem to be conforming to the "Hashable" prototype – nOk Jul 14 '19 at 18:54

5 Answers5

31

There is no direct way of converting an Image to UIImage. instead, you should treat the Image as a View, and try to convert that View to a UIImage. Image conforms to View, so we already have the View we need. now we just need to convert that View to a UIImage.

We need 2 components to achieve this. First, a function to change our Image/View to a UIView, and second one, to change the UIView we created to UIImage.

For more Convenience, both functions are declared as Extensions to their appropriate types.

extension View {
// This function changes our View to UIView, then calls another function
// to convert the newly-made UIView to a UIImage.
    public func asUIImage() -> UIImage {
        let controller = UIHostingController(rootView: self)
        
 // Set the background to be transparent incase the image is a PNG, WebP or (Static) GIF
        controller.view.backgroundColor = .clear 
        
        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()
        
// here is the call to the function that converts UIView to UIImage: `.asUIImage()`
        let image = controller.view.asUIImage()
        controller.view.removeFromSuperview()
        return image
    }
}

extension UIView {
// This is the function to convert UIView to UIImage
    public func asUIImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

How To Use?

let image: Image = Image("MyImageName") // Create an Image anyhow you want
let uiImage: UIImage = image.asUIImage() // Works Perfectly

Bonus

As i said, we are treating the Image, as a View. In the process, we don't use any specific features of Image, the only thing that is important is that our Image is a View (conforms to View protocol). This means that with this method, you can not only convert an Image to a UIImage, but also you can convert any View to a UIImage.

var myView: some View {
// create the view here
}
let uiImage = myView.asUIImage() // Works Perfectly
Sethu Senthil
  • 169
  • 2
  • 12
Mahdi BM
  • 1,826
  • 13
  • 16
  • 1
    How should I change the line `UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)` for iOS 15? I get the deprecation warning: `"'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead"`. – Rillieux Nov 28 '21 at 18:46
  • @Rillieux - Like this. Of course you could guard, etc. as well. But simply: let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene scene?.windows.first?.rootViewController?.view.addSubview(controller.view) – C6Silver May 18 '22 at 21:46
26

Such thing is not possible with SwiftUI, and I bet it will never be. It goes against the whole framework concept. However, you can go the opposite direction:

let uiImage = UIImage(systemName: "circle.fill")
let image = Image(uiImage: uiImage)
joshuakcockrell
  • 5,200
  • 2
  • 34
  • 47
kontiki
  • 37,663
  • 13
  • 111
  • 125
  • Variables of type "Image" don't seem to be conforming to the "Hashable" prototype that's why I was hoping to be able to convert it :/ – nOk Jul 14 '19 at 18:55
  • 7
    If you would like to get the data from an `Image` view, you do not get it from the `Image` itself, but from the same place the Image got it. For example, if you load the Image using a uiImage object, you should go to that uiImage object. If the view got the image from a file name, you should go get it from the file. You will **never** be able to interrogate Image for its binary, it is not the way the framework is designed. – kontiki Jul 14 '19 at 20:06
  • Thx for the help. I was kinda trying to avoid that but thought it might come to that :D Let's rewrite the code then :D – nOk Jul 14 '19 at 20:41
  • 3
    @kontiki Why it goes againts the whole framework concept? – villb Oct 11 '21 at 11:20
3

Try to add extension on View like so:

extension Image {
    @MainActor
    func getUIImage(newSize: CGSize) -> UIImage? {
        let image = resizable()
            .scaledToFill()
            .frame(width: newSize.width, height: newSize.height)
            .clipped()
        return ImageRenderer(content: image).uiImage
    }
}

And your use case will be

    let image = Image("someFromAssets")
    let size = CGSize(width: 100, height: 100)
    let uiImage = image.getUIImage(newSize: size)

Serg Basin
  • 31
  • 4
2

As of iOS 16 and MacOS 13, most answers above are out of date. The answer now is to use ImageRenderer, which is designed to transform Views -- including Image -- to both UIImage and CGImage.

The main caveat is that ImageRenderer wants to run in the main thread, so you'll need to use GCD to get the image converted.

public func convert(image: Image, callback: @escaping ((UIImage?) -> Void)) {
    DispatchQueue.main.async {
        let renderer = ImageRenderer(content: image)

        // to adjust the size, you can use this (or set a frame to get precise output size)
        // renderer.scale = 0.25
        
        // for CGImage use renderer.cgImage
        callback(renderer.uiImage)
    }
}
Ian G.
  • 31
  • 2
0

I have been using a wrapper as a workaround to achieve this

struct AppImage: View {
    private let name: String

    var body: some View {
        Image(name)
    }

    var asUIImage: UIImage? {
        UIImage(named: name)
    }
}

If you want to download an image from the internet, you can use Kingfisher to achieve the same output.

import UIKit
import Kingfisher

struct AppImage: View {
    private let urlString: String

    var body: some View {
        KFImage(URL(string: urlString))
    }

    var asUIImage: UIImage {
        let imageView = UIImageView()
        imageView.kf.setImage(with: URL(string: urlString))
        return imageView.image ?? UIImage(named: "placeholder-image")!
    }
}

Later on you can use any Image in your app as a UIImage if you stick to the AppImage type when initialising your images:

AppImage(name: "image-name").asUIImage

or AppImage(urlString: "https://url-to-your-image.com").asUIImage

Wissa
  • 1,444
  • 20
  • 24