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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

byte count formatter

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:

byte count final

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.

selecttextView

Build and run, and you’ll see most of the UI populate:

mostly populated

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:

  1. tableViewSelectionDidChange(_:) fires every time the user selects a different row in the table.
  2. 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 the result object.
  3. Finally, you call loadIcon(). This method downloads the image on a background thread and then updates the Result objects artworkImage 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:

icon populated

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:

screenshots

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:

Binding animating

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:

Spinner animating