Note: Updated for Xcode 8.3, iOS 10.3, and Swift 3.1 by Tom Elliott. Original tutorial by Colin Eberhardt.
Almost any app you can think of needs to be able to fetch some sort of data from somewhere (disk, the network, the user) and display it somewhere else, possibly transforming or translating it along the way. Often you may want to show the same data in multiple ways.
The process of keeping all your UI elements in sync (often referred to as ‘wiring up’ your UI) is harder than it looks. Left unchecked it can rapidly lead to unmaintainable, difficult to modify spaghetti monstrosities. We’ve all been there :]
In this tutorial, you will use Bond to clarify and simplify the process of attaching what your users do to what they see on the screen.
Consider the seemingly trivial example of a slider with a text field.
In this example, you will update the text field to always show the same value as the slider. Updates made to the text field will also update the slider. For extra measure, you will connect buttons to set the slider to its maximum and minimum values.
With UIKit, you might commonly write code in an imperative manner – that is, by telling the program how it should do something, rather than declaring what it should do. In this example, you would tell the program “Hey! When somebody changes the slider, make the text field show the same value. And if they change the text field, make the slider show the same value. And while you’re at it, when somebody clicks the ‘Set to Minimum’ button set the slider down to zero. Oh, and don’t forget to set the text field to ‘0’ as well!”.
Instead, Bond allows us to write declarative code, focussing on the what without worrying about the how. “Hey! I want the slider and the text field to show the same value. And when somebody clicks the ‘Set to Minimum’ button, set the slider to zero”. In the language of Bond, you bind the value of the text field to the value of the slider.
Bond is a binding framework which sits on top of the ReactiveKit framework. ReactiveKit provides the base classes needed for functional reactive programming (FRP) in Swift. If you aren’t familiar with ReactiveKit, you may want to stop and review the project documentation on github.
Bond provides AppKit and UIKit bindings, reactive delegates and datasources to ReactiveKit. Bond makes it easy to bridge the FRP world with Apple’s APIs. Time to get binding with Bond … Swift Bond.
A Quick Bond Binding
To get started, download the starter project, which contains a ‘skeleton’ application. This will allow you to concentrate on Bond instead of fiddling with Interface Builder.
This project uses CocoaPods, so open the project using the BindingWithBond.xcworkspace file. (If you are not familiar with CocoaPods, check out this tutorial to help you get started.)
Now build and run. You will be greeted with the following:
All good? Then it’s time to create your first binding!
Open PhotoSearchViewController.swift and add the following to viewDidLoad()
:
_ = searchTextField.reactive.text.observeNext { text in if let text = text { print(text) } } |
The Bond framework adds various properties to UIKit controls, under the reactive
proxy. In this case, reactive.text
is a signal based on the text
property of UITextField
. Signals represent sequences of events that can be observed.
observeNext
does just that, and executes its closure when the event occurs. Don’t worry, you’ll learn more about this shortly. For now, build and run, and then type “hello” to see what happens.
You will observe the following in the Xcode console:
h he hel hell hello |
Each time you press a key, the closure in the code you just added executes; which results in the application logging the current state of the text field. This illustrates how events such as a property changing can be observed. Now you’re going to see what power that can bring.
Manipulating Data
Returning to PhotoSearchViewController.swift, update the code after super.viewDidLoad()
as follows:
let uppercase = searchTextField.reactive.text .map { $0?.uppercased() } _ = uppercase.observeNext { text in if let text = text { print(text) } } |
The map
operation returns a signal by transforming the output of reactive.text
into an uppercase string. The result is again printed in the observeNext
closure, which is called as events arise.
Note: If you’re not familiar with map
, check out Colin Eberhardt’s excellent tutorial on Functional Swift.
Build and run. Then type “hello” again:
H HE HEL HELL HELLO |
You might have noticed that the result of applying map
is itself a signal. Therefore you don’t need the intermediate assignment. Update your code as follows:
_ = searchTextField.reactive.text .map { $0?.uppercased() } .observeNext { text in if let text = text { print(text) } } |
This gives rise to an elegant fluent syntax. Build and run your application to see the same behavior.
You’re probably only mildly impressed at the moment, so get ready to try something absolutely awesome.
Replace your current code with the following:
_ = searchTextField.reactive.text .map { $0!.characters.count > 0 } .bind(to: activityIndicator.reactive.isAnimating) |
The map
transforms each text string to a boolean, based on the presence of characters in the searchTextField
. The bind
, unsurprisingly, binds that boolean to the isAnimating
property of activityIndicator
.
Build and run your application. You will notice that the activity indicator animates only when the text field has text within it! That’s a pretty impressive feat with just three lines of code — perhaps even more impressive than a classic James Bond car chase :]
As you can see, Bond provides a fluent and expressive way to describe how data flows between your application and your UI. This makes your applications simpler, easier to understand, and ultimately gets the job done more quickly.
In the next section you’ll learn more about how bindings actually work. But for now, it’s worth appreciating the power of Bond … Bond.
A Peek Under the Hood
Before building a more meaningful app, take a quick look at how Bond functions. I’d recommend navigating around the Bond types (via cmd+click) as you read the explanation below.
Starting with your current code, the map
function returns a Signal
which fires Events
whenever the search text field text is changed. You can bind that signal to the activityIndicator.reactive.isAnimating
property, which is of type Bond
.
A Bond
just holds a weak reference to the target (the activity indicator) and acts as an observer of the signal. Whenever the signal emits a value, the Bond
calls its setter and updates the target with the given value.
Note: SignalProtocol
is where the map
operation is defined via a protocol extension. You might have heard of “protocol oriented programming,” and this is a great example.
As well as binding to signals you can also observe them. For example, consider the code you first added:
_ = searchTextField.reactive.text.observeNext { text in print(text) } |
The call to observeNext
sets up the given closure to be executed every time the text
property of searchTextField
changes.
Note: This is based on a classic design pattern known as the Observer Pattern.
Finally, consider the reactive.text
property of searchTextField
. Clearly, this is a Signal
as you are able to observe it. reactive.text
is referred to as an Observer – and these allow you to get values from the observed object.
You may also like to set the text. That is, you would like the text property of the text field to observe other objects. This requires a Subject – an object that is both a Signal
and Observer
.
Bond extends UIKit, adding corresponding Bond
or Subject
properties. You’ve already seen UITextField
has a reactive.text
observable property. You’ll explore observables later in this tutorial.
MVVM
While Bond makes it easy to bind UI properties directly to each other, this isn’t how you’ll likely want to use it in practice. Bond is a framework that supports the Model-View-ViewModel (MVVM) pattern. If you haven’t come across this pattern before, I’d recommend reading the first few sections of this tutorial on that topic.
That’s enough theory for now — you’re probably itching to write some more code. :]
Adding a View Model
It’s time to get started with a more realistic application structure. Create a new file named PhotoSearchViewModel.swift in the ViewModel group. Replace the contents with the following:
import Foundation import Bond import ReactiveKit class PhotoSearchViewModel { let searchString = Observable<String?>("") init() { searchString.value = "Bond" _ = searchString.observeNext { text in if let text = text { print(text) } } } } |
This creates a simple view model with a single searchString
property. The closure for observeNext
will print the contents of searchString
when signaled by a change.
Open PhotoSearchViewController.swift and add the following property near the top of the class:
private let viewModel = PhotoSearchViewModel() |
Next add the following method to PhotoSearchViewController
:
func bindViewModel() { viewModel.searchString.bind(to:searchTextField.reactive.text) } |
bindViewModel()
is where you’ll add further bindings, but for now you just connect searchTextField
to the underlying view model.
Now replace the contents of viewDidLoad()
with the following:
super.viewDidLoad() bindViewModel() |
Build and run your application. You’ll see the text: “Bond”:
Your view model currently observes changes to searchString
, but you’ll find that as you type text into the text field, the view model is not being updated. What gives?
Your current binding is one-way, which means it only propagates changes from the source (your viewModel
property) to the destination (the text field reactive.text
property).
How do you make changes propagate the other way? Simple – in PhotoSearchViewController
update the binding in bindViewModel()
to appear as follows:
viewModel.searchString.bidirectionalBind(to:searchTextField.reactive.text) |
That was easy! bidirectionalBind(to:context:)
establishes a bond that also signals changes in the destination back to the source.
Now, to clean up your prior test, open PhotoSearchViewModel.swift and remove the following line from init()
:
searchString.value = "Bond" |
Build and run, then type “Bond Love”:
B Bo Bon Bond Bond Bond L Bond Lo Bond Lov Bond Love |
Great – you confirmed that the text field updates are being propagated back to the view model.
Creating Observable from Observables
It’s time to do something a bit more useful with an observable mapping. You’ll enforce a requirement that searches contain more than three characters by making the text red until this is true.
Returning to PhotoSearchViewModel.swift, add the following property to the view model:
let validSearchText = Observable<Bool>(false) |
This boolean property will indicate whether the searchString
value is valid or not.
Now, replace the code in the initializer with the following:
searchString .map { $0!.characters.count > 3 } .bind(to:validSearchText) |
This maps the searchString
observable to a boolean which is true when the string length is greater than three characters, then binds it to the validSearchText
property.
In PhotoSearchViewController.swift, locate bindViewModel()
and add the following:
viewModel.validSearchText .map { $0 ? .black : .red } .bind(to: searchTextField.reactive.textColor) |
This maps the validSearchText
property to a color, based on the boolean value. It then binds the resulting color to the textColor
property of searchTextField
.
Now build and run and type some text:
When the text is too short to constitute a valid search, it is now colored red. It changes to black as soon as it’s considered valid.
Bond makes it very easy for you to connect and transform properties. As easy as…
Preparing To Search
The app you’re building will query 500px to retrieve photos as the user types, giving the same kind of immediate feedback users are used to from Google.
You could trigger a search each time the searchString
view model property changes, however this could result in TONS of requests. It’s much better to throttle the queries so that at most only one or two are sent per second.
With Bond, this is really easy to do!
In PhotoSearchViewModel.swift add the following method to the class for searching:
func executeSearch(_ text: String) { print(text) } |
For now this just logs the passed search string so that you can see the throttle in action.
Now add the following to the bottom of init()
:
_ = searchString .filter { $0!.characters.count > 3 } .throttle(seconds: 0.5) .observeNext { [unowned self] text in if let text = text { self.executeSearch(text) } } |
This filters the searchString
to exclude invalid (i.e. length <= 3) values, then applies the Bond throttle
operation, which does exactly what you require, throttling changes so that at most you receive one notification each 0.5 seconds. When the event fires, executeSearch(_:)
is called to complete the search.
Build and run, then try typing some text.
You will see something similar to the following, where the throttle disposes of events so that at most the text is logged once per 0.5 seconds once the four character threshold is reached:
Bond Bond Th Bond Throttles Bond Throttles Good! |
It would take forever to add this sort of functionality without Bond. Impressive stuff!
Using the 500px API
The app you are building uses the 500px API. This was selected because it has a relatively simple interface and authentication mechanism.
Note: The starter application already has all the code you need to query 500px; you’ll find the code in the Model group.
To use the interface, you’ll have to register as a developer. Fortunately this is free, quick and easy!
Sign up via the following URL: https://500px.com/signup
Once you have signed up, navigate to your applications: https://500px.com/settings/applications. Here you will see an interface that allows you to register an application.
Select Register your application and fill in the registration form (only a few fields are mandatory). Your application will be created immediately.
Click on your app link and grab a copy of your consumer key. This is passed to 500px along with every request.
Open up Info.plist and edit the apiKey, adding the consumer key supplied by 500px:
Return to PhotoSearchViewModel.swift and add the following lazy property to PhotoSearchViewModel
:
private let searchService: PhotoSearch = { let apiKey = Bundle.main.object(forInfoDictionaryKey: "apiKey") as! String return PhotoSearch(key: apiKey) }() |
This initializes the PhotoSearch
class, which provides a Swift API for querying 500px.
In PhotoSearchViewModel.swift, update the executeSearch(_:)
method contents as follows:
var query = PhotoQuery() query.text = searchString.value ?? "" searchService.findPhotos(query) { result in switch result { case .success(let photos): print("500px API returned \(photos.count) photos") case .error: print("Sad face :-(") } } |
This constructs a PhotoQuery
, which represents the query parameters, and then executes the search with the searchService
. The result is returned asynchronously, with the result
enumeration representing success or failure.
Build and run the application and type a string. The view model will execute the query and you’ll see the following logged:
500px API returned 20 photos |
If something went wrong then you’ll see the following:
Sad face :-( |
If this happens, double-check your API key, your internet connection, cross your fingers, and try again! If it still doesn’t work, then it’s likely the 500px API is down.
Rendering the Results
It would be much more interesting if you could actually see the photos that your query returns, wouldn’t it?
Open PhotoSearchViewModel.swift, and add the following property to PhotoSearchViewModel
:
let searchResults = MutableObservableArray<Photo>([]) |
As the name suggests, MutableObservableArray
is a special type of observable, one that supports arrays.
Before trying it out, take a look at the MutableObservableArray
(and ObservableArray
) in a bit more detail. Use cmd+click to navigate around the various Bond APIs.
ObservableArray
is similar to Observable
: it is also a Signal
. This means you can subscribe to events from that signal when the array changes. In this case, the event emits ObservableArrayEvent
instances.
ObservabelArrayEvent
encodes the change that occurred to the array via the ObservableArrayChange
enumeration. The events emitted by the ObservableArray
are quite detailed. Rather than informing observers of the new array value, they instead describe the changes that have occurred.
There is a very good reason for this level of detail. You could use a Bond Observable
to bind an array to the UI, via a table view perhaps. However, if you add a single item to this array, the Observable can only indicate that *something* has changed, and as a result the entire UI would have to be re-built. The detail provided by MutableObservableArray
allows for much more efficient UI updates.
Now that you know what an observable array is, it’s time to put one to use.
In PhotoSearchViewModel.swift, locate executeSearch(_:)
and update the success
case as follows:
case .success(let photos): self.searchResults.removeAll() self.searchResults.insert(contentsOf: photos, at: 0) |
This clears the array, adding the new results.
Open PhotoSearchViewController.swift, and add the following to the end of bindViewModel()
:
viewModel.searchResults.bind(to: resultsTable) { dataSource, indexPath, tableView in let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! PhotoTableViewCell let photo = dataSource[indexPath.row] cell.title.text = photo.title let backgroundQueue = DispatchQueue(label: "backgroundQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) cell.photo.image = nil backgroundQueue.async { if let imageData = try? Data(contentsOf: photo.url) { DispatchQueue.main.async() { cell.photo.image = UIImage(data: imageData) } } } return cell } |
Bond has a protocol extension for SignalProtocol
that is specifically intended for binding observable arrays to table views. You use it here to bind searchResults
to the resultsTable
. The closure takes on a similar role to the standard table view datasource.
The closure is your standard table view cell wire-up. A cell is dequeued and various properties are set. Notice that the image is downloaded via a background queue in order to keep the UI responsive.
Build and run to see your application coming to life:
Notice that as you type into the text field, it automagically updates. Very cool.
A Bit of UI Flair
Time to wire up a bit more of your UI!
Within PhotoSearchViewModel.swift, add the following property:
let searchInProgress = Observable<Bool>(false) |
This Observable
will be used to indicate when a search is in progress. Now update the contents of executeSearch(_:)
to make use of it:
var query = PhotoQuery() query.text = searchString.value ?? "" searchInProgress.value = true searchService.findPhotos(query) { [unowned self] result in self.searchInProgress.value = false switch result { case .success(let photos): self.searchResults.removeAll() self.searchResults.insert(contentsOf: photos, at: 0) case .error: print("Sad face :-(") } } |
This sets searchInProgress.value
to true before querying 500px, then returns it with false when the result is returned.
In PhotoSearchViewController.swift, add the following to the bottom of bindViewModel()
:
viewModel.searchInProgress .map { !$0 }.bind(to: activityIndicator.reactive.isHidden) viewModel.searchInProgress .map { $0 ? CGFloat(0.5) : CGFloat(1.0) } .bind(to: resultsTable.reactive.alpha) |
This shows the activity indicator and reduces the opacity of resultsTable
when a query is in progress.
Build and run to see this in action:
By now you should really be starting to feel the benefits of Bond! And the effects of that martini :]
Handling Errors
Currently if the 500px query fails, your app just logs it to the console. It really should report any failure back to the user in a helpful and constructive fashion.
The problem is, how should this be modeled? An error doesn’t feel like it should be a view model property, since it is a transient occurrence rather than a change in state.
The answer is simple enough: rather than an Observable
, all you need is a PublishSubject
. In PhotoSearchViewModel.swift, add the following property:
let errorMessages = PublishSubject<String, NoError>() |
Next, update the Error
case of executeSearch(_:)
as follows:
case .error: self.errorMessages.next("There was an API request issue of some sort. Go ahead, hit me with that 1-star review!") |
Within PhotoSearchViewController.swift, add the following to the bottom of bindViewModel
:
_ = viewModel.errorMessages.observeNext { [unowned self] error in let alertController = UIAlertController(title: "Something went wrong :-(", message: error, preferredStyle: .alert) self.present(alertController, animated: true, completion: nil) let actionOk = UIAlertAction(title: "OK", style: .default, handler: { action in alertController.dismiss(animated: true, completion: nil) }) alertController.addAction(actionOk) } |
This subscribes to the events emitted by the errorMessages
property, displaying the supplied error message via a UIAlertController
.
Build and run your application, then disconnect from the internet or remove your consumer key to see the error message in action:
Perfect. :]
Adding Search Settings
The current application queries the 500px API based on a simple search term. If you look at the documentation for the API endpoint, you’ll see that it supports a number of other parameters.
If you tap the Settings button on your app, you’ll see it already has a simple UI for refining the search. You’ll wire that up now.
Add a new file named PhotoSearchMetadataViewModel.swift to the ViewModel group, with the following contents:
import Foundation import Bond class PhotoSearchMetadataViewModel { let creativeCommons = Observable<Bool>(false) let dateFilter = Observable<Bool>(false) let minUploadDate = Observable<Date>(Date()) let maxUploadDate = Observable<Date>(Date()) } |
This class is going to back the settings screen. Here you have an Observable
property associated with each setting on that screen.
In PhotoSearchViewModel.swift, add the following property:
let searchMetadataViewModel = PhotoSearchMetadataViewModel() |
Update executeSearch(_:)
, adding the following lines just after the current one that sets the query.text
property:
query.creativeCommonsLicence = searchMetadataViewModel.creativeCommons.value query.dateFilter = searchMetadataViewModel.dateFilter.value query.minDate = searchMetadataViewModel.minUploadDate.value query.maxDate = searchMetadataViewModel.maxUploadDate.value |
This simply copies the view model state to the PhotoQuery
object.
This view model will be bound to the settings view controller. It’s time to perform that wire-up, so open SettingsViewController.swift and add the following property after the outlets:
var viewModel: PhotoSearchMetadataViewModel? |
So just how does this property get set? When the Settings button is tapped, the storyboard performs a segue. You can intercept this process in order to pass data to the view controller.
In PhotoSearchViewController.swift add the following method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "ShowSettings" { let navVC = segue.destination as! UINavigationController let settingsVC = navVC.topViewController as! SettingsViewController settingsVC.viewModel = viewModel.searchMetadataViewModel } } |
This ensures that when the ShowSettings
segue is executed, the view model is correctly set on the destination view controller.
In SettingsViewController.swift, add the following method:
func bindViewModel() { guard let viewModel = viewModel else { return } viewModel.creativeCommons.bidirectionalBind(to: creativeCommonsSwitch.reactive.isOn) } |
This code binds viewModel
to the creativeCommonsSwitch
.
Now add the following to the end of viewDidLoad()
:
bindViewModel() |
Build and run the application, open settings and toggle the Creative Commons switch. You will notice its state persists, i.e. if you turn it on, it stays on, and the photos that are returned are different (you will need to click in the search bar again to force a new request).
Binding the Dates
The settings view has a few other controls that need to be wired up, and that’s your next task.
Within SettingsViewController.swift, add the following to bindViewModel()
:
viewModel.dateFilter.bidirectionalBind(to: filterDatesSwitch.reactive.isOn) let opacity = viewModel.dateFilter.map { $0 ? CGFloat(1.0) : CGFloat(0.5) } opacity.bind(to: minPickerCell.leftLabel.reactive.alpha) opacity.bind(to: maxPickerCell.leftLabel.reactive.alpha) opacity.bind(to: minPickerCell.rightLabel.reactive.alpha) opacity.bind(to: maxPickerCell.rightLabel.reactive.alpha) |
This binds the date filter switch to the view model, and also reduces the opacity of the date picker cells when they are not in use.
The date picker cells contain a date picker and a few labels, with the implementation provided by the aptly-named DatePickerCell
CocoaPod. However, this poses a little problem: Bond does not supply bindings for the properties exposed by this cell type!
If you take a peek at the API for DatePickerCell
, you’ll see that it has a date property which sets both the label and the picker that it contains. You could bind bidirectionally to the picker, but this would mean the label setter logic is bypassed.
Fortunately a manual two-way binding, where you observe both the model and the date picker, is quite straightforward.
Add the following method to SettingsViewController.swift:
fileprivate func bind(_ modelDate: Observable<Date>, picker: DatePickerCell) { _ = modelDate.observeNext { event in picker.date = event } _ = picker.datePicker.reactive.date.observeNext { event in modelDate.value = event } } |
This helper method accepts a Date
and a DatePickerCell
and creates bindings between the two. The modelDate
is bound to the picker.date
and vice versa.
Now add these two lines to bindViewModel()
:
bind(viewModel.minUploadDate, picker: minPickerCell) bind(viewModel.maxUploadDate, picker: maxPickerCell) |
These bind the upload dates to the picker cells using your new helper method.
Build, run and rejoice! The dates are now correctly bound:
Note: The 500px API does not support date filtering; this is being applied to the photos returned by the code in the Model
group; as a result, you can easily filter out all of the photos returned by 500px. For a better result, try integrating with Flickr, which does support server-side date filtering.
Date Constraints
Currently, the user can create date filters that don’t make sense, i.e. where the minimum date is after the maximum.
Enforcing these constraints is really rather easy. Within PhotoSearchMetadataViewModel.swift, add the following initializer:
init() { _ = maxUploadDate.observeNext { [unowned self] maxDate in if maxDate.timeIntervalSince(self.minUploadDate.value) < 0 { self.minUploadDate.value = maxDate } } _ = minUploadDate.observeNext { [unowned self] minDate in if minDate.timeIntervalSince(self.maxUploadDate.value) > 0 { self.maxUploadDate.value = minDate } } } |
The above simply observes each date, and if the situation where min > max arises, the values are changed accordingly.
Build and run to see the code in action. You should try setting the max date to something less than the min date. It’ll change the min date accordingly.
You might have noticed a small inconsistency: when you change the search settings, the app doesn’t repeat the query. This could be a bit confusing for your user. Ideally the application should execute the search if any of the settings change.
Each setting is an observable property, so you could observe each of one of them; however, that would require a lot of repetitive code. Fortunately, there’s a better way!
Within PhotoSearchViewModel.swift, add the following to the end of init():
_ = combineLatest(searchMetadataViewModel.dateFilter, searchMetadataViewModel.maxUploadDate, searchMetadataViewModel.minUploadDate, searchMetadataViewModel.creativeCommons) .throttle(seconds: 0.5) .observeNext { [unowned self] _ in self.executeSearch(self.searchString.value!) } |
The combineLatest
function combines any number of observables, allowing you to treat them as one. The above code combines, throttles, then executes the query.
Build and run to see it in action. Try changing the dates, or either of the toggles. The search results will update after each time you change something!
How easy was that?! :]
Where To Go From Here?
You can find the finished project here.
Hopefully you’ve enjoyed this Bond tutorial and seen how easily you can wire-up your UI with Bond. Now it’s time to go forth and Bond … Swift Bond.
If you have any questions or comments, feel free to join the discussion below!
The post Bond Tutorial: Bindings in Swift appeared first on Ray Wenderlich.