4

I try to read information about icons that are shown in finder on left source list. I tried already NSFileManager with following options

  • NSURLEffectiveIconKey icon read is not the same as in finder
  • NSURLCustomIconKey - returns nil
  • NSURLThumbnailKey - returns nil
  • NSThumbnail1024x1024SizeKey - returns nil

I managed to read all mounted devices using NSFileManager but I have no clue how to read icons connected with devices? Maybe someone has any idea or a hint.

I also tried to use

var image: NSImage = NSWorkspace.sharedWorkspace().iconForFile((url as! NSURL).path!)

but it returns the same image as NSURLEffectiveIconKey

Thanks!

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
patmar
  • 221
  • 1
  • 12

4 Answers4

7

First, the proper way to query which volumes are shown in the Finder's sidebar is using the LSSharedFileList API. That API also provides a way to query the icon:

LSSharedFileListRef list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteVolumes, NULL);
UInt32 seed;
NSArray* items = CFBridgingRelease(LSSharedFileListCopySnapshot(list, &seed));
CFRelease(list);
for (id item in items)
{
    IconRef icon = LSSharedFileListItemCopyIconRef((__bridge LSSharedFileListItemRef)item);
    NSImage* image = [[NSImage alloc] initWithIconRef:icon];

    // Do something with this item and icon

    ReleaseIconRef(icon);
}

You can query other properties of the items using LSSharedFileListItemCopyDisplayName(), LSSharedFileListItemCopyResolvedURL, and LSSharedFileListItemCopyProperty().

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Awesome! I've made a Swift version of this, are you ok if I post it as an answer with attribution to your post for the source? Not sure if this is the proper way to do this... – Eric Aya Aug 01 '15 at 12:55
  • I'm not sure if there's etiquette about that. Go ahead. – Ken Thomases Aug 01 '15 at 13:20
  • Done (edited my previous answer). Thanks a lot. If you ever feel later that it was a bad idea, ping me and I will remove it. – Eric Aya Aug 01 '15 at 13:29
3

This answer is a translation to Swift 1.2 of Ken Thomases's Objective-C answer.

All credits go to Ken Thomases, this is just a translation of his awesome answer.

let listBase = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListFavoriteVolumes.takeUnretainedValue(), NSMutableDictionary())
let list = listBase.takeRetainedValue() as LSSharedFileList

var seed:UInt32 = 0
let itemsCF = LSSharedFileListCopySnapshot(list, &seed)

if let items = itemsCF.takeRetainedValue() as? [LSSharedFileListItemRef] {
    for item in items {
        let icon = LSSharedFileListItemCopyIconRef(item)
        let image = NSImage(iconRef: icon)
        // use image ...
    }
}

Explanations:

When translating Ken's answer from Objective-C to try and use it I encountered some difficulties, this is the reason why I made this answer.

First problem was with LSSharedFileListCreate, the method signature in Swift didn't accept nil as its first parameter. I had to find a constant representing a CFAllocator: kCFAllocatorDefault. And the third parameter didn't accept nil either, so I put a dummy unused NSMutableDictionary to keep the compiler happy.

Also the "seed" parameter for LSSharedFileListCopySnapshot didn't accept the usual var seed:Uint32? for the inout, I had to give a default value to seed.

For deciding when to use takeRetainedValue or takeUnRetainedValue when using these APIs I referred to this answer.

Last, I had to cast the returned array as a Swift array of LSSharedFileListItemRef elements (it was initially inferred as a CFArray by the compiler).

Update

This has been deprecated in OS X El Capitan 10.11 (thanks @patmar)

enter image description here

Update 2

Note that while it's been deprecated it still works. The cast as [LSSharedFileListItemRef] in the previous solution is now ignored so we have to cast as NSArray instead then cast the item later:

if let items = itemsCF.takeRetainedValue() as? NSArray {
    for item in items {
        let icon = LSSharedFileListItemCopyIconRef(item as! LSSharedFileListItem)
        let image = NSImage(iconRef: icon)
        // use image ...

    }
}
Community
  • 1
  • 1
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
  • Thanks for swift translation – patmar Aug 01 '15 at 14:12
  • You're welcome. Please don't forget to upvote Ken's answer as soon as you've got enough rep. :) I couldn't have made my version without his answer. – Eric Aya Aug 01 '15 at 14:15
  • 1
    Hint: iconRef can be nil. In my case I get the nil pointer for icloud icon – patmar Aug 06 '15 at 10:10
  • just to let you know today i checked source code of LSSharedList and it says @available(OSX, introduced=10.5, deprecated=10.11, message="This functionality is no longer supported on OS X.") public func LSSharedFileListCreate(inAllocator: CFAllocator!, _ inListType: CFString!, _ listOptions: AnyObject!) -> Unmanaged! – patmar Oct 30 '15 at 23:30
  • @patmar Despite being deprecated it's still working with a little modification (see updated answer). – Eric Aya Nov 03 '15 at 15:00
  • I have already change this code and i am aware of the fact that it works. I just wanted to know if there was new API proposed to replace the deprecated one. – patmar Nov 07 '15 at 12:13
0

NSURLCustomIconKey will return nil because support for this key is not implemented. It's mentioned in the header but not in the NSURL documentation. You could get the info via deprecated File Manager methods.

https://developer.apple.com/library/mac/documentation/Carbon/Reference/File_Manager/

Alternatively maybe something like this.

func getResourceValue(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>,
           forKey key: String,
            error error: NSErrorPointer) -> Bool

Parameters
value
The location where the value for the resource property identified by key should be stored.
key
The name of one of the URL’s resource properties.
error
The error that occurred if the resource value could not be retrieved. This parameter is optional. If you are not interested in receiving error information, you can pass nil.

Edison
  • 11,881
  • 5
  • 42
  • 50
  • thanks for your answer. I am using (url as! NSURL).getResourceValue(&value, forKey: NSURLVolumeNameKey, error: &error) The problem is that i cannot find key that will return finder like icon. – patmar Aug 01 '15 at 07:40
0

2022, swift 5

low-res icon (really fast work):

let icon: NSImage = NSWorkspace.shared.icon(forFile: url.path )

Hi-res icon:

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 self.icon(forFile: path)
    }
}

Also hi-res thumbnail:

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
    }
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101