Skip to content

Instantly share code, notes, and snippets.

@andersio
Last active January 16, 2017 21:39
Show Gist options
  • Select an option

  • Save andersio/0b39372ae90a5d5f3e534b4d262d7d9a to your computer and use it in GitHub Desktop.

Select an option

Save andersio/0b39372ae90a5d5f3e534b4d262d7d9a to your computer and use it in GitHub Desktop.

Mutable Validating Property

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

Bidirectional Binding

Scheduler Containment

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 ~> b

Bidirectional Binding with Mapping

final 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
	}
}

Bidirectional Binding and Mutable Property Validation

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
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment