6

In OS X Finder there is 'Comment' file property. It can be checked in finder by adding 'Comment' column or edited/checked after right clicking on file or folder and selecting 'Get info'.

How to read this value in swift or objective-c?

I checked already NSURL and none of them seems to be the right ones

Jason
  • 15,017
  • 23
  • 85
  • 116
patmar
  • 221
  • 1
  • 12

3 Answers3

5

Do not use the low-level extended attributes API to read Spotlight metadata. There's a proper Spotlight API for that. (It's called the File Metadata API.) Not only is it a pain in the neck, there's no guarantee that Apple will keep using the same extended attribute to store this information.

Use MDItemCreateWithURL() to create an MDItem for the file. Use MDItemCopyAttribute() with kMDItemFinderComment to obtain the Finder comment for the item.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • I have tested this solution and it works for most of the files/folders. The only case that it doesn't work is the root folder. Comments added to Applications, Users, Systems etc folders in root location are visible in FInder but not after reading them using MDItem. I listed also all attributes using MDItemCopyAttributeNames and kMDItemFinderComment is not there. Is this correct behaviour? – patmar Jan 11 '16 at 09:12
  • 1
    Caution: This only works with files that are indexed by Spotlight, meaning that it won't work on server volumes, for instance. OTOH, the answer by Martin R works in all such cases! – Thomas Tempelmann Sep 12 '22 at 08:42
5

Putting the pieces together (Ken Thomases reading answer above and writing answer link) you can extend URL with a computed property with a getter and a setter to read/write comments to your files:

update: Xcode 8.2.1 • Swift 3.0.2

extension URL {
    var finderComment: String? {
        get {
            guard isFileURL else { return nil }
            return MDItemCopyAttribute(MDItemCreateWithURL(kCFAllocatorDefault, self as CFURL), kMDItemFinderComment) as? String
        }
        set {
            guard isFileURL, let newValue = newValue else { return }
            let script = "tell application \"Finder\"\n" +
                String(format: "set filePath to \"%@\" as posix file \n", absoluteString) +
                String(format: "set comment of (filePath as alias) to \"%@\" \n", newValue) +
            "end tell"
            guard let appleScript = NSAppleScript(source: script) else { return }
            var error: NSDictionary?
            appleScript.executeAndReturnError(&error)
            if let error = error {
                print(error[NSAppleScript.errorAppName] as! String)
                print(error[NSAppleScript.errorBriefMessage] as! String)
                print(error[NSAppleScript.errorMessage] as! String)
                print(error[NSAppleScript.errorNumber] as! NSNumber)
                print(error[NSAppleScript.errorRange] as! NSRange)
            }
        }
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
3

As explained in the various answers to Mac OS X : add a custom meta data field to any file, Finder comments can be read and set programmatically with getxattr() and setxattr(). They are stored as extended attribute "com.apple.metadata:kMDItemFinderComment", and the value is a property list.

This works even for files not indexed by Spotlight, such as those on a network server volume.

From the Objective-C code here and here I made this simple Swift function to read the Finder comment (now updated for Swift 4 and later):

func finderComment(url : URL) -> String? {
    let XAFinderComment = "com.apple.metadata:kMDItemFinderComment"
    
    let data = url.withUnsafeFileSystemRepresentation { fileSystemPath -> Data? in

        // Determine attribute size:
        let length = getxattr(fileSystemPath, XAFinderComment, nil, 0, 0, 0)
        guard length >= 0 else { return nil }

        // Create buffer with required size:
        var data = Data(count: length)

        // Retrieve attribute:
        let result =  data.withUnsafeMutableBytes { [count = data.count] in
            getxattr(fileSystemPath, XAFinderComment, $0.baseAddress, count, 0, 0)
        }
        guard result >= 0 else { return nil }
        return data
    }

    // Deserialize to String:
    guard let data = data, let comment = try? PropertyListSerialization.propertyList(from: data,
        options: [], format: nil) as? String else {
            return nil
    }

    return comment
}

Example usage:

let url = URL(fileURLWithPath: "/path/to/file")
if let comment = finderComment(url: url) {
    print(comment)
}

The function returns an optional string which is nil if the file has no Finder comment, or if anything went wrong while retrieving it.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • For Swift 5.7 or later you can do `guard let data, ...` https://github.com/apple/swift-evolution/blob/main/proposals/0345-if-let-shorthand.md – Leo Dabus Aug 25 '23 at 05:59
  • @LeoDabus: That is correct, thanks for the notice. I'll keep my answer as it is, though, because it compiles with all Swift 4 and 5 versions. (Side note: SE-0345 was controversially discussed and I am not sure if I am happy with that feature :) – Martin R Aug 25 '23 at 08:46
  • Can you share a link to the discussion? – Leo Dabus Aug 25 '23 at 12:51
  • 1
    @LeoDabus: Here is the review thread: https://forums.swift.org/t/se-0345-if-let-shorthand-for-shadowing-an-existing-optional-variable/55805, and here a discussion after the acceptance: https://forums.swift.org/t/feedback-on-proposal-acceptances/56411. – Martin R Aug 25 '23 at 14:25