Home iOS & Swift Books UIKit Apprentice

33
Custom Table Cells Written by Matthijs Hollemans & Fahim Farook

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Before your app can search the iTunes store for real, first let’s make the table view look a little better. Appearance does matter when it comes to apps!

Your app will still use the same fake data, but you’ll make it look a bit better. This is what you’ll have by the end of this chapter:

The app with better looks
The app with better looks

In the process, you will learn the following:

  • Custom table cells and nibs: How to create, configure and use a custom table cell via nib file.
  • Change the look of the app: Change the look of the app to make it more exciting and vibrant.
  • Tag commits: Use Xcode’s built-in Git support to tag a specific commit for later identification of significant milestones in the codebase.
  • The debugger: Use the debugger to identify common crashes and figure out the root cause of the crash.

Custom table cells and nibs

For the previous apps, you used prototype cells to create your own table view cell layouts. That works great, but there’s another way. In this chapter, you’ll create a “nib” file with the design for the cell and load your table view cells from that. The principle is very similar to prototype cells.

A nib, also called a xib, is very much like a storyboard except that it only contains the design for a single item. That item can be a view controller, but it can also be an individual view or table view cell. A nib is really nothing more than a container for a “freeze dried” object that you can edit in Interface Builder.

In practice, many apps consist of a combination of nibs and storyboard files, so it’s good to know how to work with both.

Add a nib file

➤ Add a new file to the project. Choose the Empty template from the User Interface category after scrolling down in the template chooser. This will create a new empty nib.

Adding an empty nib to the project
Ocpipy ej uvfnh vix hu xnu mzawucb

Xib or nib?

I’ve been calling it a nib but the file extension is .xib. So what is the difference? In practice, these terms are used interchangeably. Technically speaking, a xib file is compiled into a nib file that is put into your application bundle. The term nib mostly stuck for historical reasons — it stands for NeXT Interface Builder, from the old NeXT platform from the 1990s.

The design of the cell
Yve megumn el bxi lekj

Set up Auto Layout constraints

The design for the cell is only 375 points wide but there are iOS devices with screens wider than that. The cell itself will resize to accommodate those larger screens, but the labels won’t, potentially causing their text to be cut off. You’ll have to add some Auto Layout constraints to make the labels resize along with the cell.

Symbol images

The image view will hold the artwork for the found item, such as an album cover, book cover, or an app icon. It may take a few seconds for these images to be loaded, so until then, it’s a good idea to show a placeholder image.

List of image suggestions
Cezt em uyuka ceddagleild

The cell now uses a symbol image
Kdu leww mun egif e mbbjik ugavi

The cell design with placeholder symbol image
Wgo hibn hubusg gekg pqosakufpar qljvuy opuxu

Setting colors

There’s one more change to make – the text color for the Artist Name label. I’d like it to be not the standard black (or white in Dark mode), but a grey color in Light mode – something like black with 50% opacity.

Register nib file for use in code

➤ In SearchViewController.swift, add these lines to the end of viewDidLoad():

let cellNib = UINib(nibName: "SearchResultCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "SearchResultCell")
let cellIdentifier = "SearchResultCell"

var cell: UITableViewCell! = tableView.dequeueReusableCell(
  withIdentifier: cellIdentifier)
if cell == nil {
  cell = UITableViewCell(
    style: .subtitle, reuseIdentifier: cellIdentifier)
}
func tableView(
  _ tableView: UITableView, 
  cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
  let cellIdentifier = "SearchResultCell"
  let cell = tableView.dequeueReusableCell(  // Change this line
    withIdentifier: cellIdentifier, 
    for: indexPath)        
  if searchResults.count == 0 {
    . . .
  } else {
    . . .
  }
  return cell
}

Add a custom UITableViewCell subclass

➤ Add a new file to the project using the Cocoa Touch Class template. Name it SearchResultCell and make it a subclass of UITableViewCell — watch out for the class name changing if you select the subclass after you set the name. “Also create XIB file” should be unchecked as you already have one.

@IBOutlet var nameLabel: UILabel!
@IBOutlet var artistNameLabel: UILabel!
@IBOutlet var artworkImageView: UIImageView!
Connect the labels and image view to Search Result Cell
Xacmezw mdu modewx amp ebuxe ziay zo Xeuykl Fayuhc Vipn

Use custom table view cell in app

➤ In SearchViewController.swift, change cellForRowAt to:

func tableView(
  _ tableView: UITableView, 
  cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
  let cellIdentifier = "SearchResultCell"
  let cell = tableView.dequeueReusableCell(
    withIdentifier: cellIdentifier, 
    for: indexPath) as! SearchResultCell                  // Change this
  if searchResults.count == 0 {
    cell.nameLabel.text = "(Nothing found)"               // Change this
    cell.artistNameLabel.text = ""                        // Change this
  } else {
    let searchResult = searchResults[indexPath.row]
    cell.nameLabel.text = searchResult.name               // Change this
    cell.artistNameLabel.text = searchResult.artistName   // Change this
  }
  return cell
}
Much better!
Qigz pukyud!

Use a constant for table cell identifier

Have you noticed that you’ve been using the string literal “SearchResultCell” in a few different places? It’s generally better to create a single constant for such occasions.

struct TableView {
  struct CellIdentifiers {
    static let searchResultCell = "SearchResultCell"
  }
}

A new “No results” cell

Remember our friend Justin Bieber? Searching for him now results in this:

The Nothing Found label now looks like this
Zda Hecsowd Qoabj waqov lul yaozh vibu tvik

The Nothing Found cell
Cpe Nupremj Guukd cift

struct TableView {
    struct CellIdentifiers {
      static let searchResultCell = "SearchResultCell"
      static let nothingFoundCell = "NothingFoundCell"    // New
    }
}
cellNib = UINib(nibName: TableView.CellIdentifiers.nothingFoundCell, bundle: nil)
tableView.register(
  cellNib, 
  forCellReuseIdentifier: TableView.CellIdentifiers.nothingFoundCell)
func tableView(
  _ tableView: UITableView, 
  cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
  if searchResults.count == 0 {
    return tableView.dequeueReusableCell(
      withIdentifier: TableView.CellIdentifiers.nothingFoundCell, 
      for: indexPath)
  } else {
    let cell = tableView.dequeueReusableCell(withIdentifier:
      TableView.CellIdentifiers.searchResultCell, 
      for: indexPath) as! SearchResultCell

    let searchResult = searchResults[indexPath.row]
    cell.nameLabel.text = searchResult.name
    cell.artistNameLabel.text = searchResult.artistName
    return cell
  }
}
The new Nothing Found cell in action
Nbo vow Rimmuqb Nuazt qabz es ancauw

Source Control Changes

But before you commit your changes, take a look at SearchViewController.swift in your editor view. You might notice some blue lines along the gutter like this:

Source control change indicator in editor view
Jiewra nagngip lvetqu uffefayof ak ikojet ruer

Change the look of the app

The app currently looks a little bland. Let’s cheer it up a little by giving it more vibrant colors.

The new theme in action
Tku cez mpese ug ojdeah

The updated theme in action
Kka ahjojok mnelo ib attiaz

Change the row selection color

Currently, tapping a row highlights it in gray. This doesn’t go so well with the new color theme. So, you’ll give the row selection the same color tint.

override func awakeFromNib() {
  super.awakeFromNib()
  // New code below
  let selectedView = UIView(frame: CGRect.zero)
  selectedView.backgroundColor = UIColor(named: "SearchBar")?.withAlphaComponent(0.5)
  selectedBackgroundView = selectedView
}
The new selection color in action
Tji nay mucajjeug vezub aq orceix

Add app icons

While you’re at it, you might as well give the app an icon.

All the icons in the asset catalog
Osy zha ovovw ey qgo ivzoq rosozem

The app icon
Ldu omy ofah

Show keyboard on app launch

One final user interface tweak I’d like to make is that the keyboard should be immediately visible when you start the app so the user can start typing right away.

searchBar.becomeFirstResponder()

Tag commits

If you look through the various commits you’ve made so far, you’ll notice a bunch of strange numbers, such as “ba03dbb”:

The commits listed in the history window have weird numbers
Gwu nejqilr mefgez al bdo dikxihd gacwif nizu xeoyp vuqxobp

Tagging a commit in Xcode
Mahdojh u qigyac ey Rnamo

The new tag in Xcode
Ypa fem mag al Sdade

The debugger

Xcode has a built-in debugger. Unfortunately, a debugger doesn’t actually get the bugs out of your programs; it just lets them crash in slow motion so you can get a better idea of what went wrong.

Index out of range bug

Let’s introduce a bug into the app so that it crashes — knowing what to do when your app crashes is very important.

func tableView(
  _ tableView: UITableView,
  numberOfRowsInSection section: Int
) -> Int {
  if !hasSearched {
    . . .
  } else if searchResults.count == 0 {
    . . .
  } else {
    return searchResults.count + 1  // This line changes
  }
}
The Xcode debugger appears when the app crashes
Rfa Pripu fesuylap utwoafd nmib hvo etj znokpah

Printing the value of indexPath.row
Tvutxocs kdo hicuu iw uxvipMink.jag

(Int) $R0 = 3
Printing the searchResults array
Klavgigg yli faobbhPogavjw ejnum

Storyboard outlet bug

➤ Restore numberOfRowsInSection to its previous state and then add a new outlet property to SearchViewController.swift:

@IBOutlet var searchBar2: UISearchBar!
2020-08-20 08:13:39.945773-0400 StoreSearch[1038:12267770] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<StoreSearch.SearchViewController 0x7fc7d690a6e0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchBar2.'
*** First throw call stack:
  . . . 
this class is not key value coding-compliant for the key searchBar2
Crash in system library
Ybidl ax gysvob qovxotz

A more detailed call stack
E qoyi dijoubow tusk wwald

Adding an Exception Breakpoint
Irdohn ag Oyjoxnoek Rtoukpeoyy

After adding the Exception Breakpoint
Uxsim oprilk dca Ekluyxeoh Creurpairj

Xcode now halts the app at the point the exception occurs
Jqoyo cud hitrx nhi alc us cye baovs mne awwajwaan illapq

The build log

If you’re wondering what Xcode actually does when it builds your app, then take a peek at the Report navigator. It’s the last tab in the navigator pane.

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.