Creating Image Thumbnails Programmatically In iOS

February 17th, 2023

⏱ Reading Time: 7 mins

Working with images in iOS projects is not rare. There are various operations one could perform to an image, and sometimes such an operation is to create a thumbnail, meaning a smaller version, of the original image.

At first sight it might look like a complicated process involving a series of transformations that the image has to go through. That’s not true, though; the Core Graphics framework is here to support us, providing us with all the -really few- APIs we need to carry out such a task.

With that said, let’s get straight into the point and let’s find out what it takes in order to create image thumbnails easily. Once that’s out of the way, we’ll put it in motion in a pretty simple demo application.

Creating an image thumbnail

All we’re going to do is to implement a method that will be performing the thumbnail creation. You can define that method anywhere that’s convenient to you, but I strongly believe that the best place to keep it is in a UIImage extension. That way, it will be always available to all images.

So, let’s get started with that:

There are two things to note here:

  1. In this particular demonstration the desired size of the thumbnail will be provided as a percentage of the original image size. The scaleValue parameter will get a value from 0 to 1, indicating the final size percentage. Of course, you could change that, or create method overloads, that would accept a downsize factor differently; for instance, you could provide an exact thumbnail size.
  2. Depending on the size of the original image, the thumbnail creation might freeze the user interface (UI) for a few moments until the process is complete. To avoid that, I would recommend to perform everything in a background thread so users do not experience any sort of disturbance. I’ll apply that logic right next, and the thumbnail image will be handed over back to the caller through the completion handler that you see above. Note that later on I’ll provide you with an async/await alternative as well.

Now, the first thing in the method’s body is to define a background queue where we’ll add everything else that will come next:

There is a specific function in Core Graphics framework that does the actual thumbnail creation, called CGImageSourceCreateThumbnailAtIndex(_:_:_:). Before we become capable of using it, it’s required to perform some prior steps in order to prepare the values that will be given as arguments to it.

Getting a CGImageSource object from the original image

The first argument the above function accepts is a CGImageSource object. Such an object can extract image information from image data or from a URL. In this post we’ll go with the first option and use the original image’s data.

There is another Core Graphics function that can provide us with a CGImageSource object which you’ll see next, but in order to be able to use it it’s mandatory to get a Data object from the UIImage that we’re working on. So, let’s do that first:

We’re getting the image data using the jpegData(compressionQuality:) instance method from the UIImage class here, but you could use the pngData() method as well. Notice that if the above won’t work and imageData won’t get a value, then there is no reason to continue; we call the completion handler passing nil and exit the method.

We can now go ahead and initialize a CGImageSource object using the CGImageSourceCreateWithData(_:_:) function from Core Graphics. The first argument is the image data, the second a dictionary of options which we’ll leave empty:

As it’s not certain if a CGImageSource object will be created or not, a guard let statement is necessary here as well. In case of a nil result we call the completion handler passing nil and exit the method once again.

Note that imageData is a Data object, but the above function expects for a CFData object instead. Casting to CFData with the as CFData suffix is unavoidable.

Specifying thumbnail options

Along with the image source, the other argument that’s necessary to prepare for the CGImageSourceCreateThumbnailAtIndex(_:_:_:) function is a dictionary with a few values regarding the thumbnail.

The first and probably the most important one is about the thumbnail size. As said already, we’ll calculate and set a percentage of the original image size using the scaleValue parameter. But there is an important detail to keep in mind here:

Thumbnail size does not define the width and height of the final image! Thumbnail size is the maximum size that the biggest dimension of the image will get, either that’s the width or the height. The size of the other dimension will be calculated accordingly automatically.

Having that in mind, here’s how we’ll calculate the thumbnail size:

At first we calculate the value of the maximum dimension of the original image, and then we scale down by multiplying with the scaleValue.

Besides the thumbnail size we’ll also add two more key-value pairs to the dictionary that we’ll define next:

  • A flag to indicate that the thumbnail image should rotate according to original image’s orientation and respect the original aspect ratio.
  • A flag that must be mandatorily present to the dictionary (a few more words about that next).

Here’s the options dictionary including all the above:

Note that casting to CFDictionary is mandatory; the CGImageSourceCreateThumbnailAtIndex(_:_:_:) function expects for such an object.

The last key in the options dictionary is the mandatory one, but it can be replaced by the kCGImageSourceCreateThumbnailFromImageIfAbsent key as well. Using the current key, the thumbnail will be always created based on the original image. With the latter, the CGImageSourceCreateThumbnailAtIndex(_:_:_:) function will first check if a thumbnail image exists in the image source object or not, and it will create it if only it’s missing (note that an image source object can contain image data from more than one images).

There are more options you could specify in the above dictionary, but those here should be always present in my opinion. You can find more about available options here. Note that the entry regarding the thumbnail size can be omitted, but then guess what; the thumbnail will have the same size as the original image.

Generating the thumbnail image

With the image source and the options dictionary being ready, it’s time to generate the thumbnail image. Here’s how to do it:

The generated thumbnail is a CGImage object, not a UIImage. If it cannot be created then we provide nil to the completion handler and leave the method.

See that the first argument above is the imageSource object created in a previous step. The last argument is the dictionary with the options. As far as it regards the second parameter value, it indicates the index of the image to create thumbnail for in the given image source. An image source might contain data regarding a series of images, not just one. For example, a PDF file. In the simple example demonstrated here, as well as in the majority of the cases where you’ll need to create thumbnails, image source will contain a single image’s data. So, the index is zero (0).

There is one last step, which is to create a UIImage object and pass it to the completion handler. We’ll do that in one line like so:

Here is everything presented step by step so far in a single code snippet:

The async/await alternative

In the async/await era and the new concurrency APIs that Apple provided us with, the above method might look a bit outdated because one of its arguments is a completion handler. Note, however, that not everyone uses async/await -yet-, so what is presented above covers that part of developers.

If you’re an async/await fan on the other hand, then it’s easy to use the thumbnail creating method without the completion handler. To achieve that, simply add the following method as well to the UIImage extension:

See that this one is marked as async and returns an optional UIImage object; the thumbnail image or nil if it was not generated for some reason.

In the method’s body a call to withUnsafeContinuation(_:) function invokes the createThumbnail(scaleTo:completion:) method. When the completion handler will be called containing either the thumbnail image or nil, then the execution is resumed returning the contents of the completion handler from the method.

Demonstrating thumbnail creation

Time to see how all the above work, and in order to perform the demonstration we’ll use the following simple SwiftUI view:

There are two UIImage state properties in the view, the originalImage and thumbnailImage. The former loads an image from the assets catalog while the latter is initially nil. In this one we’ll assign the thumbnail image after its creation.

Which image will be displayed is specified in the displayedImage computed property. If thumbnailImage is not nil and contains an image, then this is what will be shown in the view. Otherwise, the original image is what we’ll see. Obviously, the original image is what will be displayed when the app is launched.

In the view’s body there is a VStack container that contains:

  • An Image view that displays the image determined in the displayedImage.
  • A Text view that shows the size of the displayed image (width and height).
  • A button (visible only when showing the original image) which will trigger the creation of the thumbnail image.

What is missing in the above code is the button’s action. To generate the thumbnail we can use either the createThumbnail(scaleTo:completion:) method with the completion handler, or the createThumbnail(scaleTo:) async method.

Following the first approach, here’s how to create the thumbnail and keep in the thumbnailImage property:

To use the async alternative, it’s necessary to call the createThumbnail(scaleTo:) method in a Task:

Both of the above are going to work just fine, and in both cases we set the thumbnail image’s size to 25% of the original one.

No matter what the path we’re going to take is, right next you can see everything in action:

Conclusion

In a nutshell, creating an image’s thumbnail is not much of a work, and the steps to get to it remain pretty much the same all the time. It’s just configuration details that might get changed, but that’s all to it. In this post I shared two different methods to fetch the thumbnail, one with the completion handler and one with the async/await alternative, so pick what you prefer the most.

Thank you for reading!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.