Home iOS & Swift Books iOS Apprentice

22
Navigation Controllers Written by Eli Ganim

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.

At this point the high scores screen contains a table view displaying a handful of fixed data rows. However, the idea is that the high scores will be updated as the player scores them. Therefore, you need to implement the ability to add items.

In this chapter you’ll expand the app to have a navigation bar at the top. Whenever you click a row, a new screen will show up that lets the user insert the name of the high scorer. When you tap Done, the new item will be added to the list.

This chapter covers the following:

  • Navigation controller: Add a navigation controller to the app to allow navigation between screens.
  • Delete rows: Add the ability to delete rows from a list of items presented via a table view.
  • The Add Item screen: Create a new screen from which players can insert their name.

Navigation controller

First, let’s add the navigation bar. You may have seen in the Objects Library that there is an object named Navigation Bar. You can drag this into your view and put it at the top, but, in this particular instance, you won’t do that.

Instead, you will embed your view controller in a navigation controller.

Next to the table view, the navigation controller is probably the second most used iOS user interface component. It is the thing that lets you go from one screen to another:

A navigation controller in action
A navigation controller in action

The UINavigationController object takes care of most of this navigation stuff for you, which saves a lot of programming effort. It has a navigation bar with a title in the middle and a “back” button that automatically takes the user back to the previous screen. You can put a button (or several buttons) of your own on the right.

Adding a navigation controller

Adding a navigation controller is really easy.

Putting the view controller inside a navigation controller
Nimgiwc yge naoh pagtjagwaw uwhadu i bebenuraux waclwovrex

The navigation controller is now linked with your view controller
Vci xewariqaun capzcilwax us zon pohriv hoqm viec naak bocvmarsoc

Segue types

What are the possible Segues and what do they mean? Here is a brief explanation of each type of segue:

Setting the navigation bar title

➤ Go back to the storyboard, select Navigation Item under View Controller Scene in the Document Outline, switch to the Attributes Inspector on the right-hand pane, and set the value of Title to Bullseye.

Changing the title in the navigation bar
Bkibqosk vku xedlo ah lve gatimituut rad

Add a navigation item to the view controller
Itj i zucijojoup ivex ne syu rauf tuyjrapxok

Navigation bar with title
Keqituwaem boj tang dodse

Deleting rows

Imagine you let a friend enjoy the amazing Bullseye game on your iPhone and he reaches a high score you can’t beat. That would be really annoying!

Swipe-to-delete in action
Qxala-du-cicixu ox ejvaas

Swipe-to-delete

Swipe-to-delete is very easy to implement.

override func tableView(
                _ tableView: UITableView, 
        commit editingStyle: UITableViewCell.EditingStyle,
         forRowAt indexPath: IndexPath) {
  // 1
  items.remove(at: indexPath.row)
  
  // 2  
  let indexPaths = [indexPath]
  tableView.deleteRows(at: indexPaths, with: .automatic)
}

Adding a navigation button

Now that you can remove items from the list, it would be useful to also have a way to reset the high scores list to its initial state. You’ll add a button to the right of the navigation bar to reset the high scores list to its initial state.

Dragging a Bar Button Item into the navigation bar
Plamvanq o Qeq Jomlov Uwor idwe xsa bemihodaob cuq

The app with the reset button
Dca ufy gonn dfo vebih foghor

Making the navigation button do something

If you tap on your new reset button, it doesn’t actually do anything. That’s because you haven’t hooked it up to an action. You got plenty of exercise with this for Bullseye, so it should be child’s play for you by now.

// MARK:- Actions
@IBAction func resetHighScores() {
}
Control-drag from Reset button to High Scores View Controller
Yafrzom-yzud jkex Qixac yagxob lu Fuqc Szexop Wuux Rotcfedtem

  override func viewDidLoad() {
    super.viewDidLoad()
    resetHighScores()
  }
  
  // MARK:- Actions
  @IBAction func resetHighScores() {
    items = [HighScoreItem]()
    let item1 = HighScoreItem()
    item1.name = "The reader of this book"
    item1.score = 50000
    items.append(item1)
    
    . . .
    
    let item5 = HighScoreItem()
    item5.name = "Eli"
    item5.score = 500
    items.append(item5)
    tableView.reloadData()
  }

Saving and loading high scores

You probably noticed that the high scores data resets every time you restart the app. That’s because you’re not saving or loading the data.

class HighScoreItem : Codable
class PersistencyHelper {
  static func saveHighScores(_ items: [HighScoreItem]) {
    let encoder = PropertyListEncoder()
    do {
      let data = try encoder.encode(items)
      try data.write(to: dataFilePath(), options: Data.WritingOptions.atomic)
    } catch {
      print("Error encoding item array: \(error.localizedDescription)")
    }
  }
  
  static func loadHighScores() -> [HighScoreItem] {
    var items = [HighScoreItem]()
    let path = dataFilePath()
    if let data = try? Data(contentsOf: path) {
      let decoder = PropertyListDecoder()
      do {
        items = try decoder.decode([HighScoreItem].self, from: data)
      } catch {
        print("Error decoding item array: \(error.localizedDescription)")
      }
    }
    return items
  }
  
  static func dataFilePath() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory,
                                          in: .userDomainMask)
    return paths[0].appendingPathComponent("HighScores.plist")
  }
}
  override func viewDidLoad() {
    super.viewDidLoad()
    items = PersistencyHelper.loadHighScores()
    if (items.count == 0) {
      resetHighScores()
    }
  }

Adding new high scores

There’s one piece missing: How do you add new high scores to the list? Obviously, it needs to happen when a game ends.

@IBAction func startNewGame() {
    addHighScore(score)
    . . .
}
func addHighScore(_ score:Int) {
  // 1
  guard score > 0 else {
    return;
  }

  // 2
  let highscore = HighScoreItem()
  highscore.score = score
  highscore.name = "Unknown"

  // 3
  var highScores = PersistencyHelper.loadHighScores()
  highScores.append(highscore)
  highScores.sort { $0.score > $1.score }
  PersistencyHelper.saveHighScores(highScores)
}

The Edit High Score screen

You’ve learned how to add new high scores, but all of them contain the same player name - “Unknown”. You will need to provide a way to change the name. For that you will create a new screen with a text field to change the player’s name. It will look like this:

The Edit High score screen
Rwa Ehif Puhm pledi bqdioc

Adding a new view controller to the storyboard

➤ Go to the Objects Library and drag a new Table View Controller (not a regular view controller) on to the storyboard canvas.

Dragging a new Table View Controller into the canvas
Pkesbadl i sud Memyi Buof Madhwuwveb egfu fne kuflon

Control-drag from the Add button to the new table view controller
Zufbded-nxam zwed rba Ohw tahtow qu wco wus kanni loan nomlwepsed

The Action Segue popup
Cfe Owpaoh Piyui lariw

A new segue is added between the two view controllers
O jox wegaa id egbex bimguuk lhu vti koaz xunbwovbutp

Customizing the navigation bar

So now you have a new table view controller that slides into the screen when you press a cell. However, this screen is empty. Data input screens usually have a navigation bar with a Cancel button on the left and a Done button on the right. In some apps the button on the right is called Save or Send. Pressing either of these buttons will close the screen, but only Done will save your changes.

The navigation bar items for the new screen
Wxe lamegevaor zif oqabb qig phu nom cnqeoz

The Cancel and Done buttons in the app
Cso Yaqnug ayw Cowu wufzixh un tsa efl

Making your own view controller class

You created a custom view controller for the About screen. Do you remember how to do it on your own? If not, here are the steps:

import UIKit

class EditHighScoreViewController: UITableViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
  }
}

Making the navigation buttons work

There’s still one issue — the Cancel and Done buttons ought to close the Add Item screen and return the app to the main screen, but tapping them has no effect yet.

// MARK:- Actions
@IBAction func cancel() {
  navigationController?.popViewController(animated: true)
}

@IBAction func done() {
  navigationController?.popViewController(animated: true)
}
Control-dragging from the bar button to the view controller
Wuhfgaf-ynugbirn xfef xku tuh sorsix ki hle ruax tegksizwuk

Container view controllers

You’ve read that one view controller represents one screen, but here you actually have two view controllers for each screen: a Table View controller that sits inside a navigation controller.

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.