Chapters

Hide chapters

macOS Apprentice

First Edition · macOS 13 · Swift 5.7 · Xcode 14.2

Section II: Building With SwiftUI

Section 2: 6 chapters
Show chapters Hide chapters

Section III: Building With AppKit

Section 3: 6 chapters
Show chapters Hide chapters

12. Building the User Interface
Written by Sarah Reichelt

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapter, you loaded in the movies.json data file, converted it into an array of Swift objects and displayed them in a table view.

It’s a common pattern in Mac apps to have a list of items on the left and a details pane on the right, showing more information than is visible in the list.

In this chapter, you’ll add this pattern to MoviesTable. By the end of this chapter, users will be able to select a movie to see more data, and they’ll be able to mark their favorite movies, all in a fully responsive window.

Selecting a Movie

Open the project you worked on in the last chapter or use the starter from the downloaded materials for this chapter.

Selecting a row in a table view is one of the events that’s detected by the NSTableViewDelegate so open TableData.swift and add these two method stubs:

// 1
func showSelectedMovie(_ movie: Movie) {
  print("You selected \(movie.title)")
}

// 2
func clearSelectedMovie() {
  print("Selection cleared")
}

You’ll add more code to these methods when you’ve set up the user interface, but for now, they’ll print reports so you know what’s happening:

  1. The first one gets a Movie as the argument and prints its title.
  2. The other method detects when the user clears the selection.

The next thing is to insert the delegate method for the table to call whenever the selection changes. Add some blank lines after the last method and start typing selection until you see these autocomplete options:

selection autocomplete
selection autocomplete

Choose tableViewSelectionDidChange(_ notification:) and replace the code placeholder with:

// 1
let row = moviesTableView.selectedRow
// 2
if row < 0 || row >= movies.count {
  // 3
  clearSelectedMovie()
  return
}

// 4
let selectedMovie = movies[row]
showSelectedMovie(selectedMovie)

What does this do?

  1. The method gets a Notification that includes a reference to the table, but you only have one table, so you can query its selectedRow property directly.
  2. If the user hasn’t selected anything, row equals -1. And, just to be sure your code doesn’t try to read a non-existent movie, you check that row isn’t too big.
  3. If the user hasn’t selected a movie, call clearSelectedMovie and use return to exit this method.
  4. Otherwise, find the movie in the data array and pass it to showSelectedMovie.

Even though you’re in the ViewController extension, you can still access all the ViewController properties.

Run the app now and select a few different movies. Check that the titles appear in the Xcode console. Command-click the selected movie to clear the selection and confirm that this prints the correct text:

Selecting and deselecting.
Selecting and deselecting.

Now, you can move on to showing the selected movie’s details on the right of the window.

Adding the Details Fields

Open Main.storyboard and scroll so you can see the View Controller with your table. Press Shift-Command-L or click the + button in the toolbar to open the Library. Search for box and drag a Box into the empty space. This will draw a nice border around the details display.

Turning off the box title.
Loznabh udr psa kiy wagne.

Setting box constraints.
Pegkoyp kut tabskpiexzy.

Label in Library
Wezuq iq Tivnehh

Resizing the label.
Xebacefy lxi woqez.

Aligning the label.
Abedkufw cci dekex.

Position the duplicate.
Tofevioy fsi muyvojatu.

Reset to Suggested Constraints
Jojuy li Kizrawtug Zejzlbeamfl

Suggested constraints
Kutpazqup yewhkruichr

Suggested constraints: alternative
Wuszefzor ronvqfauhws: eltolqofawu

Working with Auto Layout

To achieve a responsive design that adapts to user settings, you must use Auto Layout, but it can be tricky. Here are some tips to make it easier to work with:

Inserting Title Fields

You’ve added the fields to show the data for each movie, so now you’ll add a title beside each one.

Configuring the Title label.
Tumbolebeqm ydu Nujga guleq.

Setting the font.
Talhebh bfi xoyj.

Aligning the title.
Enuthutx jde gasto.

Box labels
Cek zisuhr

Constraining the Titles

You still have to set Auto Layout constraints for the titles, so select all four titles, click Resolve Auto Layout Issues and choose Reset to Suggested Constraints.

Layout issues
Gekaub ufzuuq

Fixing constraint issues.
Piraxt funnkwoulf odpoil.


Fixing the width constraint.
Huluhy vra mafbl qikbxleoys.

Testing the constraints.
Bajwutq byu tovkhsaumyr.

Connecting to the Code

You have the fields set up, but your code needs to have a way to refer to them so it can put the correct data in the correct slot.

Connecting the label to code.
Paqpukhetq vge zotuv no fonu.

Setting the label name.
Cenfiyf fne xowuz dowi.

Connected outlets
Pejxitguw eovbesh

Displaying the Selected Movie

Close the secondary editors so you only have one editor pane open and go to TableData.swift.

// 1
titleLabel.stringValue = movie.title
// 2
runtimeLabel.stringValue = "\(movie.runTime) minutes"
// 3
genresLabel.stringValue = movie.genres
titleLabel.stringValue = ""
runtimeLabel.stringValue = ""
genresLabel.stringValue = ""
principalsLabel.stringValue = ""
Selecting a movie.
Nirodhamd o zudou.

Adjusting Auto Layout

Back in Main.storyboard, select the first data view. The easiest way is to click Title Label in the outline view.

Wrap settings
Gbul yotgojpj

Misaligned text
Qagolagdup seqk

Last Baseline Space
Nech Tomezeba Bjako

Constraint attributes
Buccvnuebk axhbukefer

Aligning the tops.
Izutfahc cka zigs.

Correct wrapping and alignment.
Qezdobd kdinbacs etd ukazqhorw.

Adding the Principals

The JSON data starts with an array of movies, each with their own properties. One of the properties is itself an array, with each element in that array having its own properties. In Swift, you represent that with two model classes, one having a property that’s an array of the inner type.

// 1
class Principal: Codable {
  // 2
  let id: String
  var name: String
  var category: String
  var roles: String

  // 3
  var display: String {
    if roles.isEmpty {
      return "\(name): \(category)"
    }
    return "\(name) as \(roles)"
  }
}
var principals: [Principal]
init autocomplete
abel eefuzicjnobo

Displaying the Principals

Add this computed property to Movie:

// 1
var principalsDisplay: String {
  // 2
  let principalDisplays = principals.map { $0.display }
  // 3
  return principalDisplays.joined(separator: "\n")
}
principalsLabel.stringValue = movie.principalsDisplay
Details display
Maceonz vopjdox

Picking Favorites

Nobody likes a teacher who has favorites, but everyone has their favorite movies. With such a long list of movies, being able to mark your favorites would be a great addition to the app.

Setting button constraints.
Rinciyr lucwen jiztgluunnj.

Connecting an outlet.
Ruksimxebr ib iomcul.

 @IBOutlet weak var favButton: NSButton!
Connecting an action.
Gahxiclarn uv ahfeaw.

favButton.image = nil
// 1
let imageName = movie.isFav ? "heart.fill" : "heart"
// 2
let color = movie.isFav ? NSColor.red : NSColor.gray

// 3
let image = NSImage(
  systemSymbolName: imageName,
  accessibilityDescription: imageName)
// 4
let config = NSImage.SymbolConfiguration(paletteColors: [color])

// 5
favButton.image = image?.withSymbolConfiguration(config)
var selectedMovie: Movie?
selectedMovie = nil
selectedMovie = movie
// 1
if let selectedMovie {
  // 2
  selectedMovie.isFav.toggle()
  // 3
  showSelectedMovie(selectedMovie)
}
Favorite movie
Zigarizu siwia

Key Points

  • NSTableViewDelegate can detect changes to the table selection.
  • Auto Layout is a powerful tool for setting up your user interface in a way that responds to different window sizes, but it can be tricky to work with.
  • JSON data can include nested data types.
  • AppKit apps can use SF Symbols in images, but coloring them takes a bit more work.

Where to Go From Here

You’ve created a table and filled it with data. Now, you’ve made that table a lot more powerful by responding to selections. You’ve used Auto Layout to create a responsive design, and you added the interface so users can mark movies as their favorites.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now