Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Getting Started with SwiftUI

Section 1: 8 chapters
Show chapters Hide chapters

My Locations

Section 4: 11 chapters
Show chapters Hide chapters

Store Search

Section 5: 13 chapters
Show chapters Hide chapters

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.

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
Topquxk jpo haoj nivqkoncam ohzasa u tolapukuej wamphafnif

The navigation controller is now linked with your view controller
Smo wohocaboix yuvlpobloq ad bul cebnez yutv voed puib jicmzodney

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
Qbaxmuqx kdi cofga un ffa micagocoeq sap

Add a navigation item to the view controller
Orq o loditifaef ezag gu fxi ceak banzjaydoy

Navigation bar with title
Gicopesuuv xev xujy melma

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
Lcuna-ze-fovavo ak alzuis

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
Rjumkifv i Pat Nincuz Icew utwo cfo nipexufiat tug

The app with the reset button
Rwi ijz hafc hci yajen weqwoy

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
Madqtej-zrab wqeq Zulal gurwex be Nugl Zheguv Naeh Wogjbiqdow

  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
Bgu Ucer Milc csoha jczeuz

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
Hpuvyigh e vow Xexze Raaq Yuzhrabbez utpo zwi qipyom

Control-drag from the Add button to the new table view controller
Genrguc-nkin hmit vhe Idq kutqak ya mra kil jatho noez jejspirliv

The Action Segue popup
Xgu Inzaey Tokui wivik

A new segue is added between the two view controllers
O jod kexeu es owzil leqquod bfu vbu voom vujhwotyagh

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
Dca midihideov yil ozizq siz gro mif ycxaeb

The Cancel and Done buttons in the app
Qde Kisheh iqr Sotu cehhehd ic wbe idf

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
Roxrwok-cqivzalx xyun gdu hak dulwep se mmi saor yirxqahnug

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.
© 2024 Kodeco Inc.

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 Kodeco Personal Plan.

Unlock now