3

I'm displaying images in my app that are downloaded from the network, but I'd like to downsample them so they aren't taking up multiple MB of memory. I could previously do this quite easily with UIKit:

func resizedImage(image: UIImage, for size: CGSize) -> UIImage? {
    let renderer = UIGraphicsImageRenderer(size: size)
    return renderer.image { (context) in
        image.draw(in: CGRect(origin: .zero, size: size))
    }
}

There are other methods as well, but they all depend on knowing the image view's desired size, which isn't straightforward in SwiftUI.

Is there a good API/method specifically for downsampling SwiftUI images?

bze12
  • 727
  • 8
  • 20

3 Answers3

1

I ended up solving it with geometry reader, which isn't ideal since it messes up the layout a bit.

@State var image: UIImage
var body: some View {
   GeometryReader { geo in
       Image(uiImage: self.image)
           .resizable()
           .aspectRatio(contentMode: .fit)
           .onAppear {
               let imageFrame = CGRect(x: 0, y: 0, width: geo.size.width, height: geo.size.height)
               self.downsize(frame: imageFrame) // call whatever downsizing function you want
           }
      }
}

Use the geometry proxy to determine the image's frame, then downsample to that frame. I wish SwiftUI had their own API for this.

bze12
  • 727
  • 8
  • 20
1

For resizing use this function. It works fast in lists or LazyVStack as well and reduces the memory consumption of the images.

public var body: some View {
    GeometryReader { proxy in
        let image = UIImage(named: imageName)?
            .resize(height: proxy.size.height)
        Image(uiImage: image ?? UIImage())
            .resizable()
            .scaledToFill()
    }
}
public extension UIImage {
    
    /// Resizes the image by keeping the aspect ratio
    func resize(height: CGFloat) -> UIImage {
        let scale = height / self.size.height
        let width = self.size.width * scale
        let newSize = CGSize(width: width, height: height)
        let renderer = UIGraphicsImageRenderer(size: newSize)
        
        return renderer.image { _ in
            self.draw(in: CGRect(origin: .zero, size: newSize))
        }
    }
}
Hans Bondoka
  • 437
  • 1
  • 4
  • 14
-1

This method use CIFilter to scale down UIImage

https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CILanczosScaleTransform

public extension UIImage {
    func downsampled(by reductionAmount: Float) -> UIImage? {

        let image = UIKit.CIImage(image: self)
        guard let lanczosFilter = CIFilter(name: "CILanczosScaleTransform") else { return nil }
        lanczosFilter.setValue(image, forKey: kCIInputImageKey)
        lanczosFilter.setValue(NSNumber.init(value: reductionAmount), forKey: kCIInputScaleKey)

        guard let outputImage = lanczosFilter.outputImage else { return nil }
        let context = CIContext(options: [CIContextOption.useSoftwareRenderer: false])
        guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil}
        let scaledImage = UIImage(cgImage: cgImage)

        return scaledImage
    }
}

And then you can use in SwiftUI View

struct ContentView: View {
    var body: some View {
        if let uiImage = UIImage(named: "sample")?.downsampled(by: 0.3) {
            Image(uiImage: uiImage)
        }
    }
}

Won
  • 1,107
  • 9
  • 16
  • How do I know what to set as the reductionAmount? Usually UIKit downsampling techniques depend on knowing the imageView frame for the image you want to display, and downsampling to fit that frame. My issue is that SwiftUI doesn't have ImageViews with frame properties. – bze12 Sep 10 '20 at 00:09
  • @bze12 This method can be used when you know about how much scale down. 1.0 is default value and you can set below 1.0 to scale down. – Won Sep 10 '20 at 00:50
  • yes I know that but my point is I don't know how much to scale down. – bze12 Sep 12 '20 at 22:08
  • This filter is not working. When I use it with a factor of 0.5, my memory usage nearly doubled from 120 MB to 197 MB and it's extremely slow (ok, this could be resolved by any cache) – Hans Bondoka May 25 '22 at 13:34