Sans Pareil Technologies, Inc.

Key To Your Business

Lab 8: Navigation Controller


In this session we will use a Navigation Controller to achieve the same work flow we used in Lab7. We will add an additional scene that will present a confirmation screen to the user before saving the information. We will use a Navigation Controller, which will render a standard Navigation Bar at the top of the application, with a title and a forward and backward button as appropriate.

Create a new project named lab8. Copy the Person.swift file from Lab7 to the project.

Storyboard

We will use three View Controllers in our storyboard. Drag and drop two View Controller objects from the object library to the storyboard. Click the initial scene and click the Editor->Embed In->Navigation Controller menu to let a Navigation Controller take over the initial view controller.
Screen Shot 2017-10-16 at 21.43.39

Layout the initial scene similar to lab7. Set up the view controller outlets also similar to lab7. Control+Drag a connection from the Edit (Bar Button Item) button to the second scene and select “Show” as the option. Set the title for the Navigation Bar to “Details”.
Screen Shot 2017-10-16 at 21.46.57

Layout the second scene similar to lab7 using an EditViewController. Drag and drop a Navigation Item to the second scene, to bring in the Navigation Bar into the scene. Set its title to “Edit”, and drop a Bar Button Item to it and name it “Save”. Control+Drag a connection from the Save button to scene three.
Screen Shot 2017-10-16 at 21.47.06

Layout the third scene with just a Text View as follows. Create a SaveViewController and use it with the scene. Add a Navigation Item and Bar Button Item to the scene. Connect the Save button to the “Exit” icon. Choose the action function from the first ViewController to connect to. Create and connect an outlet in the view controller to the text view.
Screen Shot 2017-10-16 at 21.47.16

ViewController

The modified ViewController is as follows. Note that we have modified the updatePerson segue action function to use the SaveViewController, since that is the controller from which the “Exit” action is triggered.

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    createPerson()
    if let person = repository.by(email: emailAddress) {
      name.text = person.name
      firstName.text = person.firstName
      middleName.text = person.middleName
      lastName.text = person.lastName
      email.text = person.email
      age.value = Float(person.age)
    }
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let controller = segue.destination as! EditViewController
    controller.person = repository.by(email: emailAddress)
  }
  
  @IBAction func updatePerson(segue: UIStoryboardSegue) {
    let controller = segue.source as! SaveViewController
    if var person = repository.by(email: emailAddress) {
      if let p = controller.person {
        person.firstName = p.firstName
        person.middleName = p.middleName
        person.lastName = p.lastName
        person.age = p.age
        let _ = repository.save(person: person)
        
        name.text = person.name
        firstName.text = person.firstName
        middleName.text = person.middleName
        lastName.text = person.lastName
        email.text = person.email
        age.value = Float(person.age)
      }
    }
  }
  
  private func createPerson() {
    let person = repository.by(email: emailAddress)
    if person == nil {
      let p = Person(firstName:"User", middleName: nil, lastName:"One", email:emailAddress, age: 10)
      let _ = repository.save(person: p)
    }
  }
  
  private let repository = PersonRepository.instance
  private var emailAddress = "user@one.com"
  
  @IBOutlet weak var name: UILabel!
  @IBOutlet weak var firstName: UITextField!
  @IBOutlet weak var middleName: UITextField!
  @IBOutlet weak var lastName: UITextField!
  @IBOutlet weak var email: UITextField!
  @IBOutlet weak var age: UISlider!
}

EditViewController

The EditViewController now has a prepareForSegue function to handle the transition from the edit scene to the confirmation scene.

class EditViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    if let p = person {
      firstName.text = p.firstName
      middleName.text = p.middleName
      lastName.text = p.lastName
      email.text = p.email
      age.value = Float(p.age)
    }
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let email = person?.email {
      let controller = segue.destination as! SaveViewController
      var person = repository.by(email: email)!
      person.firstName = firstName.text!
      person.middleName = middleName.text
      person.lastName = lastName.text!
      person.email = email
      person.age = Int16(age.value)
      controller.person = person
    }
  }
  
  @IBOutlet weak var firstName: UITextField!
  @IBOutlet weak var middleName: UITextField!
  @IBOutlet weak var lastName: UITextField!
  @IBOutlet weak var email: UITextField!
  @IBOutlet weak var age: UISlider!
  var person : Person? = nil
  private let repository = PersonRepository.instance
}

SaveViewController

The SaveViewController uses a Text View to display a HTML representation of the edited information for the person. The user may either go back to the “Edit” scene, or “Save” and go back to the initial “Details” scene.

extension NSAttributedString {
  
  internal convenience init?(html: String) {
    guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
      return nil
    }
    
    guard let attributedString = try? NSMutableAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else {
      return nil
    }
    
    self.init(attributedString: attributedString)
  }
}

class SaveViewController: UIViewController {

    override func viewDidLoad() {
      super.viewDidLoad()
      display()
    }

    override func didReceiveMemoryWarning() {
      super.didReceiveMemoryWarning()
    }

  @IBOutlet weak var textView: UITextView!
  var person : Person? = nil
  
  private func display() {
    if let p = person {
      let middleName = p.middleName != nil ? p.middleName! : ""
      let email = p.email != nil ? p.email! : ""
      
      let html =
"""
<div>
  <label>First Name:</label> <label>\(p.firstName)</label><br/>
  <label>Middle Name:</label> <label>\(middleName)</label><br/>
  <label>Last Name:</label> <label>\(p.lastName)</label><br/>
  <label>Email:</label> <label>\(email)</label><br/>
  <label>Age:</label> <label>\(p.age)</label>
</div>
<br/><br/>
<div>Click <b>Save</b> button to save this information, or the <b>Edit</b>
button to go back and re-edit the information.</div>
"""

      let attrStr = NSAttributedString(html: html)
      textView.attributedText = attrStr
    }
  }
}


Xcode 9.0.1+ Modification
Xcode 9.0.1 and up will need a slight modification to the NSAttributedString extension.

extension NSAttributedString {

    internal convenience init?(html: String) {
        guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
            return nil
        }

        guard let attributedString = try? NSMutableAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) else {
            return nil
        }

        self.init(attributedString: attributedString)
    }
}
extension NSAttributedString {

    internal convenience init?(html: String) {
        guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
            return nil
        }

        guard let attributedString = try? NSMutableAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) else {
            return nil
        }

        self.init(attributedString: attributedString)
    }
}