Categories
iOS Swift UIKit

Displaying images efficiently on iOS

Loading an image and displaying it on a screen consists of several steps. Firstly, we need to load the image data into memory, then decoding it to pixel data and finally, telling GPU to display it on screen. The whole process can be as short as two lines of code: creating an instance of UIImage using the name of the image and then assigning it to an UIImageView. Simple, but not so efficient.

Memory consumption impacts

Memory management is important topic as misusing memory can lead to, in worse case, system terminating our app. In addition, using too much memory will  cause high system CPU usage due to it trying to make more memory available by compressing it. Moreover, high CPU will lead to shorter battery life and no-one is happy about it.

High memory usage can be caused by keeping whole images in the memory and letting GPU to downscale it. The more efficient approach is to create a thumbnail with the size of the image view. This approach will use the minimum amount of pixel data and therefore system will use less resources.

Creating a thumbnail

For keeping resource consumption low, lets create UIImage extension for loading and creating the image at URL with specified size.

extension UIImage {
convenience init?(thumbnailOfURL url: URL, size: CGSize, scale: CGFloat) {
let options = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, options) else { return nil }
let targetDimension = max(size.width, size.height) * scale
let thumbnailOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceThumbnailMaxPixelSize: targetDimension] as CFDictionary
guard let thumbnail = CGImageSourceCreateThumbnailAtIndex(source, 0, thumbnailOptions) else { return nil }
self.init(cgImage: thumbnail)
}
}

Thumbnail creation consists of a couple of steps. Firstly, we create an instance of CGImageSource and tell it not to load and decode the data immediately (by setting kCGImageSourceShouldCacheImmediately to false). Instead, we pass the source into the thumbnail creation method which will immediately process the image data and scale it to the appropriate size. This approach avoids keeping the whole image in memory and instead, just uses the unscaled version.

private func loadThumbnailImage() {
let size = imageView.bounds.size
let scale = traitCollection.displayScale
let url = Bundle.main.url(forResource: "Wallpaper", withExtension: "jpg")!
DispatchQueue.global(qos: .userInitiated).async {
let image = UIImage(thumbnailOfURL: url, size: size, scale: scale)!
DispatchQueue.main.async { [weak self] in
self?.imageView.image = image
}
}
}

In a bit extreme example: displaying a thumbnail of a 5120 by 2880 pixels JPG image makes the app’s memory usage to be around 7 MB compared to 28 MB when the whole image is in memory. But on the other hand, we can have an app with multiple image views, each of them displaying a much larger image. Depending on the app, the difference can be huge.

Summary

We took a look at issues what can be caused by excessive use of system resources when displaying images. Then, we added an extension to UIImage for loading a larger image and scaling it to the size it is going to displayed. Small change, but has a huge impact.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Example project

UIImageThumbnail (GitHub) Xcode 10.1, Swift 4.2

Resources

Image and Graphics Best Practices (Apple)

One reply on “Displaying images efficiently on iOS”

Hi,
An image of 5120 by 2880 pixels JPG should not have 56MB as memory usage instead of 28MB?

(5120 x 2880 x 4) / 1024 / 1024 = 56,25

Am I missing something?

Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s