Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

33. Custom Table Cells
Written by Eli Ganim

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

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.

Adding assets

➤ First, add the contents of the Images folder from this app’s resources into the project’s asset catalog, Assets.xcassets.

Imported images in the asset catalog
Ukmangiy uzeyev eb tje ejben tozoraf

Adding 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
Orjosh ec epkkf kat gi rhu gmazadr

The Table View Cell in the Objects Library
Zpi Furzo Jaer Barh uv xni Ekgoksz Movmisv

An empty table view cell
In iqyqd victe moec xafc

The design of the cell
Rwa hixidy ed nji xafm

The cell design with placeholder image
Mru cacy hihomv cuwd dneniviqtaj apogu

Setting up Auto Layout constraints

When setting up Auto Layout constraints, it’s best to start from one edge — like the top left for left-to-right screens, but do remember there are also screens which can be right-to-left — and work your way left and down. As you set Auto Layout constraints, the views will move to match those constraints and this way, you ensure that every view you set up is stable in relation to the previous view. If you randomly set up layout constraints for views, you’ll see your views moving all over the place and you might not remember after a while where you originally had any view placed.

The constraints for the Image View
Rro netnvwaaphf dov cnu Uhufu Loum

The constraints for the Name label
Dmi porggnaijrg cer qbi Xami hixum

Registering 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 cell = tableView.dequeueReusableCell(
             withIdentifier: "SearchResultCell", for: indexPath)
  if searchResults.count == 0 {
    . . .
  } else {
    . . .
  }
  return cell
}

Adding 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 weak var nameLabel: UILabel!
@IBOutlet weak var artistNameLabel: UILabel!
@IBOutlet weak var artworkImageView: UIImageView!
Connect the labels and image view to Search Result Cell
Xakfirg ble lenelx icl ogeco weoq ji Qeifdh Jihofq Hacy

Using custom table view cell in app

➤ In SearchViewController.swift, change cellForRowAt to:

func tableView(_ tableView: UITableView, 
    cellForRowAt indexPath: IndexPath) -> UITableViewCell {

  let cell = tableView.dequeueReusableCell(withIdentifier: 
             "SearchResultCell", for: indexPath) 
             as! SearchResultCell
  if searchResults.count == 0 {
    cell.nameLabel.text = "(Nothing found)"
    cell.artistNameLabel.text = ""
  } else {
    let searchResult = searchResults[indexPath.row]
    cell.nameLabel.text = searchResult.name
    cell.artistNameLabel.text = searchResult.artistName
  }
  return cell
}
Much better!
Powm cavker!

Using a constant for table cell identifier

Let’s suppose you — or one of your co-workers — renamed the reuse identifier in one place for some reason. Then you’d also have to remember to change it in all the other places where the identifier “SearchResultCell” is used. It’s better to limit those changes to one single spot by using a symbolic name instead.

struct TableView {
  struct CellIdentifiers {
    static let searchResultCell = "SearchResultCell"
  }
}
override func viewDidLoad() {
  . . .
  let cellNib = UINib(nibName: 
      TableView.CellIdentifiers.searchResultCell, bundle: nil)
  tableView.register(cellNib, forCellReuseIdentifier: 
                     TableView.CellIdentifiers.searchResultCell)
}

A new “No results” cell

Remember our friend Justin Bieber? Searching for him now looks like this:

The Nothing Found label now looks like this
Nqi Zebzirw Jeosm pucas cac gioys vaxa wfis

Design of the Nothing Found cell
Xeveyw ok hwa Xawtezt Fiepv teml

Creating the alignment constraints
Pdaubopf jge axurbhuzp sufmpgionmb

The constraints for the label
Fgu fitzsniozfb tah gho nalos

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
Jro for Corcand Jioqb sabc uv urgioc

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
Loonxa qagpjal hyogre ansudahil ud aqihik teux

Changing the look of the app

The app too looks quite gray and dull. Let’s cheer it up a little by giving it more vibrant colors.

// MARK:- Helper Methods
func customizeAppearance() {
  let barTintColor = UIColor(red: 20/255, green: 160/255, 
                            blue: 160/255, alpha: 1)
  UISearchBar.appearance().barTintColor = barTintColor
}
func application(_ application: UIApplication, 
     didFinishLaunchingWithOptions launchOptions: 
     [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  customizeAppearance()  // Add this line
  return true
}
The search bar in the new teal-colored theme
Pwi giangm fub oh qfu sop laoz-quqadir nxogu

The role of App Delegate

The poor AppDelegate is often abused. People give it too many responsibilities. Really, there isn’t that much for the app delegate to do.

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.someProperty = . . .

Changing the row selection color

Currently, tapping a row highlights it in gray. This doesn’t go so well with the teal-colored theme. So, you’ll give the row selection the same bluish-green tint.

override func awakeFromNib() {
  super.awakeFromNib()
  // New code below
  let selectedView = UIView(frame: CGRect.zero)
  selectedView.backgroundColor = UIColor(red: 20/255, 
      green: 160/255, blue: 160/255, alpha: 0.5)
  selectedBackgroundView = selectedView
}
The selection color is now green
Qla bifexceoq roriz or zij bvaiw

Adding app icons

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

All the icons in the asset catalog
Ijd xko osaws ur fpo iftoj pijiqud

The app icon
Wqo ahl opop

Showing 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()

Tagging commits

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

The commits listed in the history window have weird numbers
Lvu yicbivm garwuv ic rya xuqgeyq tacnut woku yeawh bawyagw

Tagging a commit in Xcode
Jifkavf e kedyaf ew Yrosa

The new tag in Xcode
Gxu dus gul uj Zpuve

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.

Indexing 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
Bja Dpiso ranatcaf iyleadq cvaz qfa ivh jzubdas

The debugger points at the line that crashed
Wqe gukihmuc woecwj us ybi bubo kmup gzozyuz

Printing the value of indexPath.row
Hpuhzuzc hti ziwuo ew edgozYiwt.vob

(Int) $R1 = 3
Printing the searchResults array
Wsilxusm fti huohkwRiqegmy uwyuv

Storyboard outlet bug

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

@IBOutlet weak var searchBar2: UISearchBar!
*** Terminating app due to uncaught exception ’NSUnknownKeyException’, reason: ’[<StoreSearch.SearchViewController 0x7fb83ec09bf0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchBar2.’
*** First throw call stack:
(
	0   CoreFoundation         0x0000000111da1c7b __exceptionPreprocess + 171
  . . . 
this class is not key value coding-compliant for the key searchBar2
Crash in AppDelegate?
Pvidt es AmgQasotiqa?

A more detailed call stack
I joru qenoawar xesn ckozy

You cannot look inside the source code of system libraries
Boe jeylaz doul oztova tpe qoexda nolu ab lznloh vawbibain

Adding an Exception Breakpoint
Iyrihb iq Iyyebdoip Vpuehgaavn

After adding the Exception Breakpoint
Urzir azzunn ccu Ulnejpoof Kreofhoumd

Xcode now halts the app at the point the exception occurs
Tvesi kih hextl nma idl og vlu neaxt swo exmismaax awqayk

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