QuickLook Thumbnailing ist ein relativ neues Framework von Apple, das hauptsĂ€chlich fĂŒr zwei AnwendungsfĂ€lle verwendet wird:
- Erstellen von Vorschauen ("Thumbnails") fĂŒr diverse Objekte, darunter unter anderem Bilder, Textdateien, PDF-Dateien, Audio-Dateien und Video-Dateien. Mit dieser API lassen sich also Vorschauen von ĂŒblichen Dateien wie in der Finder-App implementieren.
- Bereitstellung von Thumbnail-Extensions fĂŒr eigene Dateitypen. Angenommen ein Entwickler programmiert eine Pages/Word-Alternative, dann ist es sehr wahrscheinlich, dass dieser auch einen eigenen Dateityp zum persistieren der Daten entwerfen wird. Solche fĂŒr Finder unbekannten Dateitypen erhalten dann natĂŒrlich auch keine Vorschau, stattdessen wird lediglich ein generisches Datei-Icon angezeigt. Der Entwickler kann fĂŒr seinen eigenen Dateityp nun aber eine Thumbnail-Extension bereitstellen, wodurch das Betriebssystem und dritte Apps Vorschauen des unbekannten Dateityps anfordern und anzeigen können.
In diesem Artikel werde ich mich auf Anwendungsfall 1 konzentrieren und nur diesen praktisch in Swift demonstrieren. Anwendungsfall 2 ist bei Document-Based-Apps aber durchaus relevant, weswegen ich dazu in Zukunft vielleicht einen weiteren Artikel schreiben werde.
Leider ist dieses wunderbare Framework noch immer ziemlich unbekannt und meiner Meinung nach auch absolut unterschĂ€tztâ-âes bietet schlieĂlich eine Vielzahl an Möglichkeiten. Ich habe mich zwar selbst durch die Apple-Developer-Dokumentation gewĂŒhlt und so das Framework gefunden, in Online-Foren wird meiner Erfahrung nach aber noch immer viel zu wenig auf dieses Framework verwiesen. Bei meiner Kurzsuche habe ich lediglich eine StackOverflow-Antwort gefunden, die auf QuickLook Thumbnailing verweist. Dagegen stehe eine Vielzahl an Foren-Threads, bei denen dieses Framework die perfekte Lösung wĂ€re, aber kein Verweis darauf zu finden ist:
- https://stackoverflow.com/questions/4504834/how-to-generate-thumbnails-from-a-pdf-document-with-iphone-sdk
- https://stackoverflow.com/questions/4107850/how-can-i-programmatically-generate-a-thumbnail-of-a-pdf-with-ios
- https://stackoverflow.com/questions/10505273/document-thumbnails-generated-from-preview-content
- https://stackoverflow.com/questions/5658993/creating-pdf-thumbnail-in-iphone
- ... (!)
Schaut man sich diese BeitrĂ€ge an, werden meistens deprecated APIs, externe Frameworks, Hacks oder sogar eigene Implementierungen mit Fallunterscheidungen je nach Dateityp vorgeschlagen. Das gipfelt darin, fĂŒr jeden zu berĂŒcksichtigenden Dateityp eine andere Lösungsstrategie zu verwenden, fĂŒr PDFs beispielsweise PDFKit. Das ist logischerweise völliger Unsinn. NatĂŒrlich spielt auch das Deployment-Target beim Lösungsansatz eine wichtige Rolle, aber grundsĂ€tzlich sollte iOS 13.0, macOS 10.15 und Mac Catalyst 13.0 heute kein Hindernis mehr darstellen. Warum? Einfach mal in die Statistiken der App-Stores sehen: iOS- und iPadOS-Nutzung.
Werfen wir nun aber endlich einen Blick auf den notwendigen Swift-Code, der fĂŒr Version 5.4.2 lauffĂ€hig ist. Wie bereits oben beschrieben, habe ich ein NSCollectionView Ă€hnlich zu dem im Titelbild nachgebaut. Jedes NSCollectionViewItem ist selbst dafĂŒr verantwortlich, eine Vorschau seines zugehörigen Dokuments anzuzeigen. Nachfolgend ist der stark vereinfachte Aufbau dieser NSCollectionViewItems dargestellt:
import Cocoa
final class DocumentBrowserItem: NSCollectionViewItem {
// MARK: - Properties
// ...
@IBOutlet private weak var documentName: NSTextField!
@IBOutlet private weak var documentThumbnail: NSImageView!
// ...
private var document: DocumentManager.DocumentInformation? {
didSet {
guard self.isViewLoaded else { return }
guard let document = self.document else { return }
self.documentName.stringValue = document.name + document.extensionName
DocumentManager.shared.getThumbnail(for: document.location, withHeight: 150, andWidth: 150) { error, image in
guard !error else {
// ...
return
}
self.documentThumbnail.image = image
}
}
}
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
// ...
}
Sobald das DocumentBrowserItem das Dokument "eingesetzt" bekommt, wird der didSet-Block ab Zeile 13 ausgefĂŒhrt. FĂŒr diesen Artikel ist primĂ€r Zeile 18 bis Zeile 24 innerhalb dieses Blocks relevant. Die Funktion getThumbnail(...) ist eine Wrapper-Funktion der Singleton-Klasse DocumentManager, die unter anderem die Verwendung von Dokument-Vorschauen innerhalb der macOS-App vereinfachen und zentralisieren soll. NatĂŒrlich wĂ€re an dieser Stelle auch der direkte Aufruf der entsprechenden Funktion des Frameworks QuickLook Thumbnailing möglich. Ob das sinnvoll ist, hĂ€ngt stark vom Kontext der Anwendung ab. Die Singleton-Klasse enthĂ€lt zudem die Strukturdefinition DocumentInformation, die ein Dokument innerhalb der macOS-App reprĂ€sentiert. Dementsprechend sind darin alle relevanten Informationen ĂŒber das Dokument gespeichert.
Als nĂ€chstes werfen wir einen Blick auf die interessantere Implementierung von DocumentManager. NatĂŒrlich ist auch das lediglich ein stark vereinfachter Ausschnitt:
import Cocoa
import QuickLookThumbnailing
final class DocumentManager: NSObject {
// MARK: - Singleton Definition
static let shared = DocumentManager()
private override init() { }
// MARK: - Type Definitions
// ...
struct DocumentInformation: Codable {
let fileSystemName: String
let location: URL
let name: String
let extensionName: String
let description: String
let creationDate: Date
}
// ...
// MARK: - Functions
// ...
func getThumbnail(for location: URL, withHeight height: Int, andWidth width: Int, completion: @escaping (Bool, NSImage?) -> ()) {
let request = QLThumbnailGenerator.Request(fileAt: location, size: CGSize(width: width, height: height), scale: NSScreen.main!.backingScaleFactor, representationTypes: .thumbnail)
QLThumbnailGenerator.shared.generateBestRepresentation(for: request) { thumbnail, error in
DispatchQueue.main.async {
if thumbnail == nil || error != nil {
return completion(true, nil)
}
return completion(false, thumbnail!.nsImage)
}
}
}
// ...
}
Aus KomplexitĂ€tsgrĂŒnden habe ich das gesamte Sandbox-Management aus der Funktion getThumbnail(...) entfernt. macOS-Apps, die ĂŒber den App-Store vertrieben werden sollen, mĂŒssen in einer Sandbox "leben", wodurch Zugriffe auf das Dateisystem (auĂerhalb des eigenen Containers) erst durch den Benutzer genehmigt werden mĂŒssen. Mit Security-Scoped-Bookmarks ist es dann möglich, die durch den Benutzer genehmigten Orte dauerhaft zu speichern. Mit startAccessingSecurityScopedResource() und stopAccessingSecurityScopedResource() kann dann dementsprechend die Sandbox "geöffnet" und "geschlossen" werden.
Die Wrapper-Funktion getThumbnail(...) versteckt somit die gesamte KomplexitĂ€t der Vorschau-Erstellung und des Sandbox-Managements nach auĂen, wodurch die Erstellung von Vorschauen innerhalb der macOS-App trivial wird. Die Klasse QLThumbnailGenerator bietet mehrere Funktionen zum Erzeugen von Vorschauen an. Diese unterscheiden sich in ihrer Funktionsweise sehr stark. Die Funktion generateBestRepresentation(...) bot fĂŒr meine Anforderungen die ideale Lösung, da sie gleich die bestmögliche Vorschau zurĂŒckgibt und das Callback nicht mehrfach aufgerufen wird. Auf die Verwendung von generateRepresentations(...) habe ich bewusst verzichtet, da die Dauer, bis die erste Vorschau verfĂŒgbar ist, in meinem Anwendungsfall nebensĂ€chlich war und das mehrfache Aufrufen des Callbacks andere Nebeneffekte gehabt hĂ€tte.
GrundsĂ€tzlich ist die Verwendung des Frameworks QuickLook Thumbnailing intuitiv und selbsterklĂ€rend. AuĂerdem ist QuickLook Thumbnailing sehr gut dokumentiert, sodass eigentlich keine Fragen mehr offen bleiben. Leider ist das bei vielen APIs von Apple nicht der Fall, das Reverse-Engineeren bleibt einem hier zumindest mal erspart đ
Mein Ziel war es, mit diesem Artikel zu zeigen, wie schnell und einfach mit QuickLook Thumbnailing schöne Ergebnisse erzielt werden können. Das ist auch der Grund, warum ich nur wenig des gezeigten Codes erklĂ€rt habe. Die wirklich gute Dokumentation sollte fĂŒr das VerstĂ€ndnis absolut ausreichend sein: Apple Developer Documentation.