Just a simple bindings.
Add
pod 'Bindy'
to your podfile, and run
pod install
SPM is supported too.
For now, Bindy has a couple of basic types
- Signal - allows triggering a callback when some signal received.
- Observable - allows observing changing of value.
- ObservableArray - conforms to MutableCollection protocol, so you can work with it like with a regular array: subscript index, replace objects, map, enumerate, etc... Also, ObservableArray has
updates
signal, which will notify you about any changes in the array, such as insert, replace, delete.
@Observable var firstname = "Salvador"
@Observable var age = 54
func setupBindings() {
$age.bind(self) { [unowned self] newAge in
print("Happy \(newAge) birthday, \(firstname)")
}
age = 55
}
Don't forget always use [unowned owner]
in closure to prevent the retain cycle.
let messages: ObservableArray<Message> = []
let newMessage = Signal<Message>()
func setupBindings() {
newMessage.bind(self) { [unowned self] message in
self.messages.append(message)
}
messages.updates.bind(self) { [unowned tableView] updates in
self.tableView.pefrom(updates: updates)
}
}
func handleDidRecieveMessage(_ message: Message) {
newMessage.send(message)
}
}
You don't need to remove binding manually if you don't want. When the object that you pass as owner in bind(_ owner: AnyObject...
method deallocates, corresponding bindings will clean. However, if you want to unbind manually, just call unbind(_ owner: AnyObject)
.
Bindy has an extension for tableView for performing updates tableView.perform(updates:...
Also, observables have a method observe(_ owner: AnyObject...
, it works like bind
, but triggers callback immediately, this may be more comfortable in some situations.
If you want to receive events with transformed type, you can use transform
function on Observables like:
let speed = Observable(20)
lazy var speedString = speed.transform { "\($0)km/h" }
func setupBindings() {
speedString.observe(self) { [unowned self] speedString in
// speedString = "20km/h"
self.speedLabel.text = speedString
}
}
You can combine two Observable types with combined(with: ..., transform: ...)
function like:
let firstname = Observable("Maxim")
let lastname = Observable("Kotliar")
let age = Observable(24)
lazy var fullName = firstname
.combined(with: lastname) { "name: \($0) \($1)" }
.combined(with: age) { "\($0), age: \($1)" }
func setupBindings() {
userInfo.observe(self) { [unowned self] info in
// info = "name: Maxim Kotliar, age:24"
self.userInfoLabel.text = info
}
}
For Observable<Bool>
combinations Bindy have more convenient operators &&
and ||
, so you can combine Observable<Bool>
like regular Bool, also you can invert it with !
:
let isPremiumPurchased = Observable(true)
let isInTrialPeriodEnded = Observable(false)
let isAdsShowForced = Observable(false)
lazy var shouldShowAds = isAdsShowForced || !isPremiumPurchased && isInTrialPeriodEnded
Bindy supports KVO, so you can create Observable
from any KVO capable property with easy subscript syntax like:
let textField = UITextField()
let text = textField[\.text] // type will be Observable<String?>
text.observe(self) { newText in
print(newText)
}
For any Observable
type you can receive old value in closure, just pass two parameters to binding closure, first one will be an old value, the second one – new value:
let observableString = Observable("test")
observableString.bind(self) { oldString, newString in
print("String changed from \(oldString) to \(newString)")
}
Bindy contains some high order functions:
map
- applies on any type, behavior similar to a swift map.flatMap
- applies on Observable with optional type, returns Signal with non-optional type.compactMap
- applies on Observable with Collection inside, behavior similar to a swift version of the function.reduce
- applies on Observable with Collection inside, behavior similar to a swift version of the function.filter
- applies on Observable with Collection inside, behavior similar to a swift version of the function.