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

30. The Tag Location Screen
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.

There is a big button on the main screen of the app that says Tag Location. It only becomes active when GPS coordinates have been captured, and you use it to add a description and a photo to that location.

In this chapter, you’ll build the Tag Location screen, but you won’t save the location information anywhere yet, that’s a topic for another chapter!

This chapter covers the following:

  • The Screen: What the finished screen looks like and what it will do.
  • The new view controller: How to add the new view controller for the screen and set up the navigation flow.
  • Make the cells: Create the table view cells for displaying information.
  • Display location info: Display location info on screen via the new view.
  • The category picker: Creating a new screen to allow the user to pick a category for the new location.

The screen

The Tag Location screen is a regular table view controller with static cells. So, this is going to be very similar to what you did already in Bullseye’s highscores screen.

The finished Tag Location screen will look like this:

The Tag Location screen
The Tag Location screen

The description cell (the empty area above the Category cell) at the top contains a UITextView for text. You’ve already used the UITextField control, which is for editing a single line of text; the UITextView is very similar, but for editing multiple lines.

Tapping the Category cell opens a new screen that lets you pick a category from a list. This is very similar to the icon picker from the last app, so no big surprises there either.

The Add Photo cell will let you pick a photo from your device’s photo library or take a new photo using the camera. You’ll skip this feature for now and build that later on. Let’s not get ahead of ourselves and try too much at once!

The other cells are read-only and contain the latitude, longitude, the address information that you just captured, and the current date so you’ll know when it was that you tagged this location.

Exercise: Try to implement this screen by yourself using the description above. You don’t have to make the Category and Add Photo buttons work yet. Yikes, that seems like a big job! It sure is, but you should be able to pull this off. This screen doesn’t do anything you haven’t done previously. So if you feel brave, go ahead!

The new view controller

➤ Add a new file to the project using the Swift File template. Name the file LocationDetailsViewController.

import UIKit

class LocationDetailsViewController: UITableViewController {
  @IBOutlet weak var descriptionTextView: UITextView!
  @IBOutlet weak var categoryLabel: UILabel!
  @IBOutlet weak var latitudeLabel: UILabel!
  @IBOutlet weak var longitudeLabel: UILabel!
  @IBOutlet weak var addressLabel: UILabel!
  @IBOutlet weak var dateLabel: UILabel!

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

  @IBAction func cancel() {
    navigationController?.popViewController(animated: true)
  }
}
The Tag Location screen in the storyboard
Jze Luc Popeceoy jbkeok um tku xyalmwoemv

Hiding the navigation bar

You’ll notice that the Tag Scene (the Current Location View Controller) now has an empty navigation bar area. This is because it is now embedded in a Navigation Controller. You can either set the title (and/or make it a large title), or, you can hide the navigation bar altogether for the first view.

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)
  navigationController?.isNavigationBarHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
  super.viewWillAppear(animated)
  navigationController?.isNavigationBarHidden = false
}

Adding navigation buttons

Of course, the new screen won’t do anything useful yet. Let’s add some buttons.

Making the cells

There will be three sections in this table view:

Adding a row to a table view section
Avwogr e qen si i fucsu diev milwaak

The right detail cells

The second row from the first section, and the first, second and fourth rows in the last section will all use a standard cell style.

The cells with the Right Detail style
Zye monpt ninz mle Gonxb Kiluuq twmqe

The labels in the Tag Location screen
Qti yaqard un pno Kum Vogoyueg wtnaaw

Tappable cells

Only the Category and Add Photo cells should handle taps, so you have to set the cell selection color to None on the other cells.

Category and Add Photo now have a disclosure indicator
Zepekivx uxc Emh Hboye sok gahu i marnyegero ehsozayuj

The address cell

The empty cell in the last section is for the Address label. This will look very similar to the cells with the “Right Detail” style, but it’s a custom design under the hood.

The address detail label can have multiple lines
Nne ozlmuzr domoum xeyip vol lasi doxnulcu gimel

The description cell

So far, you’ve left the cell at the top empty. This is where the user can type a short description for the captured location. Currently, there is not much room to type anything. So first, you’ll make the cell larger.

Changing the height of a row
Jgeqyozg kqu kouzch ud i dir

The attributes for the text view
Xye ahydiqigil qal nfi xuch goal

Giving the section a header
Hokakk bhi caksaif e riinac

The finished design of the Tag Location screen
Swo muzawfiy bemofn em hka Qat Romevoim wpried

Connecting outlets

➤ Connect the Detail labels and the text view to their respective outlets. It should be obvious which one goes where. (Tip: Control-drag from the round yellow icon that represents the view controller to each of the labels. That’s the quickest way.)

The connections of the Location Details View Controller
Tju yomwizyaold iv dba Xowoyoud Dosiajf Zuoc Zoqqlehfif

Displaying location info

➤ Add two new properties to LocationDetailsViewController.swift:

var coordinate = CLLocationCoordinate2D(latitude: 0, 
                                       longitude: 0)
var placemark: CLPlacemark?
import CoreLocation

Structs

Unlike the objects you’ve seen before, CLLocationCoordinate2D is not a class, instead, it is a struct (short for structure).

struct CLLocationCoordinate2D {
  var latitude: CLLocationDegrees
  var longitude: CLLocationDegrees
}
typealias CLLocationDegrees = Double
struct CLLocationCoordinate2D {
  var latitude: Double
  var longitude: Double
}

Pass data to the details view

Back to the new properties that you just added to LocationDetailsViewController. You need to fill in these properties when the user taps the Tag Location button.

// MARK:- Navigation
override func prepare(for segue: UIStoryboardSegue, 
                         sender: Any?) {
  if segue.identifier == "TagLocation" {
    let controller = segue.destination 
                     as! LocationDetailsViewController
    controller.coordinate = location!.coordinate
    controller.placemark = placemark
  }
}

Display information on the Tag Location screen

viewDidLoad() is a good place to display the passed in values on screen.

override func viewDidLoad() {
  super.viewDidLoad()

  descriptionTextView.text = ""
  categoryLabel.text = ""

  latitudeLabel.text = String(format: "%.8f", 
                              coordinate.latitude)
  longitudeLabel.text = String(format: "%.8f", 
                               coordinate.longitude)

  if let placemark = placemark {
    addressLabel.text = string(from: placemark)
  } else {
    addressLabel.text = "No Address Found"
  }

  dateLabel.text = format(date: Date())
}
// MARK:- Helper Methods
func string(from placemark: CLPlacemark) -> String {
  var text = ""

  if let s = placemark.subThoroughfare {
    text += s + " "
  }
  if let s = placemark.thoroughfare {
    text += s + ", "
  }
  if let s = placemark.locality {
    text += s + ", "
  }
  if let s = placemark.administrativeArea {
    text += s + " "
  }
  if let s = placemark.postalCode {
    text += s + ", "
  }
  if let s = placemark.country {
    text += s
  }
  return text
}

Date formatting

To format the date, you’ll use a DateFormatter object. You’ve seen this class at work in the previous app. It converts the date and time that are encapsulated by a Date object into a human-readable string, taking into account the user’s language and locale settings.

private let dateFormatter: DateFormatter = {
  let formatter = DateFormatter()
  formatter.dateStyle = .medium
  formatter.timeStyle = .short
  return formatter
}()
private let dateFormatter = DateFormatter()
private let dateFormatter: DateFormatter = {
  // the code that sets up the DateFormatter object 
  return formatter
}()
func format(date: Date) -> String {
  return dateFormatter.string(from: date)
}
The Address label doesn’t fit well
Qvu Urpkatq qimal seomw’m jay hosp

Content Compression Resistance

You earlier configured the label to fit multiple lines of text, but the problem is that the two labels in the addres row don’t know how to get along with each other — the detail label is too full of itself and encroaches on the space of the Address label.

The label is not cut off by the address
Kbe yutoc oq jun moh ukh bh dri udhciyw

The category picker

When the user taps the Category cell, the app should show a list of category names:

The category picker
Nza velasefm muhwan

The view controller class

This is a new screen, so you need a new view controller. The way this works is very similar to the icon picker from Checklists. I’m just going to give you the source code and tell you how to hook it up.

import UIKit

class CategoryPickerViewController: UITableViewController {
  var selectedCategoryName = ""

  let categories = [
    "No Category",
    "Apple Store",
    "Bar",
    "Bookstore",
    "Club",
    "Grocery Store",
    "Historic Building",
    "House",
    "Icecream Vendor",
    "Landmark",
    "Park"]

  var selectedIndexPath = IndexPath()

  override func viewDidLoad() {
    super.viewDidLoad()

    for i in 0..<categories.count {
      if categories[i] == selectedCategoryName {
        selectedIndexPath = IndexPath(row: i, section: 0)
        break
      }
    }
  }

  // MARK:- Table View Delegates
  override func tableView(_ tableView: UITableView, 
        numberOfRowsInSection section: Int) -> Int {
    return categories.count
  }

  override func tableView(_ tableView: UITableView,
               cellForRowAt indexPath: IndexPath) -> 
               UITableViewCell {
    let cell = tableView.dequeueReusableCell(
                         withIdentifier: "Cell", 
                                    for: indexPath) 

    let categoryName = categories[indexPath.row]
    cell.textLabel!.text = categoryName

    if categoryName == selectedCategoryName {
      cell.accessoryType = .checkmark
    } else {
      cell.accessoryType = .none
    }
    return cell
  }

  override func tableView(_ tableView: UITableView, 
             didSelectRowAt indexPath: IndexPath) {
    if indexPath.row != selectedIndexPath.row {
      if let newCell = tableView.cellForRow(at: indexPath) {
        newCell.accessoryType = .checkmark
      }
      if let oldCell = tableView.cellForRow(
                       at: selectedIndexPath) {
        oldCell.accessoryType = .none
      }
      selectedIndexPath = indexPath
    }
  }
}
for category in categories {
for i in 0..<categories.count {
  let category = categories[i]
  . . . 
}
for (i, category) in categories.enumerated() {
  . . . 
}

The storyboard scene

➤ Open the storyboard and drag a new Table View Controller on to the canvas. Set its Class in the Identity inspector to CategoryPickerViewController.

The category picker in the storyboard
Qpa piduyomp xoqqeg ag mlo cmezdxiuxk

The Segue

➤ Switch back to LocationDetailsViewController.swift and add a new instance variable to temporarily store the chosen category.

var categoryName = "No Category"
override func viewDidLoad() {
  . . .
  categoryLabel.text = categoryName      // change this line
  . . .
// MARK:- Navigation
override func prepare(for segue: UIStoryboardSegue, 
                         sender: Any?) {
  if segue.identifier == "PickCategory" {
    let controller = segue.destination as! 
                     CategoryPickerViewController
    controller.selectedCategoryName = categoryName
  }
}
Selecting a new category
Haxumdihv o soc kibofudf

The unwind segue

In case you were wondering what the orange “Exit” icons in the storyboard are for, you now have your answer: unwind segues.

The Exit icon
Pja Uhil icuh

@IBAction func categoryPickerDidPickCategory(
                  _ segue: UIStoryboardSegue) {
  let controller = segue.source as! CategoryPickerViewController
  categoryName = controller.selectedCategoryName
  categoryLabel.text = categoryName
}
Control-dragging to the Exit icon to make an unwind segue
Yakqpeb-fnojtakl ti jhu Ipoq ower co xumo ar azkifj kenoa

The pop-up lists the unwind action methods
Pwo zis-ax jibcg rco uyyogt ubsaez tohwawt

// MARK:- Navigation
override func prepare(for segue: UIStoryboardSegue, 
                         sender: Any?) {
  if segue.identifier == "PickedCategory" {
    let cell = sender as! UITableViewCell
    if let indexPath = tableView.indexPath(for: cell) {
      selectedCategoryName = categories[indexPath.row]
    }
  }
}
You can find unwind segues in the Document Outline
Vio yoh fefs ebsody hazieq uz jte Nuzuqeyq Aiczobu

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