sindresorhus / Defaults

💾 Swifty and modern UserDefaults
https://swiftpackageindex.com/sindresorhus/Defaults/documentation/defaults
MIT License
2.03k stars 121 forks source link

[Suggestion] Support for Cocoa Bindings #15

Closed Ivorforce closed 5 years ago

Ivorforce commented 5 years ago

... which is extremely useful for GUI. An "emulation" instead using kvc would also suffice for most purposes, I think.

Ivorforce commented 5 years ago

A rough implementation might look like this:

class SimpleTransformer<Source, Dest : AnyObject>: ValueTransformer {
    let there: (Any?) -> Any?
    let back: ((Any?) -> Any?)?

    override class func transformedValueClass() -> Swift.AnyClass { return Dest.self }

    init(there: @escaping (Source?) -> Dest?, back: ((Dest?) -> Source?)? = nil) {
        self.there = { there($0 as? Source) }
        self.back = back != nil ? { back!($0 as? Dest) } : nil
    }

    override func transformedValue(_ value: Any?) -> Any? {
        return there(value)
    }

    override func reverseTransformedValue(_ value: Any?) -> Any? {
        return back!(value)
    }
}

    extension NSObject {
        open func bind<Object, Source>(_ binding: NSBindingName, to observable: Object, withKey key: Defaults.Key<Source>, options: [NSBindingOption: Any] = [:], transform: ((Source) -> AnyObject?)? = nil, back: ((AnyObject?) -> Source)? = nil) {
            let ftransform: (String?) -> AnyObject? = { value in
                guard let value = value.map(decode) else {
                        return nil
                }

                return transform.map { $0(value) } ?? value as AnyObject
            }

            let fback: (AnyObject?) -> String? = { value in
                guard let value = (back.map { $0(value) } ?? (value as? Source)) else {
                    return nil
                }

                return encode(value)
            }

            var options = options
            options[.valueTransformer] = SimpleTransformer<String, AnyObject>(there: ftransform, back: fback)

            bind(binding, to: observable, withKeyPath: key.name, options: options)
        }
    }

Personally I like passing transformers directly but obviously this can be accomplished with a usual ValueTransformer as well.

This might be used like:

        button.bind(.value, to: AppDelegate.defaults, withKey: Defaults.Keys.something,
                    transform: { NSNumber(value: value.ordinal <= $0.ordinal) },
                    back: { ($0 as? NSNumber)?.boolValue == true ? value : prev }
        )
sindresorhus commented 5 years ago

With SwiftUI and Combine, there's no future for legacy Cocoa bindings, so I'm not really interested in maintaining code for this.

Closing this in favor of #23.