Are you tired of manually wiring up your UI code? Yeah, I thought so. It’s such a mundane task, but we all have to do it. Or do we?
Swift Bond is a binding framework that removes the mundane task of wiring up your UI, giving you more time to think about the real problems you are trying to solve with your application.
In this Bond tutorial, you’ll learn how you can save time and write more maintainable code in this Bond.
Bond has a lot in common with ReactiveCocoa, something I’ll touch on towards the end of this article. For now, let’s get binding with Bond … Swift Bond.
Note: For those readers who don’t know the reference, the name of this framework, Swift Bond, is a pun based on the fictional spy James Bond, who famously introduces himself as “Bond … James Bond.” He also loved dry martinis, ordered “Shaken, not stirred,” so feel free to make yourself one to get in the Swift Bond mood!
A Quick Binding
Let’s fire up Xcode and start writing some code – martinis optional :]
First, download the starter project, which contains a ‘skeleton’ application. This will allow you to concentrate on Bond instead of fiddling with Interface Builder.
Once downloaded, ensure you have CocoaPods installed (if not, I’d suggest reading this tutorial to help you get started). Open the terminal and run pod install
, which will result in an output similar to below:
$ pod install Updating local specs repositories Analyzing dependencies Downloading dependencies Installing Bond (4.3.0) Installing DatePickerCell (1.0.4) Generating Pods project Integrating client project [!] Please close any current Xcode sessions and use `BindingWithBond.xcworkspace` for this project from now on. Sending stats Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed. |
Open the newly generated workspace with Xcode, then build and run. You will be greeted with the following:
All good? Then it’s time to create your first binding!
Open ViewController.swift and add the following to viewDidLoad()
:
searchTextField.bnd_text.observe { text in print(text) } |
The Bond framework adds various properties to UIKit controls. In this case, bnd_text
is an ‘observable’ based on the text
property of UITextField
. Observables represent streams of events. 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:
Optional("") Optional("h") Optional("he") Optional("hel") Optional("hell") Optional("hello") |
Each time you press a key, the closure expression in the code you just added is being executed, 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 ViewController.swift, update your code as follows:
let uppercase = searchTextField.bnd_text .map { $0?.uppercaseString } uppercase.observe { text in print(text) } |
Those of you who have used map
to transform Swift arrays can probably guess what the code above does.
Note: If you’re not familiar with map
, check out my tutorial on Functional Swift.
Build and run, then type in some more text: hello
Optional("") Optional("H") Optional("HE") Optional("HEL") Optional("HELL") Optional("HELLO") |
The map
operation returns an observable by transforming the output of bnd_text
into an uppercase string. Once again, the closure expression is being executed as events arise.
The result of applying the map
operation is also an observable, therefore you don’t need the intermediate assignment. Update your code as follows:
searchTextField.bnd_text .map { $0?.uppercaseString } .observe { text in 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 how about I show you something absolutely awesome?
Replace your current code with the following:
searchTextField.bnd_text .map { $0!.characters.count > 0 } .bindTo(activityIndicator.bnd_animating) |
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, Swift 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 the above actually works. But for now, it’s worth taking a moment to see the power of Bond … Swift Bond.
A Peek Under the Hood
Before building a more meaningful app, let’s 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 bnd_text
property is a Bond Observable
with a generic type of String?
. Observable
is a very simple subclass of EventProducer
, where most of the interesting stuff happens.
Event producers emit events where the type of the emitted data is defined by a generic parameter. When you call the observe
method, you are registering the given closure expression as an observer. Each time an event is emitted, your closure expression is invoked with the event data (as defined by the generic type).
For example, consider the code you first added:
searchTextField.bnd_text.observe { text in print(text) } |
The call to observe
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.
EventProducer
conforms to the EventProducerType
protocol, which 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.
An Observable
simply adds a value
property to the EventProducer
interface, allowing it to model a property or variable. You can ‘get’ or ‘set’ its value via the value
property and observe changes thanks to the EventProducer
functionality.
Bond extends UIKit, adding corresponding Observable
properties. For example, UITextField has a text
property and the observable equivalent bnd_text
. So getting and setting ‘bnd_text.value’ is equivalent to getting and setting text
, but with the added bonus that this change can now be observed.
The final piece of the puzzle is binding. The bindTo
method can be used to connect a couple of Observable
properties in order to synchronize changes. You saw this when you bound the animating property of the activity indicator to the length of text in the field, like so:
searchTextField.bnd_text .map { $0!.characters.count > 0 } .bindTo(activityIndicator.bnd_animating) |
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 my previous tutorial on that topic.
That’s enough theory for now — you’re probably itching to write some more code. :]
Adding a View Model
First, remove those few lines of prototype code from ViewController.swift. You’re now ready to get started with a more realistic application structure. You should now have a viewDidLoad
which looks like this:
override func viewDidLoad() { super.viewDidLoad() } |
Create a new file named PhotoSearchViewModel.swift within the ViewModel
category, adding the following:
import Foundation import Bond class PhotoSearchViewModel { let searchString = Observable<String?>("") init() { searchString.value = "Bond" searchString.observeNew { text in print(text) } } } |
This creates a simple view model with a single property, initializing it with the value Bond.
Open ViewController.swift and add the following property near the top of the class:
private let viewModel = PhotoSearchViewModel() |
Within the same file, update viewDidLoad()
as follows:
override func viewDidLoad() { super.viewDidLoad() bindViewModel() } |
And then add this method to the class:
func bindViewModel() { viewModel.searchString.bindTo(searchTextField.bnd_text) } |
The bindViewModel()
method is where you’ll be adding further bindings, but for now you’re just connecting the text field to the underlying view model.
Build and run your application. You’ll see the text ‘Bond’ display:
Your view model currently observes changes to the 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 bnd_text
property).
How do we make changes propagate the other way? Simple — just update the binding as follows:
viewModel.searchString.bidirectionalBindTo(searchTextField.bnd_text) |
That was easy!
Build and run, then type “Bond Love”:
Optional("Bond ") Optional("Bond L") Optional("Bond Lo") Optional("Bond Lov") Optional("Bond Love") |
Great — we’ve 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 obervable mapping. Returning to PhotoSearchViewModel.swift, add the following property to the view model:
let validSearchText = Observable<Bool>(false) |
This boolean property indicates whether the current searchString
value is valid or not.
Still within your view model, add the following within the initializer, init
:
searchString .map { $0!.characters.count > 3 } .bindTo(validSearchText) |
This maps the searchString
observable to a boolean which is true when the string length is greater than four characters, then binds it to the validSearchText
property.
Within ViewController.swift, locate your bindViewModel
method and add the following:
viewModel.validSearchText .map { $0 ? UIColor.blackColor() : UIColor.redColor() } .bindTo(searchTextField.bnd_textColor) |
This maps the validSearchText
property to a color, then binds it to the text field’s textColor
property.
Now build and run:
As a result, when the text is too short to constitute a valid search, it is now highlighted in red.
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!
Within PhotoSearchViewModel.swift add the following to the init
method:
searchString .filter { $0!.characters.count > 3 } .throttle(0.5, queue: Queue.Main) .observe { [unowned self] text in self.executeSearch(text!) } |
This filters the searchString
to exclude invalid (i.e. length <= 4) values, then applies the Bond throttle
operation, which does exactly what we require, throttling changes so that at most we receive one notification each 0.5 seconds.
The closure expression supplied to observe
invoked the executeSearch method, so add that one next:
func executeSearch(text: String) { print(text) } |
For now this just logs the result so that you can see the throttle in action.
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:
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.
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.
Fill in the registration form (only a few fields are mandatory) and your application will be created immediately.
Click on your app and grab a copy of your consumer key. This is passed to 500px along with every request.
The starter application already has all the code you need in order to query 500px; you’ll find the code within the Model
group. It’s all commented, so it should be easy to follow.
Returning to PhotoSearchViewModel.swift, add the following lazy property:
private let searchService: PhotoSearch = { let apiKey = NSBundle.mainBundle().objectForInfoDictionaryKey("apiKey") as! String return PhotoSearch(key: apiKey) }() |
This initializes the PhotoSearch
class, which provides a Swift API for querying 500px, passing your consumer key.
The consumer key is obtained from your plist. Open up Info.plist and edit the apiKey, adding the consumer key supplied by 500px:
Within PhotoSearchViewModel.swift, update the executeSearch
method as follows:
func executeSearch(text: String) { 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 invokes findPhotos
on the searchService
to execute the search. The result is returned asynchronously by the callback, with the Result
enumeration representing success or failure.
Build and run the application. The view model will initiate the query on construction 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. But in 99% of cases, it’ll be that your API key is entered incorrectly. Double check that you’ve entered the consumer key from your application on 500px.
Rendering the Results
It would be much more interesting if you could actually see the photos that your query returns, wouldn’t it? Let’s work on how to do that.
Within PhotoSearchViewModel.swift, add the following property:
let searchResults = ObservableArray<Photo>() |
As the name suggests, this is a special type of observable, one that supports arrays.
Before trying it out, I’d suggest looking into the ObservableArray
in a bit more detail; again, use cmd+click to navigate around the various Bond APIs.
The ObservableArray
is similar to Observable
in that it is also an EventProducer
, meaning you can subscribe to events that signal when the array changes. In this case, the event emits ObservableArrayEvent
instances.
The ObservabelArrayEvent
encodes the change that occurred to the array via the ObservableArrayOperation
enumeration. I’ll reproduce it here for reference:
public indirect enum ObservableArrayOperation<ElementType> { case Insert(elements: [ElementType], fromIndex: Int) case Update(elements: [ElementType], fromIndex: Int) case Remove(range: Range<Int>) case Reset(array: [ElementType]) case Batch([ObservableArrayOperation<ElementType>]) } |
As you can see, 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 then entire UI would have to be re-built. The detail provided by ObservableArray
allows for much more efficient UI updates.
Note: ReactiveCocoa doesn’t have an observable array concept, which is a problem I have stumbled upon before. You can see my own solution (quite similar to Bond) over on my blog.
Now that you know what an observable array is, it’s time to put it to use.
Within PhotoSearchViewModel.swift, locate the executeSearch
method and update the Success
case as follows:
case .Success(let photos): self.searchResults.removeAll() self.searchResults.insertContentsOf(photos, atIndex: 0) |
This clears the array, adding the new results.
Within ViewController.swift, add the following to bindViewModel
:
viewModel.searchResults.lift().bindTo(resultsTable) { indexPath, dataSource, tableView in let cell = tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath) as! PhotoTableViewCell let photo = dataSource[indexPath.section][indexPath.row] cell.title.text = photo.title let qualityOfServiceClass = QOS_CLASS_BACKGROUND let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0) cell.photo.image = nil dispatch_async(backgroundQueue) { if let imageData = NSData(contentsOfURL: photo.url) { dispatch_async(dispatch_get_main_queue()) { cell.photo.image = UIImage(data: imageData) } } } return cell } |
Bond has a protocol extension for EventProducerType
that is specifically intended for binding observable arrays to table views. The binding takes a table view instance and a closure expression; as you can see from the above, this expression takes on a similar role to the standard table view datasource.
Looking in a bit more detail, first the lift
method is invoked on the observable array. Bond supports table views with categories via a nested array structure. The lift
operation converts the observable array into the required nested form for a non-categorized table.
The closure expression is your standard table view cell wire-up. A cell is dequeued and various properties 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) |
Now update the executeSearch
method to make use of it:
func executeSearch(text: String) { 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.insertContentsOf(photos, atIndex: 0) case .Error: print("Sad face :-(") } } } |
This simply sets the property to true before querying 500px, then returns it to false when the result is asynchronously returned.
Within ViewController.swift, add the following to bindViewModel
:
viewModel.searchInProgress .map { !$0 } .bindTo(activityIndicator.bnd_hidden) viewModel.searchInProgress .map { $0 ? CGFloat(0.5) : CGFloat(1.0) } .bindTo(resultsTable.bnd_alpha) |
This shows the activity indicator when a query is in progress and also reduces the opacity of the table view.
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 an EventProducer
. Within PhotoSearchViewModel.swift, add the following property:
let errorMessages = EventProducer<String>() |
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 ViewController.swift, add the following to bindViewModel
:
viewModel.errorMessages.observe { [unowned self] error in let alertController = UIAlertController(title: "Something went wrong :-(", message: error, preferredStyle: .Alert) self.presentViewController(alertController, animated: true, completion: nil) let actionOk = UIAlertAction(title: "OK", style: .Default, handler: { action in alertController.dismissViewControllerAnimated(true, completion: nil) }) alertController.addAction(actionOk) } |
This subscribes to the events emitted by the errorMessages
property, displaying the supplied error message via a UIAlert.
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<NSDate>(NSDate()) let maxUploadDate = Observable<NSDate>(NSDate()) } |
This class is going to ‘back’ the settings screen.
Within 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 |
The above 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:
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.
Within ViewController.swift add the following method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "ShowSettings" { let navVC = segue.destinationViewController 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.
Note: Why didn’t I use a guard
statement here when performing both casts? Because if they fail, I want to know about it right away! The only reason they can fail is if the code is wrong.
In SettingsViewController.swift, add the following to the end of viewDidLoad()
:
bindViewModel() |
Then add the implementation:
func bindViewModel() { guard let viewModel = viewModel else { return } viewModel.creativeCommons.bidirectionalBindTo(creativeCommonsSwitch.bnd_on) } |
The code above binds the view model property to the corresponding switch.
Build and run the application, open up the settings and toggle that switch. You should notice that its state persists, i.e. if you turn it on, it stays on, and the photos that are returned are different.
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.bidirectionalBindTo(filterDatesSwitch.bnd_on) let opacity = viewModel.dateFilter.map { $0 ? CGFloat(1.0) : CGFloat(0.5) } opacity.bindTo(minPickerCell.leftLabel.bnd_alpha) opacity.bindTo(maxPickerCell.leftLabel.bnd_alpha) opacity.bindTo(minPickerCell.rightLabel.bnd_alpha) opacity.bindTo(maxPickerCell.rightLabel.bnd_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:
private func bind(modelDate: Observable<NSDate>, picker: DatePickerCell) { modelDate.observe { event in picker.date = event } picker.datePicker.bnd_date.observe { event in modelDate.value = event } } |
Then add these two lines to bindViewModel()
:
bind(viewModel.minUploadDate, picker: minPickerCell) bind(viewModel.maxUploadDate, picker: maxPickerCell) |
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 within 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.observe { [unowned self] maxDate in if maxDate.timeIntervalSinceDate(self.minUploadDate.value) < 0 { self.minUploadDate.value = maxDate } } minUploadDate.observe { [unowned self] minDate in if minDate.timeIntervalSinceDate(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(0.5, queue: Queue.Main) .observe { [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. In the introduction I stated that Bond has some similarities with ReactiveCocoa (and RxSwift). You might be wondering: which one should I use and why?
Put simply, ReactiveCocoa is a more complex framework; its concept of streams (which are analogous to Bond event producers) can emit a range of event types, which allows it to model more complex data flows, such as network requests. ReactiveCocoa can be used for UI binding, but that is not its main focus. Also, ReactiveCocoa 4.0 doesn’t have any UIKit extensions to support binding.
Bond is simpler, with full UIKit support. If you’re just interested in using the MVVM, or in more easily wiring up your UI in general, it is a great choice.
Currently the author of Bond is looking to grow the framework under the new name of ReactiveKit. Hopefully it won’t become a fully featured functional reactive framework to compete directly with ReactiveCocoa, because its current simplicity and focus on binding is what I find most appealing.
You can find the full sourcecode for this project on GitHub, with a commit for each build-and-run step.
Go forth and Bond … Swift Bond.
I hope you enjoyed this Bond tutorial. 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.