Sans Pareil Technologies, Inc.

Key To Your Business

Lab 9 : Tab Bar Controller


In this exercise, we will create a Tabbed application which uses a Tab Bar Controller to manage multiple tabs in our application.

Create a new project named lab9, which uses a “Tabbed App” template. Note that the template creates two views by default. We could also have dragged and dropped a Tab Bar Controller into the storyboard, which would have performed the same action for us.

Click each of the two view controllers, and in the bottom tab bar select the button icons, and set their title to “College” and “Person” respectively. We will use the application to manage a college entity, and a person entity that belong to the college.

College

Create a new Swift file named College. We will create a simple model object and repository for managing college entities.

import Foundation

struct College {
  let id = UUID()
  var name: String
  var description: String?
  var address: Address

  struct Address {
    var street : [String]
    var city: String
    var state: String
    var postalCode: String
    let country: String
  }
}

class CollegeRepository {
  static let instance = CollegeRepository()

  func by(id: UUID) -> College? {
    return colleges[id]
  }

  func by(name: String) -> [College] {
    var result = [College]()
    for college in colleges.values {
      if college.name == name { result.append(college) }
    }
    return result
  }

  func by(city: String) -> [College] {
    var result = [College]()
    for college in colleges.values {
      if college.address.city == city { result.append(college) }
    }
    return result
  }

  func save(college: College) -> Bool {
    if let _ = colleges.index(forKey: college.id) {
      colleges.updateValue(college, forKey: college.id)
      return false
    }
    
    colleges[college.id] = college
    return true
  }

  func remove(college: College) -> Bool {
    if let index = colleges.index(forKey: college.id) {
      colleges.remove(at: index)
      return true
    }

    return false
  }

  private init() {}

  private var colleges = [UUID:College]()
}

Storyboard

Refactor the auto generated First/Second view controllers to CollegeViewController and PersonViewController. Set the title for the tab bar items to College and Person.

Embed the first view into a navigation controller as in lab8. Set up a navigation hierarchy with three view controllers as in lab8 for editing a college entity.

Embed the second view into a navigation controller. Set up a navigation hierarchy with three view controllers as in lab8 for editing a person entity. Note that for this part, you can copy over most of the code and UI layout from lab8.

Add a view controller to the storyboard, and control drag a connection from the tab bar controller to the view of the new view controller. Chose “Relationship Segue” option from the popup. This will create a new tab bar item and associate the view controller with the tab bar.
Screen Shot 2017-10-23 at 19.15.58

College controllers

CollegeViewController
Note that this is the refactored FirstViewController that Xcode generated automatically. Add outlets for the UI components used to represent a college entity, as well as segue action functions.

class CollegeViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    if college == nil {
      college = createCollege()
    }

    bind()
  }

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

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let controller = segue.destination as! CollegeEditViewController
    controller.college = college
  }

  @IBAction func updateCollege(segue: UIStoryboardSegue) {
    let controller = segue.source as! CollegeSaveViewController
    if let c = controller.college {
      let _ = repository.save(college: c)
      college = repository.by(id: c.id)
      bind()
    }
  }

  private func bind() {
    if let c = college {
      name.text = c.name
      street.text = c.address.street[0]
      city.text = c.address.city
      state.text = c.address.state
      postalCode.text = c.address.postalCode
      country.text = c.address.country
      desc.text = c.description
    }
  }

  private func createCollege() -> College {
    return College(name: "Moraine Valley", description: "Moraine Valley Community College", address: College.Address(street: ["9000 W. College Pkwy"], city: "Palos Hills", state: "IL", postalCode: "60465", country: "USA"))
  }

  @IBOutlet weak var name : UITextField!
  @IBOutlet weak var street : UITextField!
  @IBOutlet weak var city : UITextField!
  @IBOutlet weak var state : UITextField!
  @IBOutlet weak var postalCode : UITextField!
  @IBOutlet weak var country : UITextField!
  @IBOutlet weak var desc : UITextView!
  var college : College?

  private let repository = CollegeRepository.instance
}


CollegeEditViewController
Create and associate a CollegeEditViewController instance with the second scene in the college tab navigation hierarchy.

class CollegeEditViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    if let c = college {
      name.text = c.name
      street.text = c.address.street[0]
      city.text = c.address.city
      state.text = c.address.state
      postalCode.text = c.address.postalCode
      country.text = c.address.country
      desc.text = c.description
    }
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let controller = segue.destination as! CollegeSaveViewController
    controller.college = College(name: name.text!, description: desc.text, address: College.Address(street: [street.text!], city: city.text!, state: state.text!, postalCode: postalCode.text!, country: country.text!))
  }
  
  @IBOutlet weak var name : UITextField!
  @IBOutlet weak var street : UITextField!
  @IBOutlet weak var city : UITextField!
  @IBOutlet weak var state : UITextField!
  @IBOutlet weak var postalCode : UITextField!
  @IBOutlet weak var country : UITextField!
  @IBOutlet weak var desc : UITextView!

  var college : College?
}


CollegeSaveViewController
Create and associate a CollegeSaveViewController with the last view controller in the college tab navigation hierarchy. Note that the NSAttributedString extension for converting HTML string into an attributed string is embedded in this source file in this project. You may place the extension into a dedicated Swift source file if you desire.

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 CollegeSaveViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    display()
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }

  @IBOutlet weak var textView: UITextView!
  var college : College? = nil

  private func display() {
    if let c = college {
      let desc = c.description != nil ? c.description! : ""
      let html =
  """
  <div>
  <label>Name:</label> <label>\(c.name)</label><br/>
  <label>Description:</label> <label>\(desc)</label><br/>
  <label>Street:</label> <label>\(c.address.street[0])</label><br/>
  <label>City:</label> <label>\(c.address.city)</label><br/>
  <label>State:</label> <label>\(c.address.state)</label>
  <label>Postal Code:</label> <label>\(c.address.postalCode)</label>
  <label>Country:</label> <label>\(c.address.country)</label>
  </div>
  <br/><br/>
  <div>Click <b>Done</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
    }
  }
}

Person controllers

Modify the PersonViewController to match the ViewController from lab8. Add edit and save view controllers for person and assign to the view controllers in the storyboard as in lab8. The second tab for editing a person entity works exactly as in lab8.

DetailViewController

The last view controller that was added to the tab bar is purely to illustrate how to add additional view controllers to a tab bar. We will also see how we can access another view controller that has been associated with the tab bar. This is in general not a great idea, but we can access them if need be.

class DetailViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    if let tbc = tabBarController {
      let nc = tbc.viewControllers?.first as! UINavigationController
      let controller = nc.childViewControllers.first as! CollegeViewController
      display(college: controller.college)
    }
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }
  
  @IBOutlet weak var textView: UITextView!
  
  private func display(college: College?) {
    if let c = college {
      let desc = c.description != nil ? c.description! : ""
      let html =
  """
  <div>
  <label>Name:</label> <label>\(c.name)</label><br/>
  <label>Description:</label> <label>\(desc)</label><br/>
  <label>Street:</label> <label>\(c.address.street[0])</label><br/>
  <label>City:</label> <label>\(c.address.city)</label><br/>
  <label>State:</label> <label>\(c.address.state)</label>
  <label>Postal Code:</label> <label>\(c.address.postalCode)</label>
  <label>Country:</label> <label>\(c.address.country)</label>
  </div>
  <br/><br/>
  <div>Click <b>Done</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
    }
  }
}