macosquicklook

Show actual file icon in macOS Quicklook Preview


I'm writing a QuickLook extension for my app, and I want to be able to display the actual icon image for a particular file, as shown in Finder. I've tried using QLThumbnailGenerator for this, but it will always return a plain white document icon.

QLThumbnailGenerationRequest *request = [[QLThumbnailGenerationRequest alloc] initWithFileAtURL:url size:size scale:scale representationTypes:QLThumbnailGenerationRequestRepresentationTypeIcon];

QLThumbnailGenerator *generator = [QLThumbnailGenerator sharedGenerator];

__unsafe_unretained PreviewViewController *weakSelf = self;

[generator generateBestRepresentationForRequest:request completionHandler:^(QLThumbnailRepresentation *thumbnail, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (thumbnail == nil || error) {
            NSLog(@"Failed to generate thumbnail! %@", error);
            // Handle the error case gracefully
        } else {
            // Display the thumbnail that you created
            NSLog(@"Generated thumbnail!");
            weakSelf.imageView.image = thumbnail.NSImage;
        }
    });
}];

Original code here.


Solution

  • 2022 SwiftUI, MacOs

    import SwiftUI
    import QuickLookThumbnailing
    
    struct ThumbnailImageView: View {
        let url: URL
        let size: CGFloat
    
        @State private var thumbnail: NSImage? = nil
    
        var body: some View {
            Group {
                if let thumbnail = thumbnail {
                    Image(nsImage: thumbnail)
                } else {
                    Image(systemName: "photo") // << any placeholder
                      .onAppear(perform: generateThumbnail) // << here !!
                }
            }
        }
    
        func generateThumbnail() {
            let size: CGSize = CGSize(width: size, height: size)
            let request = QLThumbnailGenerator.Request(fileAt: url, size: size, scale: 1.0, representationTypes: .lowQualityThumbnail)
                request.iconMode = true
            let generator = QLThumbnailGenerator.shared
            
            generator.generateRepresentations(for: request) { (thumbnail, type, error) in
                DispatchQueue.main.async {
                    if thumbnail == nil || error != nil {
                        assert(false, "Thumbnail failed to generate")
                    } else {
                        DispatchQueue.main.async { // << required !!
                            self.thumbnail = thumbnail!.nsImage  // here !!
                        }
                    }
                }
            }
        }
    }
    

    usage:

    ThumbnailImageView(url: url, size: 100)
    

    and another way:

    import Foundation
    import SwiftUI
    import Quartz
    import QuickLook
    
    struct UKSImage: View {
        let url: URL
        let size: CGFloat
        
        @State private var thumbnail: NSImage? = nil
        
        var body: some View {
            if let thumbnail = thumbnail {
                Image(nsImage: thumbnail)
                    .resizable()
                    .scaledToFit()
            } else {
                Image(systemName: "photo") // << any placeholder
                  .onAppear(perform: generateThumbnail) // << here !!
            }
        }
        
        func generateThumbnail() {
            DispatchQueue.global(qos: .background).async {
                self.thumbnail = url.imgThumbnailAdv(size)
            }
        }
    }
    
    
    fileprivate extension URL {
        func imgThumbnailAdv(_ size: CGFloat) -> NSImage? {
            let extensionsExceptions: [String]  = ["txt","docx","doc","pages","odt","rtf","tex","wpd","ltxd",
                                                   "btxt","dotx","wtt","dsc","me","ans","log","xy","text","docm",
                                                   "wps","rst","readme","asc","strings","docz","docxml","sdoc",
                                                   "plain","notes","latex","utxt","ascii",
    
                                                   "xlsx","patch","xls","xlsm","ods",
    
                                                   "py","cs","swift","html","css", "fountain","gscript","lua",
    
                                                   "markdown","md",
                                                   "plist"
            ]
    
            if extensionsExceptions.contains(self.pathExtension.lowercased()) {
                let img = NSWorkspace.shared.highResIcon(forPath: self.path, resolution: Int(size))
    
                return img
            }
            
            if let img2 = self.getImgThumbnail(size) {
                return img2
            }
            
            return NSWorkspace.shared.highResIcon(forPath: self.path, resolution: Int(size))
        }
    }
    
    extension NSImage{
        var pixelSize: NSSize?{
            if let rep = self.representations.first{
                let size = NSSize(width: rep.pixelsWide, height: rep.pixelsHigh)
                return size
            }
            return nil
        }
    }
    
    fileprivate extension URL {
        func getImgThumbnail(_ size: CGFloat) -> NSImage? {
            let ref = QLThumbnailCreate ( kCFAllocatorDefault,
                                          self as NSURL,
                                          CGSize(width: size, height: size),
                                          [ kQLThumbnailOptionIconModeKey: false ] as CFDictionary
            )
            
            guard let thumbnail = ref?.takeRetainedValue()
            else { return nil }
            
            if let cgImageRef = QLThumbnailCopyImage(thumbnail) {
                let cgImage = cgImageRef.takeRetainedValue()
                return NSImage(cgImage: cgImage, size: CGSize(width: cgImage.width, height: cgImage.height))
            }
            
            return nil
        }
    }
    
    extension NSWorkspace {
        func highResIcon(forPath path: String, resolution: Int = 512) -> NSImage? {
            if let rep = self.icon(forFile: path)
                .bestRepresentation(for: NSRect(x: 0, y: 0, width: resolution, height: resolution), context: nil, hints: nil) {
                let image = NSImage(size: rep.size)
                image.addRepresentation(rep)
                return image
            }
            return nil
        }
    }
    

    usage:

    UKSImage(url: url, size: 100)