Swift 3: Set Finder label color

前端 未结 4 1968
夕颜
夕颜 2020-12-09 00:52

I\'m trying to set the colored labels shown by the finder. The only function I know is setResourceValue. But this needs localized names!

I could image my mother lang

4条回答
  •  感动是毒
    2020-12-09 01:17

    History

    First there was my previous answer, which works for setting one color label to a file: https://stackoverflow.com/a/39751001/2227743.

    Then @mz2 posted this excellent answer which successfully sets several color labels to a file and explains the process: https://stackoverflow.com/a/40314367/2227743.

    And now this little add-on, a simple follow-up to @mz2's answer.

    Solution

    I've simply implemented @mz2's suggestion: I've expanded his enum example with methods for fetching the Finder's preferences and extract the correct localized label color names before setting the attributes to the file.

    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 []
        }
    }
    

    Usage:

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

提交回复
热议问题