【问题标题】:Swift 3: Set Finder label colorSwift 3:设置 Finder 标签颜色
【发布时间】:2017-02-04 18:38:31
【问题描述】:

我正在尝试设置取景器显示的彩色标签。我知道的唯一函数是 setResourceValue。但这需要本地化的名称!

我也可以想象我的母语和英语,但其他我不知道。我不敢相信,这应该是方式。

are 转换函数是否采用标准参数(如 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
}

【问题讨论】:

    标签: swift macos cocoa label finder


    【解决方案1】:

    感谢新的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,所以现在这个文件被标记为绿色。

    【讨论】:

    • 不错! (旁注:在 Swift 3 中不再需要 as NSError 演员表。)
    • 太好了。但是不止一种颜色又是什么呢?我可以为 .labelNumber 分配一个 0 - 7 的值。我不能添加它们,它们也不是可以组合的位。
    • @PeterSilie 在我发现的每一个提到 URLResourceValues 的文档中,总是只使用一个标签颜色/标签编号。我的猜测是 Finder 具有能够设置多个标签的遗留代码,但没有可用的公共 API 来做同样的事情。我希望有一天有人会看到这个并证明我错了......
    • @EricAya:标签名称和颜色存储在文件的“扩展属性”中,例如arstechnica.com/apple/2013/10/os-x-10-9/9,其中分析了格式,它是一个二进制属性列表。以下是一些读取/写入扩展属性的 Swift 方法:stackoverflow.com/a/38343753/1187415。颜色名称实际上存储在该 bplist 中。
    • @MartinR 很棒的文章,不幸的是我已经阅读了它并且无法使用这些信息来实现我不使用本地化名称的目标 - 也许是因为它不可能,也许是因为我没有不太了解,所以我想确定一下,因此赏金。当我有时间时,我会尝试使用你的扩展,它看起来很有希望。谢谢!
    【解决方案2】:

    要设置单一颜色,setResourceValue API 调用确实是您应该使用的。但是,您应该使用的资源键是 NSURLLabelNumberKey,或者在 Swift 3 中是 URLResourceKey.labelNumberKey(不是 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)")
    }
    

    (这是 an answer to a related Objective-C question 的 Swift 3 端口。使用 Xcode 8.1、macOS Sierra 10.12.1 测试)

    要设置多种颜色,您可以使用用于设置资源值的 API 和标签键。这里描述了这两种编码之间的区别:http://arstechnica.com/apple/2013/10/os-x-10-9/9/ - 基本上标签键在内部设置扩展属性“com.apple.metadata:_kMDItemUserTags”,它将这些标签字符串的数组存储为二进制 plist,而单色上面显示的选项是设置 32 字节长的扩展属性值“com.apple.FinderInfo”的第 10 个字节。

    该键名称中的“本地化”有点令人困惑,因为它实际上设置的是用户选择的标签集,以及用户设置的标签名称。这些标签值确实是本地化的,但仅限于在您最初创建帐户时根据本地化设置进行设置的程度。为了演示,这些是 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 以避免不得不接触笨拙的 xattr C API):

    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)")
    }
    

    【讨论】:

    • 看起来我确实找到了基于扩展属性 API 的解决方案。此特定值不是响应您的回复的评论中建议的二进制 plist。我会再挖掘一些东西,很确定 com.apple.FinderInfo 属性值中有两个字节包含这个作为选项掩码。
    • 好吧,我得到了同样的结论:superuser.com/a/295155/157584 - 32 字节长的扩展属性值中的第 10 个字节包含颜色信息。我不确定我读得是否完全正确。
    • 没关系,该键包含可以使用此资源值 API 操作的值,即它仅包含第一个设置颜色。包含颜色集的键是“com.apple.metadata:_kMDItemUserTags”,它确实包含一个二进制 plist。
    • 不能说我对我找到的解决方案非常满意,但我相信我的解决方案已经足够详尽,至少描述了为什么不太可能存在更好的解决方案(因为标签没有本地化到一些固定的集合,这些集合会根据系统本地化而改变,但在创建帐户后根据当时使用的本地化进行修复,当然也可以由用户操作)。
    • 我喜欢你的解决方案,它有效,设置了几个颜色标签,这正是我所需要的。当然,标签颜色名称仍然是硬编码的,但正如你所说,看起来不可能这样做。非常感谢! :) 注意:如果您的回答得到足够的支持,您将在 3 天内自动获得赏金 - 如果没有,我会将其归因于您的回答。
    【解决方案3】:

    历史

    首先是我之前的回答,它适用于将 one 颜色标签设置为文件:https://stackoverflow.com/a/39751001/2227743

    然后@mz2 发布了这个出色的答案,它成功地将几个颜色标签设置为一个文件并解释了这个过程:https://stackoverflow.com/a/40314367/2227743

    现在这个小插件,一个简单的follow-up 到@mz2 的答案。

    解决方案

    我只是简单地实现了@mz2 的建议:我已经扩展了他的枚举示例,其中包含用于获取 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)")
    }
    

    【讨论】:

    • 当我尝试实现这一点时,我还必须安装“setExtendedAttributeData”作为github.com/billgarrison/SOExtendedAttributes 的一部分。但这不是 Swift 模块,我只是从这种语言开始。使用这个 C 模块我需要做什么?有没有 Swift 唯一的解决方案?
    • 在你的 Xcode 项目中集成 SOExtendedAttributes 很容易(而且你不必担心它不在 Swift 中):下载 zip,然后拖放“NSURL+SOExtendedAttributes.h”和项目导航器中的“NSURL+SOExtendedAttributes.m”,位于您自己的文件旁边。在出现的面板中,应选中“复制”框。然后 Xcode 会询问您是否要制作桥接头。说是。然后在此标题中,添加行#import "NSURL+SOExtendedAttributes.h":完成,您现在可以使用setExtendedAttributeData。 :)
    【解决方案4】:
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-17
    • 1970-01-01
    • 2015-04-22
    • 2015-09-04
    • 2014-07-05
    • 1970-01-01
    相关资源
    最近更新 更多