Solution for Ch 14 Bronze, Silver, and Gold Challenge

Bronze Challenge: Displaying a Number Pad

Add the following method in class DetailViewController:

override func viewDidLoad() {
  valueField.keyboardType = .numberPad
}

Silver Challenge: A Custom UITextField

Create a new Cocoa Touch Class CustomTextField. Make it a subclass of UITextField.

import UIKit

class CustomTextField: UITextField {
  override func becomeFirstResponder() -> Bool {
    borderStyle = .bezel
    return super.becomeFirstResponder()
  }
  
  override func resignFirstResponder() -> Bool {
    borderStyle = .roundedRect
    return super.resignFirstResponder()
  }
}

Use subclass CustomTextField for the text fields in DetailViewController.

Gold Challenge: Pushing More View Controllers

In class Item, change dateCreated to a variable.

var dateCreated: Date

Create a new Cocoa Touch Class DateCreatedViewController. Make it a subclass of UIViewController.

import UIKit

class DateCreatedViewController: UIViewController {
  var datePicker: UIDatePicker!
  var item: Item!
  
//      override func loadView() {
//        super.loadView()
  override func viewDidLoad() {
    super.viewDidLoad()
    
    self.title = "Date Created"
    
    datePicker = UIDatePicker()
    datePicker.datePickerMode = .date
    datePicker.date = item.dateCreated
    datePicker.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(datePicker)
    
    datePicker.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    datePicker.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
  }
  
  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    item.dateCreated = datePicker.date
  }
}

In StoryBoard, add a new View controller and set its class to DateCreatedViewController
Add a new button underneath the dateLabel in DetailViewController with the title “Change Date”
Add a segue with Identifier “changeDate” from the new button to the new View controller.

Add the following method in class DetailViewController

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  switch segue.identifier {
  case "changeDate"?:
    let dateCreatedViewController = segue.destination as! DateCreatedViewController
    dateCreatedViewController.item = item
  default:
    preconditionFailure("Unexpected segue identifier.")
  }
}

From the above Apple Document extract for loadView():

You should never call this method directly. The view controller calls this method when its view property is requested but is currently nil. This method loads or creates a view and assigns it to the view property.
If the view controller has an associated nib file, this method loads the view from the nib file. A view controller has an associated nib file if the nibName property returns a non-nil value, which occurs if the view controller was instantiated from a storyboard, if you explicitly assigned it a nib file using the init(nibName:bundle:) method, or if iOS finds a nib file in the app bundle with a name based on the view controller’€™s class name. If the view controller does not have an associated nib file, this method creates a plain UIView object instead.
If you use Interface Builder to create your views and initialize the view controller, you must not override this method.
You can override this method in order to create your views manually. If you choose to do so, assign the root view of your view hierarchy to the view property. The views you create should be unique instances and should not be shared with any other view controller object. Your custom implementation of this method should not call super.
If you want to perform any additional initialization of your views, do so in the viewDidLoad() method.

2 things you should change in your code:

  • don’t call super.loadView()

  • call this method only to attach a root view. Any additional initialisation (like add subviews) should be made in viewDidLoad()

Besides, looking at what your code is actually doing inside loadView(), I noticed there’s no need at all to implement it inside loadView(). You should move your code entirely in viewDidLoad()

@giovannict73 Thanks for your advice. The solution is updated accordingly.

Here is my Storyboard approach for the Gold challenge using your code as a reference.

class DatePickerViewController: UIViewController {
    
    @IBOutlet var datePicker: UIDatePicker!

    var item: Item!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        self.title = "Date Created"
        
        datePicker.datePickerMode = .date
        datePicker.date = item.dateCreated
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        item.dateCreated = datePicker.date
    }
}

Do we have to code the datepicker constraints manually since we can’t use story board to code in the date to be equal to item.dateCreated?

If the DatePicker control is created by code, then its constraints can only be set up by code.

If the DatePicker control is created at the storyboard with Interface Builder, then its constraints can be set up at the storyboard with Interface Builder or by code.

In the last chapter we learned about StackViews. Jut use them. They make life so much easier.

Another approach:
Replace DetailViewController with a TableViewController with static cells. Put The DatePicker Control inside one of the tableview cell. Hide/unhide the DatePicker Control by tapping the cell. It will look like the Edit Event View of the native Calendar App.

Silver Challenge alternative solution:
While the solution provided here does work, .numberPad doesn’t have a decimal point:
44 PM

However, using .decimalPad conveniently adds a decimal point to the number pad.
38 PM