Cocoa Bindings on macOS
Cocoa bindings make glue code a thing of the past. Discover how you can simplify your controller code in this Cocoa Bindings on macOS Tutorial! By Andy Pereira.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Cocoa Bindings on macOS
25 mins
- Getting Started
- Searching via iTunes
- Setting Up an NSArrayController
- Adding the Search Box and Button
- Your First Bindings
- Binding Text Fields to Their Properties
- Adding in Rank
- Binding a Table View’s Selection
- Formatting Bound Data
- Formatting as Bytes
- Binding Images
- Just-in-Time Downloads
- Binding the Image View
- Populating the Collection View
- Binding Other Properties
- Seting Up a Progress Spinner
- Adding a Little More Detail
- Where to Go From Here?
Formatting as Bytes
You’ll be using a Byte Count Formatter next to show the file size.
Find and select File Size Label (Bind), open the Bindings Inspector and bind it to the Search Results Controller. Set Controller Key to selection and Model Key Path to fileSizeInBytes.
Then find a Byte Count Formatter in the Object Library and attach it to the NSTextFieldCell. There’s no need to configure anything here; the default settings on a byte formatter will work just fine.
You should see your byte count formatter in the document outline like so:
Build and run, select an app in the list, and you’ll see file size using the proper units, such as KB, MB, and GB:
You’re quite used to binding things by now, so here’s a short list of the remaining keys you need to bind:
- Bind the Artist Label (Bind) to artistName.
- Bind the Publication Date (Bind) to releaseDate.
- Add a Date Formatter; the default settings are fine.
- Bind the All Ratings Count (Bind) to userRatingCount.
- Bind the All Ratings (Bind) to averageUserRating.
- Bind the Genre Label (Bind) to primaryGenre.
All these labels should be bound to the Search Results Controller and the selection controller key.
For more precision in your UI, you can also bind the Description Text View (Bind), the Attributed String binding, to the itemDescription Model Key Path. Make sure you bind the NSTextView
, which is several levels down in the hierarchy, not the NSScrollView
which is at the top.
Build and run, and you’ll see most of the UI populate:
Binding Images
The next step is to bind the image for the icon to the Icon Image View. This is a little trickier because the JSON doesn’t contain the actual image, but instead a URL for the image.
Result
includes a method to download the image file and make it available as an NSImage
on the artworkImage
property.
Just-in-Time Downloads
You don’t want to download all the icons at once — just the one for the current selection in the table. You’ll download a new icon whenever the selection changes.
Add the following method to ViewController:
//1
func tableViewSelectionDidChange(_ notification: NSNotification) {
//2
guard let result = searchResultsController.selectedObjects.first as? Result else { return }
//3
result.loadIcon()
}
Here’s the play-by-play:
-
tableViewSelectionDidChange(_:)
fires every time the user selects a different row in the table. - The array controller property
selectedObjects
returns an array containing all the objects for the indexes of the rows selected in the table. In your case, the table will only permit a single selection, so this array always contains a single object. You store the object in theresult
object. - Finally, you call
loadIcon()
. This method downloads the image on a background thread and then updates theResult
objectsartworkImage
property when the image downloads on the main thread.
Binding the Image View
Now that your code is in place, you’re ready to bind the image view.
Head back to Main.storyboard, select the Icon Image View (Bind) object and open the Bindings Inspector.
Go to the Value section and bind to the Search Results Controller, set Controller Key to selection and Model Key Path to artworkImage.
Did you notice the Value Path and a Value URL sections? Both of these bindings are intended for local resources only. You can connect them to a network resource, but that will block the UI thread until the resource has finished downloading.
Build and run, search for fruit and then select a row. You’ll see the icon image appear once it has downloaded:
The collection view beneath the description text view is currently looking a little bare. Time to populate that with some screenshots.
Populating the Collection View
First you’ll bind the collection view to the screenShots
property and then ensure the screenShots
array populated correctly.
Select the Screen Shot Collection View (Bind). Open the Bindings Inspector and expand the Content binding in the Content group.
Bind to the Search Results Controller, set Controller Key to selection and Model Key Path to screenShots.
The screenShots
array starts out empty. loadScreenShots()
downloads the image files and populates the screenShots
array with NSImage
objects.
Add the following line to ViewController.swift, in tableViewSelectionDidChange(_:)
, right after result.loadIcon()
:
result.loadScreenShots()
This populates the screenshot images and creates the right number of views.
The next thing you need to do is set the right collection view item prototype. Although the collection view item scene is present in the storyboard, it’s not connected to the collection view. You’ll have to create this connection in code.
Add the following code to the end of viewDidLoad()
in ViewController.swift:
let itemPrototype = self.storyboard?.instantiateController(withIdentifier:
"collectionViewItem") as! NSCollectionViewItem
collectionView.itemPrototype = itemPrototype
Now that the collection view knows how to create each item via the prototype, you need to provide the content for each item via a binding.
Open Main.storyboard and select Screen Shot Image View (Bind) inside the Collection View Item Scene. You’ll find this floating next to the main view controller.
Bind the Value option to the Collection View Item object. The controller key should be blank, and Model Key Path should be representedObject.
The representedObject
property represents the object in the collection view array for that item; in this case, it’s an NSImage
object.
Build and run, and you’ll see see the screenshots appearing below the description text:
Great work! There’s just a few more features of Cocoa Bindings to cover before wrapping up.
Binding Other Properties
Your UI could use a bit of feedback. Users don’t like to stare at static screens when something is loading, and they’ll assume the worst when nothing happens in response to a user action.
Instead of leaving a static screen, you’ll show a spinner to the user while the image downloads.
Seting Up a Progress Spinner
You can easily bind a progress spinner to a new property in the ViewController
. Add the following property to ViewController
:
dynamic var loading = false
Loading requires two things in order to work correctly: the dynamic
keyword, and the parent class to be a subclass of NSObject
. Bindings relies on KVO (Key Value Observing), and a Swift class that doesn’t inherit from NSObject wouldn’t be able to use KVO.
Add the following line of code inside searchClicked(:_)
, right before the line that executes the call to getSearchResults(_:results:langString:completionHandler:)
:
loading = true
Locate the line in the same method that sets the content
property on searchResultsController
. Add the following code immediately before that line:
self.loading = false
Next, open Main.storyboard and select Search Progress Indicator (Bind). You’re going to bind two properties of the progress spinner: hidden
and animate
.
First, expand the Hidden group, and bind to the View Controller. Controller Key should be blank, and Model Key Path should be self.loading.
In this case, you want hidden
to be false when loading
is true
, and vice versa. There’s an easy way to do that: use NSValueTransformer
to flip the value of the boolean.
NSValueTransformer
is a class that helps you convert the form or value of data when moving between UI and data model.
You can subclass this object in order to do much more complex conversions, you can learn more about NSValueTransformers in this tutorial: How to Use Cocoa Bindings and Core Data in a Mac App.
NSValueTransformer
is a class that helps you convert the form or value of data when moving between UI and data model.
You can subclass this object in order to do much more complex conversions, you can learn more about NSValueTransformers in this tutorial: How to Use Cocoa Bindings and Core Data in a Mac App.
Choose NSNegateBoolean from the Value Transformer dropdown list.
Bind the Animate value to the View Controller object. Set Controller Key as blank, and set Model Key Path as self.loading.
This Boolean doesn’t need to be negated. The bindings look like this:
Build and run; search for something that will return a large number of results so you have time to watch the spinner do its thing: