Custom Thumbnails and Previews with Quick Look on iOS

Learn how to build your own Quick Look preview and thumbnail extensions to display custom file types in iOS. By Chuck Krutsinger .

5 (3) · 1 Review

Download materials
Save for later
Share

You may find yourself wanting to display a thumbnail representation of a file in your app, or you might even find yourself wanting to show a richer preview of the file itself. Fortunately, the QuickLook framework lets you generate thumbnails and display previews for many standard file types with little effort.

Thumbnails artwork

While this is great for standard file types, what if your app focuses on a specific type of file that isn’t covered by QuickLook? Well fear not; with Quick Look previews and thumbnail extension points introduced in iOS 13, you can provide custom previews and thumbnails for custom file types both in your own app and any other app on the same device that also uses QuickLook.

Note: You’ll see both forms of spacing used for the back-end QuickLook framework without a space and for the user-facing Quick Look with a space. The rules for spacing in the name appear to be mixed so we’ve endeavored to strike a balance.

In this tutorial, you’ll learn how to:

  • Generate thumbnail images using QLThumbnailGenerator
  • Define and export your own document type for your custom file format
  • Build your own Quick Look preview extension
  • Build your own Thumbnail extension
  • Debug your code that runs within an extension

To do this, you’ll work on RazeThumb, an app that shows a list of files and presents the Quick Look preview of each file when tapped. You’ll enhance the app to use QuickLook to display a thumbnail representing each file in the list. Finally, you’ll add a Quick Look preview extension and a Thumbnail extension for your custom .thumb file type and see it in action both in your app and in the Files app.

Getting Started

Begin by downloading the project materials using the Download Materials button at the top or bottom of this tutorial. Then open RazeThumb.xcodeproj in the starter folder.

RazeThumb is a simple document browsing app. It comes preloaded with a variety of file types containing thumb images. Five of the six formats are standard file types. The sixth is a custom file type called a .thumb file.

Build and run:

The RazeThumb app home screen containing a list of documents using a default placeholder icon for each one

The app presents a list of six different file types, each represented with a placeholder document icon. Tapping each file will present the QLPreviewController, which displays a preview of the file. That’s all it does for now, but you’ll be enhancing the app as you go.

Adding Quick Look Thumbnails

The first thing you’ll do is enhance RazeThumb to use a thumbnail of each file type. Currently, RazeThumb displays a generic document icon for each file. The QuickLook framework provides thumbnail images for a variety of file types, including images, PDFs, audio and video. Review Apple’s documentation for more details on what other file types Apple supports. If QuickLook doesn’t recognize a file type, it’ll still create some kind of placeholder thumbnail. You’ll learn more about that after you’ve added thumbnailing to the app.

For now, it’s time to start prettying up RazeThumb.

Pretty pink thumbnail artwork

Open Document.swift and change import Foundation to:

import QuickLook

At the bottom of the file, paste in the following extension:

// MARK: - QLThumbnailGenerator
extension Document {
  func generateThumbnail(
    size: CGSize,
    scale: CGFloat,
    completion: @escaping (UIImage) -> Void
  ) {
    if let thumbnail = UIImage(systemName: "doc") {
      completion(thumbnail)
    }
  }
}

The code above is a placeholder for your asynchronous thumbnail generating method. For now, it returns a UIImage of the document system icon. In a moment, you’ll replace this with a request to the QuickLook framework to generate a thumbnail.

Next, open DocumentThumbnailView.swift and paste the following code immediately below .groupBoxStyle(PlainGroupBoxStyle()):

.onAppear {
  document.generateThumbnail(
    size: thumbnailSize,
    scale: displayScale
  ) { uiImage in
    DispatchQueue.main.async {
      self.thumbnail = Image(uiImage: uiImage)
    }
  }
}

The code above waits for the view to appear. Then, it asks for a thumbnail UIImage for each file. Once the UIImage arrives, the view updates the SwiftUI Image using the main thread. This is necessary for all user interface updates.

Build and run:

The RazeThumb app home screen containing a list of documents using the new placeholder icon generated by the previous steps

You’ll see that the document icon for each file is now much smaller. This is because you didn’t size the images. But soon the QuickLook framework will handle the sizing for you.

Generating a Quick Look thumbnail

To get a thumbnail from the QuickLook framework, you’ll need to use QLThumbnailGenerator to create a QLThumbnailGenerator.Request and perform the request. Open Document.swift again and replace the contents of generateThumbnail(size:scale:completion:) with the following code:

// 1
let request = QLThumbnailGenerator.Request(
  fileAt: url,
  size: size,
  scale: scale,
  representationTypes: .all)

// 2
let generator = QLThumbnailGenerator.shared
generator.generateRepresentations(for: request) { thumbnail, _, error in
  // 3
  if let thumbnail = thumbnail {
    print("\(name) thumbnail generated")
    completion(thumbnail.uiImage)
  } else if let error = error {
    print("\(name) - \(error)")
  }
}

In the code above, you do the following:

  1. Create a new QLThumbnailGenerator.Request using the size and scale provided by the view, along with the defined representation type. The available representation types are .icon, .lowQualityThumbnail and .thumbnail. In this case, you request all of them and use the best one. In cases where using all formats is too slow, you can specify one format.
  2. Using the .shared generator, you start the request and wait for the thumbnails to be provided.
  3. If the generator passes an image in the completion closure, you then pass it back to the view. If you receive an error, you print the error message. Keep in mind that the generator can return up to three different images because the request is for all representations.

Build and run:

The RazeThumb app home screen containing a list of documents using the default thumbnails from Quick Look

You’ll now see the thumbnails for each file type. Unfortunately, QuickLook doesn’t know anything about .thumb files, so its thumbnail image is blank.

Searching messages printed in the Console

Now, take a look at the messages in the Debug area. There are three possible representations of each thumbnail, so there are three messages in the Console for each document displayed.

In the Filter field at the bottom-right corner of the Debug area, type .html. You’ll see there were three messages — one showing a thumbnail was generated and two showing failures. This is normal, and it’s something you can ignore when requesting all representations. The advantage of asking for all representations is that you’ll get at least one you can use.

Filtering thumbnail debugger messages

Now erase .html and type .pdf. For the PDF file, QuickLook generated two thumbnails. The DocumentThumbnailView displayed the placeholder document icon, and then it updated twice using the generated thumbnails. The trade-off for using all representations is the extra work of updating the view multiple times.

Replace .pdf and type .thumb. Even though QuickLook knows nothing about thumb files, it still gave you at least one thumbnail, albeit a blank one.

Now, take a moment to examine the different thumbnails the framework generated. The files zombiethumb.jpg, humanthumb.pdf and thumbsup.txt generated nice thumbnails of the file contents. The thumbnail of the Markdown file thumbsdown.md rendered as text since the framework doesn’t support Markdown rendering.

The RazeThumb app home screen containing a list of documents using the default thumbnails from Quick Look with annotations explaining that Markdown doesn't render, HTML uses a simple icon, and the custom .thumb thumbnail is not implemented

The thumbsdown.html file put up a generic HTML icon as its thumbnail, because the framework designers decided that rendering the HTML to produce an actual thumbnail wouldn’t perform well.

Finally, as you already discovered, the greenthumb.thumb thumbnail is a blank image. But don’t worry; you’ll soon fix that.

Give yourself a big “thumbs up” for generating thumbnails. On to the next thing.