final class ViewModel {
enum Error {
case usernameNonAlphanumeric
case usernameMismatch
}
// Normal validation
let username = ValidatingProperty<String, Error>("") { input, error in
if nil == input.rangeOfCharacter(from: CharacterSet.alphanumeric) {
error = .usernameNonAlphanumeric
}
}
// Linked validation
let confirmedUsername = ValidatingProperty<String, Error>("", with: username) { input, username, error in
if username != input {
error = .usernameMismatch
}
}
}Formulated upon the fact that UI bindings to targets go through UIScheduler by default, bidirectional bindings require both ends to specify the type of their containing scheduler at static time. If both ends use ImmediateScheduler, the resulting binding would be synchronous. Otherwise, the resulting binding would be asynchronous, and a value merge policy must be specified at static time.
// Two immediate properties, `<~>` is commutative.
a <~> b
b <~> a
// Either or both scheduler-contained.
// The binding must specify a merge policy.
a <~ .preferLeft ~> bfinal class ViewModel {
enum Gender {
case unspecified
case male
case female
}
let gender = MutableProperty(Gender.unspecified)
}
final class ViewController: UIViewController {
enum GenderTransform: BidirectionalTransform {
static func forward(_ value: ViewModel.Gender) -> Int {
switch value {
case .unspecified: return 0
case .male: return 1
case .female: return 2
}
static func reverse(_ value: Int) -> ViewModel.Gender {
assert(value >= 0 && value <= 2)
return value == 1 ? .male : (value == 2 ? .female : .unspecified)
}
}
var viewModel: ViewModel!
let genderPicker = UISegmentedControl()
override func viewDidLoad() {
super.viewDidLoad()
genderPicker.selectionSocket <~ (GenderTransform.self, .preferLeft) ~> viewModel.gender
}
}final class ViewModel {
enum Error {
case usernameNonAlphanumeric
}
// Normal validation
let username = ValidatingProperty<String, Error>("") { input, error in
if nil == input.rangeOfCharacter(from: CharacterSet.alphanumeric) {
error = .usernameNonAlphanumeric
}
}
init(_ user: User) {
// Both are immediate mode.
user.username <~> username
// Either or both scheduler-contained, e.g. `ManagedObjectProperty`.
// Bindings must specify a merge policy.
user.username <~ .preferRight ~> username
}
}