To set a single color, the setResourceValue
API call is indeed what you should use. However, the resource key you should use is NSURLLabelNumberKey
, or URLResourceKey.labelNumberKey
in Swift 3 (not NSURLTagNamesKey
):
enum LabelNumber: Int {
case none
case grey
case green
case purple
case blue
case yellow
case red
case orange
}
do {
// casting to NSURL here as the equivalent API in the URL value type appears borked:
// setResourceValue(_, forKey:) is not available there,
// and setResourceValues(URLResourceValues) appears broken at least as of Xcode 8.1…
// fix-it for setResourceValues(URLResourceValues) is saying to use [URLResourceKey: AnyObject],
// and the dictionary equivalent also gives an opposite compiler error. Looks like an SDK / compiler bug.
try (fileURL as NSURL).setResourceValue(LabelNumber.purple.rawValue, forKey: .labelNumberKey)
}
catch {
print("Error when setting the label number: (error)")
}
(This is a Swift 3 port of an answer to a related Objective-C question. Tested with Xcode 8.1, macOS Sierra 10.12.1)
To set multiple colors, you can either use the API you've used with setting resource values with the label key. The distinction between these two encodings is described here: http://arstechnica.com/apple/2013/10/os-x-10-9/9/ – basically the label key is internally setting the extended attribute "com.apple.metadata:_kMDItemUserTags" which stores an array of those label strings as a binary plist, whereas the single colour option shown above is setting the 10th byte of 32 byte long extended attribute value "com.apple.FinderInfo".
The "localized" in that key name is a bit confusing in the sense that what is actually being set with it is the set of labels chosen by the user, amongst the label names set by the user. Those label values are indeed localized, but only to the extent where they are set according to the localisation setting when you initially created your account. To demonstrate, these are the label values used by Finder on my system, which I'd set to Finnish localization as a test and restarted Finder, rebooted machine etc:
? defaults read com.apple.Finder FavoriteTagNames
(
"",
Red,
Orange,
Yellow,
Green,
Blue,
Purple,
Gray
)
The way the data is encoded in that binary plist value is simply the favourite tag name followed by its index in the array (which is fixed to be of length 8, with actual values starting from 1, i.e. matching the seven colours in the order Red, Orange, Yellow, Green, Blue, Purple, Gray). For example:
xattr -p com.apple.metadata:_kMDItemUserTags foobar.png | xxd -r -p | plutil -convert xml1 - -o -
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>Gray
1</string>
<string>Purple
3</string>
<string>Green
2</string>
<string>Red
6</string>
</array>
</plist>
So, the system localisation is not taken into account, and in fact setting the tag with any string followed by a linefeed, followed by a number between 1–7 will show up in Finder with the colour indicated by the tag's index. However, to know the correct current values to apply to get the tags to be applied from the set of favorite tags (such that both colour and the label match up) you would need to read that key from Finder preferences (key 'FavoriteTagNames' from domain 'com.apple.Finder' which encodes an array of those favourite tag names as shown above).
Ignoring the above complication in case you want to get the label name and colour correct, requiring reading from Finder preferences domain (which you may or may not be able to do, depending on whether your app is sandboxed or not), should you wish to use multiple colours, here's an example solution that sets the colour using extended attribute values directly (I used SOExtendedAttributes to avoid having to touch the unwieldy xattr C APIs):
enum LabelNumber: Int {
case none
case gray
case green
case purple
case blue
case yellow
case red
case orange
// using an enum here is really for illustrative purposes:
// to know the correct values to apply you would need to read Finder preferences (see body of my response for more detail).
var label:String? {
switch self {
case .none: return nil
case .gray: return "Gray
1"
case .green: return "Green
2"
case .purple: return "Purple
3"
case .blue: return "Blue
4"
case .yellow: return "Yellow
5"
case .red: return "Red
6"
case .orange: return "Orange
7"
}
}
static func propertyListData(labels: [LabelNumber]) throws -> Data {
let labelStrings = labels.flatMap { $0.label }
let propData = try! PropertyListSerialization.data(fromPropertyList: labelStrings,
format: PropertyListSerialization.PropertyListFormat.binary,
options: 0)
return propData
}
}
do {
try (fileURL as NSURL).setExtendedAttributeData(LabelNumber.propertyListData(labels: [.gray, .green]),
name: "com.apple.metadata:_kMDItemUserTags")
}
catch {
print("Error when setting the label number: (error)")
}