Swift 5.7, iOS 16
Here are all the useful Binding
related extensions I've curated or written.
These cover all bases for me - I haven't found any others to be needed.
I hope someone finds them useful.
import SwiftUI
/// Shortcut: Binding(get: .., set: ..) -> bind(.., ..)
func bind<T>(_ get: @escaping () -> (T), _ set: @escaping (T) -> () = {_ in}) -> Binding<T> {
Binding(get: get, set: set)
}
/// Rebind a Binding<T?> as Binding<T> using a default value.
func bind<T>(_ boundOptional: Binding<Optional<T>>, `default`: T) -> Binding<T> {
Binding(
get: { boundOptional.wrappedValue ?? `default`},
set: { boundOptional.wrappedValue = $0 }
)
}
/// Example: bindConstant(false)
func bind<Wrapped>(constant: Wrapped) -> Binding<Wrapped> { Binding.constant(constant) }
extension Binding {
/// `transform` receives new value before it's been set,
/// returns updated new value (which is set)
func willSet(_ transform: @escaping (Value) -> (Value)) -> Binding<Value> {
Binding(get: { self.wrappedValue },
set: { self.wrappedValue = transform($0) })
}
/// `notify` receives new value after it's been set
func didSet(_ notify: @escaping (Value) -> ()) -> Binding<Value> {
Binding(get: { self.wrappedValue },
set: { self.wrappedValue = $0; notify($0) })
}
}
/// Example: `TextField("", text: $test ?? "default value")`
/// See https://stackoverflow.com/a/61002589/5970728
func ??<T>(_ boundCollection: Binding<Optional<T>>, `default`: T) -> Binding<T> {
bind(boundCollection, default: `default`)
}
// Allows use of optional binding where non-optional is expected.
// Example: `Text($myOptionalStringBinding)`
// From: https://stackoverflow.com/a/57041232/5970728
extension Optional where Wrapped == String {
var _bound: String? {
get {
return self
}
set {
self = newValue
}
}
public var bound: String {
get {
return _bound ?? ""
}
set {
_bound = newValue.isEmpty ? nil : newValue
}
}
}
/// Returns binding for given `keyPath` in given `root` object.
func keyBind<Root, Element>(_ root: Root, keyPath: WritableKeyPath<Root, Element>) -> Binding<Element> {
var root: Root = root
return Binding(get: { root[keyPath: keyPath] }, set: { root[keyPath: keyPath] = $0 })
}
/// Bind over a collection (is this inbuilt now? ForEach makes it available)
/// Override `get` and `set` for custom behaviour.
/// Example: `$myCollection.bind(index)`
extension MutableCollection where Index == Int {
func bind(_ index: Index,
or defaultValue: Element,
get: @escaping (Element) -> Element = { $0 }, // (existing value)
set: @escaping (Self, Index, Element, Element) -> Element = { $3 } // (items, index, old value, new value)
) -> Binding<Element> {
var _self = self
return Binding(
get: { _self.indices.contains(index) ? get(_self[index]) : defaultValue },
set: { if _self.indices.contains(index) { _self.safeset(index, set(_self, index, _self[index], $0)) } }
)
}
}