iOS Tip: How to get Thumbnails of Files

Lee young-jun
5 min readDec 20, 2023

--

ShowNote presented a file list, and the files were displayed as thumbnails.

Image file

If the file is an image file, use a small image like in this article.

ctx.draw(self.cgImage!, in: CGRect(origin: CGPoint.zero, size: size));

Before attempting, first check if it is an image file.

var image = UIImage(contentsOfFile: url.path);

MP3

If the file is an audio file, we should extract the album art image.

File Type

1. I defined file extensions to generate thumbnails for different file types.

enum FileExtension: String{
case MP3 = "mp3"
}

2. I extracted extension from the file url and made it lowercased. I will regard the file is image, if it doesn’t have a extension.

guard let ext = FileExtension.init(rawValue: url.pathExtension.lowercased()) else{
return try getImageThumbnail(url)
}

When the file extension is ‘mp3’, we need to extract the album cover.

switch ext {
case .MP3:
return await getAudioThumbnail(url)

Artwork

The album cover is contained in the metadata of AVAsset, so I created an AVPlayerItem instance with the file.

func getAudioThumbnail(_ url: URL) async -> UIImage? {
let playerItem = AVPlayerItem(url: originalUrl);

To get metadata from the asset, I obtained it like this:

let metas = playerItem.asset.metadata;

However, this approach is now deprecated. For the example project, I had to use the new method:

let metas = await playerItem.asset.loadMetadata(for: .id3Metadatav);

The new approach is throwable, so it is required to use the try keyword:

let metas = try await playerItem.asset.loadMetadata(for: .id3Metadata);

Metadata has a lot of information, and I had to find the one with the key artwork:

let artworkMeta = metas.first{ $0.commonKey == .commonKeyArtwork }

I created a UIImage with the image data extracted from the Artwork meta:

if artworkMeta?.dataValue != nil{
return UIImage(data: artworkMeta!.dataValue!);
}

But this way is also little modified. Now, you should use load method like loadMetadata .

if let imageData = try await artworkMeta?.load(.dataValue) {
return UIImage(data: imageData);
}

Video

Typically, we use the first frame of the video as a thumbnail.

File Type

Before creating it, I defined file extensions for the video types.

enum FileExtension: String{
...
case MP4 = "mp4"
case M4V = "m4v"
case MOV = "mov"
}

Asset

To obtain a frame, I first loaded the asset from the mp4 file.

func getVideoThumbnail(_ url: URL) async throws -> UIImage? {
let asset = AVURLAsset(url: url);

Image Generator

With the asset, I created an image generator.

let imageGen = AVAssetImageGenerator(asset: asset);

It can generate the frame image at the given play time.

let cgImage = try imageGen.copyCGImage(at: .zero, actualTime: nil);

PDF

What if the file is a PDF? Does Apple support the PDF type without any third-party libraries?

Yes!

Document

To access the PDF resources, I used CGPDFDocument. Therefore, I created a CGPDFDocument instance with the PDF file. (The CGPDFDocument constructor can return nil.)

func getPDFThumbnail(_ url: URL) throws -> UIImage? {
guard let doc : CGPDFDocument = .init(url as CFURL) else {
return nil
}

Page

CGPDFDocument has multiple pages. I obtained the first page using the page method, which takes the page number.

Therefore the first page number is 1.

guard let page = doc.page(at: 1) else{
return nil
}

Snapshot

To get a snapshot image of the page, we have to use a graphics context. CGPDFPage doesn't provide a method to get a UIImage directly.

let renderer = UIGraphicsImageRenderer(size: size)

How can we get the size of a PDF page? The page has getBoxRect:

let frame = page.getBoxRect(.cropBox)
let size = frame.size

The getBoxRect method can receive various box types, and in this case, I used cropBox. (I forgot the specific reason for choosing it.)

The final task is to draw the PDF on the graphic context.

return renderer.image { context in
let ctx = context.cgContext

ctx.drawPDFPage(page)
}

Wait… PDF snapshot is something weird. It is upside-down.
You might remember the orientation issue, but I will use simple way this time.

guard let image = renderer.image { context in
let ctx = context.cgContext

ctx.drawPDFPage(page)
}.cgImage else {
return nil
}

I can give new orientation into the UIImage constructor.

return UIImage(cgImage: image, scale: 1, orientation: .downMirrored)

Now the page is presented correct!

The full source is in this example repository.

If you found this post helpful, please give it a round of applause 👏. Explore more iOS-related content in my other posts.

For additional insights and updates, check out my LinkedIn profile. Thank you for your support!

Troubleshootings

‘metadata’ was deprecated in iOS 16.0: Use load(.metadata) instead

playerItem.asset.loadMetadata(for: .iTunesMetadata);

Cannot convert value of type ‘AVMetadataKey?’ to expected argument type ‘String’

let artworkMeta = metas.filter{ $0.commonKey == .commonKeyArtwork }

References

--

--

No responses yet