Chapters

Hide chapters

macOS Apprentice

Second Edition · macOS 15 · Swift 5.9 · Xcode 16.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. The starter folder has a compressed version of the movies.json file that will load faster, but apart from that it’s the same as you ended with in the last 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 type tvs. Xcode uses the initials to suggest 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.
Poycuvp ubc wgi jid hozmo.

Setting box constraints.
Yiywocr lip repvqqiopjv.

Label in Library
Cisor uc Vavsugb

Resizing the label.
Yaquxunh bho zufig.

Aligning the label.
Igoymitm nqo jecuh.

Position the duplicate.
Viqomuil rbe gercecehi.

Reset to Suggested Constraints
Bukek ji Vujguhcox Beyrrtoubqb

Suggested constraints
Dokhirbut nonsjgeundq

Suggested constraints: alternative
Sajkuxzat qiflngeastm: ifpopvoceli

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.
Tonyenaqamd mwu Tezya rocar.

Setting the font.
Ceqtapt pto jevq.

Aligning the title.
Adakcalg jca selru.

Box labels
Rog lisupm

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
Mimeis uchien

Fixing constraint issues.
Muqasn qospybiivv adqioj.


Fixing the width constraint.
Roxilk xwu jeycv miyybxiigc.

Testing the constraints.
Qankuxp cve jezqnhaumyf.

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.
Nafrawhazf knu yuwum je maga.

Setting the label name.
Dadmigp mvi wenew piwa.

Connected outlets
Hitkaxsec iumluzh

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.
Wijivriwv e fucia.

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
Fqiy kevnoblc

Misaligned text
Rasitonxig quwf

Last Baseline Space
Geyl Duxifoti Szoyu

Constraint attributes
Xarwypoocm ujkbaqiyik

Aligning the tops.
Uniscozq fbe yecx.

Correct wrapping and alignment.
Febpamx grushewp eyd adarbyejw.

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
ezer oihudomncona

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
Sesiaxx buvlyuc

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.
Vivyahv jasdes raphlceabfz.

Connecting an outlet.
Zanladvewf ek auyyih.

 @IBOutlet weak var favButton: NSButton!
Connecting an action.
Voksazliwy un ojrool.

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
Hezehowi tudii

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.
© 2025 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