Swift 3: установить цвет метки Finder
Я пытаюсь установить цветные метки, отображаемые искателем. Единственная функция, которую я знаю, это setResourceValue. Но для этого нужны локализованные имена!
Я мог бы изобразить мой родной язык и английский язык, но все остальные я не знаю. Я не могу поверить, что это должно быть так.
Является ли функция перевода, которая принимает стандартный параметр типа enum или int и выдает локализованное имя цвета?
У меня есть бегущая часть, но только для двух языков (немецкий и английский):
let colorNamesEN = [ "None", "Gray", "Green", "Purple", "Blue", "Yellow", "Red", "Orange" ]
let colorNamesDE = [ "", "Grau", "Grün", "Lila", "Blau", "Gelb", "Rot", "Orange" ]
public enum TagColors : Int8 {
case None = -1, Gray, Green, Purple, Blue, Yellow, Red, Orange, Max
}
//let theURL : NSURL = NSURL.fileURLWithPath("/Users/dirk/Documents/MyLOG.txt")
extension NSURL {
// e.g. theURL.setColors(0b01010101)
func tagColorValue(tagcolor : TagColors) -> UInt16 {
return 1 << UInt16(tagcolor.rawValue)
}
func addTagColor(tagcolor : TagColors) -> Bool {
let bits : UInt16 = tagColorValue(tagcolor) | self.getTagColors()
return setTagColors(bits)
}
func remTagColor(tagcolor : TagColors) -> Bool {
let bits : UInt16 = ~tagColorValue(tagcolor) & self.getTagColors()
return setTagColors(bits)
}
func setColors(tagcolor : TagColors) -> Bool {
let bits : UInt16 = tagColorValue(tagcolor)
return setTagColors(bits)
}
func setTagColors(colorMask : UInt16) -> Bool {
// get string for all available and requested bits
let arr = colorBitsToStrings(colorMask & (tagColorValue(TagColors.Max)-1))
do {
try self.setResourceValue(arr, forKey: NSURLTagNamesKey)
return true
}
catch {
print("Could not write to file \(self.absoluteURL)")
return false
}
}
func getTagColors() -> UInt16 {
return getAllTagColors(self.absoluteURL)
}
}
// let initialBits: UInt8 = 0b00001111
func colorBitsToStrings(colorMask : UInt16) -> NSArray {
// translate bits to (localized!) color names
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleLanguageCode)!
// I don't know how to automate it for all languages possible!!!!
let colorNames = countryCode as! String == "de" ? colorNamesDE : colorNamesEN
var tagArray = [String]()
var bitNumber : Int = -1 // ignore first loop
for colorName in colorNames {
if bitNumber >= 0 {
if colorMask & UInt16(1<<bitNumber) > 0 {
tagArray.append(colorName)
}
}
bitNumber += 1
}
return tagArray
}
func getAllTagColors(file : NSURL) -> UInt16 {
var colorMask : UInt16 = 0
// translate (localized!) color names to bits
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleLanguageCode)!
// I don't know how to automate it for all languages possible!!!!
let colorNames = countryCode as! String == "de" ? colorNamesDE : colorNamesEN
var bitNumber : Int = -1 // ignore first loop
var tags : AnyObject?
do {
try file.getResourceValue(&tags, forKey: NSURLTagNamesKey)
if tags != nil {
let tagArray = tags as! [String]
for colorName in colorNames {
if bitNumber >= 0 {
// color name listed?
if tagArray.filter( { $0 == colorName } ).count > 0 {
colorMask |= UInt16(1<<bitNumber)
}
}
bitNumber += 1
}
}
} catch {
// process the error here
}
return colorMask
}
5 ответов
история
Сначала был мой предыдущий ответ, который работает для установки одной цветовой метки в файл: /questions/12793806/swift-3-ustanovit-tsvet-metki-finder/12793824#12793824.
Затем mz2 опубликовал этот превосходный ответ, который успешно устанавливает несколько цветовых меток для файла и объясняет процесс: /questions/12793806/swift-3-ustanovit-tsvet-metki-finder/12793821#12793821.
А теперь это маленькое дополнение, простое продолжение ответа @mz2.
Решение
Я просто реализовал предложение @mz2: я расширил его пример enum методами для извлечения предпочтений Finder и извлечения правильных локализованных имен цветов меток перед установкой атрибутов в файл.
enum LabelColors: Int {
case none
case gray
case green
case purple
case blue
case yellow
case red
case orange
func label(using list: [String] = []) -> String? {
if list.isEmpty || list.count < 7 {
switch self {
case .none: return nil
case .gray: return "Gray\n1"
case .green: return "Green\n2"
case .purple: return "Purple\n3"
case .blue: return "Blue\n4"
case .yellow: return "Yellow\n5"
case .red: return "Red\n6"
case .orange: return "Orange\n7"
}
} else {
switch self {
case .none: return nil
case .gray: return list[0]
case .green: return list[1]
case .purple: return list[2]
case .blue: return list[3]
case .yellow: return list[4]
case .red: return list[5]
case .orange: return list[6]
}
}
}
static func set(colors: [LabelColors],
to url: URL,
using list: [String] = []) throws
{
// 'setExtendedAttributeData' is part of https://github.com/billgarrison/SOExtendedAttributes
try (url as NSURL).setExtendedAttributeData(propertyListData(labels: colors, using: list),
name: "com.apple.metadata:_kMDItemUserTags")
}
static func propertyListData(labels: [LabelColors],
using list: [String] = []) throws -> Data
{
let labelStrings = labels.flatMap { $0.label(using: list) }
return try PropertyListSerialization.data(fromPropertyList: labelStrings,
format: .binary,
options: 0)
}
static func localizedLabelNames() -> [String] {
// this doesn't work if the app is Sandboxed:
// the users would have to point to the file themselves with NSOpenPanel
let url = URL(fileURLWithPath: "\(NSHomeDirectory())/Library/SyncedPreferences/com.apple.finder.plist")
let keyPath = "values.FinderTagDict.value.FinderTags"
if let d = try? Data(contentsOf: url) {
if let plist = try? PropertyListSerialization.propertyList(from: d,
options: [],
format: nil),
let pdict = plist as? NSDictionary,
let ftags = pdict.value(forKeyPath: keyPath) as? [[AnyHashable: Any]]
{
var list = [(Int, String)]()
// with '.count == 2' we ignore non-system labels
for item in ftags where item.values.count == 2 {
if let name = item["n"] as? String,
let number = item["l"] as? Int {
list.append((number, name))
}
}
return list.sorted { $0.0 < $1.0 }.map { "\($0.1)\n\($0.0)" }
}
}
return []
}
}
Использование:
do {
// default English label names
try LabelColors.set(colors: [.yellow, .red],
to: fileURL)
// localized label names
let list = LabelColors.localizedLabelNames()
try LabelColors.set(colors: [.green, .blue],
to: fileURL,
using: list)
} catch {
print("Error when setting label color(s): \(error)")
}
Чтобы установить один цвет, setResourceValue
API-вызов действительно то, что вы должны использовать. Однако ключ ресурса, который вы должны использовать, NSURLLabelNumberKey
, или же URLResourceKey.labelNumberKey
в Свифте 3 (не 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)")
}
(Это порт Swift 3 для ответа на связанный вопрос Objective-C. Протестировано с Xcode 8.1, macOS Sierra 10.12.1)
Чтобы установить несколько цветов, вы можете использовать API, который вы использовали для установки значений ресурсов с помощью ключа метки. Различие между этими двумя кодировками описано здесь: http://arstechnica.com/apple/2013/10/os-x-10-9/9/ - в основном ключ метки внутренне устанавливает расширенный атрибут "com.apple.metadata:_kMDItemUserTags", которая хранит массив этих строк меток в виде двоичного списка, в то время как показанная выше опция для одного цвета устанавливает 10-й байт значения расширенного атрибута длиной 32 байта"com.apple.FinderInfo".
"Локализованный" в этом имени ключа немного сбивает с толку в том смысле, что на самом деле он задает набор меток, выбранных пользователем, среди имен меток, установленных пользователем. Эти значения меток действительно локализованы, но только в той степени, в которой они установлены в соответствии с настройками локализации при первоначальном создании учетной записи. Чтобы продемонстрировать, это значения меток, используемые Finder в моей системе, для которых я выбрал финскую локализацию в качестве тестового и перезапущенного Finder, перезагруженного компьютера и т. Д.
➜ defaults read com.apple.Finder FavoriteTagNames
(
"",
Red,
Orange,
Yellow,
Green,
Blue,
Purple,
Gray
)
То, как данные кодируются в этом двоичном значении plist, - это просто имя любимого тега, за которым следует его индекс в массиве (который имеет фиксированную длину 8, а фактические значения начинаются с 1, то есть соответствуют семи цветам в порядке красного цвета)., Оранжевый, желтый, зеленый, синий, фиолетовый, серый). Например:
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>
Таким образом, локализация системы не учитывается, и на самом деле установка тега с любой строкой, за которой следует перевод строки, за которым следует число от 1 до 7, будет отображаться в Finder с цветом, указанным индексом тега. Однако, чтобы узнать правильные текущие значения, применяемые для получения тегов, которые будут применены из набора избранных тегов (например, совпадают и цвет, и метка), вам необходимо прочитать этот ключ в настройках Finder (ключ FavoriteTagNames из домен 'com.apple.Finder', который кодирует массив этих имен любимых тегов, как показано выше).
Игнорирование вышеуказанного усложнения в случае, если вы хотите получить правильное имя и цвет метки, требуя чтения из домена настроек Finder (что вы можете или не можете делать, в зависимости от того, находится ли ваше приложение в изолированной программной среде или нет), если вы захотите использовать несколько цветов, вот пример решения, которое устанавливает цвет с использованием расширенных значений атрибутов напрямую (я использовал SOExtendedAttributes, чтобы не касаться громоздких API-интерфейсов C xattr):
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\n1"
case .green: return "Green\n2"
case .purple: return "Purple\n3"
case .blue: return "Blue\n4"
case .yellow: return "Yellow\n5"
case .red: return "Red\n6"
case .orange: return "Orange\n7"
}
}
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)")
}
Я получил его работать без необходимости знать название цвета, благодаря новому URLResourceValues()
структура и номера тегов.
Зная, что каждый из этих номеров тегов представляет цвет тега:
0 нет
1 серый
2 зеленых
3 Фиолетовый
4 синий
5 желтый
6 красный
7 Апельсин
Сделайте URL вашего файла:
var url = URL(fileURLWithPath: pathToYourFile)
Это должен быть var, потому что мы собираемся изменить его.
Создать новый URLResourceValues
экземпляр (также должен быть переменной):
var rv = URLResourceValues()
Установите номер этикетки следующим образом:
rv.labelNumber = 2 // green
Наконец, напишите тег в файл:
do {
try url.setResourceValues(rv)
} catch {
print(error.localizedDescription)
}
В нашем примере мы установили номер тега на 2, так что теперь этот файл помечен зеленым цветом.
NSWorkspace имеет функции, которые возвращают все имена и цвета меток:
https://developer.apple.com/documentation/appkit/nsworkspace/1527553-filelabelcolors
https://developer.apple.com/documentation/appkit/nsworkspace/1533953-filelabels
По состоянию на ноябрь 2023 года лучший способ получить теги нескольких цветов выглядит следующим образом (с использованием пакета Swift-xattr ):
import XAttr
func getTags(for url: URL) -> [Tag] {
let colors = NSWorkspace.shared.fileLabelColors
do {
let tagsXattrName = "com.apple.metadata:_kMDItemUserTags"
let tagsBinaryPropertyList = try url.extendedAttributeValue(forName: tagsXattrName)
let tags:[String] = try PropertyListDecoder().decode([String].self, from: tagsBinaryPropertyList)
return tags.map {
// if the tag has a color, the color index is stored as a number after a newline character
let tagAndMaybeColor = $0.split(separator: "\n")
if tagAndMaybeColor.count == 1 {
return Tag(name: $0, color: nil)
}
let colorIndex = Int(tagAndMaybeColor[1])!
return Tag(name: String(tagAndMaybeColor[0]), color: colors[colorIndex])
}
} catch {
//print("error gettings tags for \(url): \(error)")
return []
}
}