iOS Tip: How to get Thumbnails of Files
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);
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 }