ViewController and Multiple Views L2.1.1

Views

A view (i.e. UIView subclass) represents a rectangular area. It defines a coordinate space for drawing and handling touch events.

Hierarchical

A view has only one superview (var superview: UIView?), but can have many (or zero) subviews var subviews: [UIView]

The order in the subviews array matters, those later in the array are on top of those earlier.

A view can clip it's subviews to its own bounds or not (the default is not to)

The hierarchy is most ofter constructed in Xcode graphically, even custom views are usually added to the view hierarchy using Xcode, but it can be done in code as well.

func addSubview(_ view: UIView) // sent to a view's (soon to be) superview  
func removeFromSuperview() // sent tot he view you want to remove (not its superview)  

The (usable) view hierarchy starts at the view controllers var view: UIView. This simple property is a a ver important thing, for example in this is the one whose bounds will change on rotation and will be the one we likely will programmatically add subviews to.

All of our MVC's view's UIViews will have this view as an ancestor and it automatically gets hooked up by Xcode.

UIWindow

This is the UIView at the very very top of the view hierarchy (even includes status bar). Usually there is only one UIWindow in an entire iOS app, and we don't really ever need to worry about it.

Initializing a UIView

Try to avoid an initializer if possible (having one in UIView is slightly more common that having a UIViewController initializer). If we do need to initialize a UIView it's important to note that it has two inits, and the one that's used depends on how it is created.

init(frame: CGRect) // initializer if the UIView is created in code  
init(coder: NSCoder) // initializer if the UIView comes out of a storyboard  

When using storyboard, all the stuff you have created there gets output to an xml file. Thats the coder part of the second init. So when you run the app all that data in the xml files gets loaded in and is essentially the instructions on how to build the UI.

So if we do create a UIView in code because we need to initialize some var for example, we need to override both init methods (the init(coder: NSCoder) is a required init) then call some setup function from both of them. That way we can guarantee that the view will be initialized property whether it was created in code or interface builder.

func setup() { ... }

override init(frame: CGRect) { // a designate initializer  
    super.init(frame: frame)
    setup()
}

required init(coder aDecoder: NSCoder) { // a required initializer  
    super.init(coder: aDecoder)
    setup()
}

View Controllers

All view controllers maintain a standard lifecycle when they are called into action or transitioned away from view.

The start of the lifecycle...

  1. Creation (MVCs are most often instantiated out of a storyboard, there are ways to do it in code but that's rare).
  2. Preparation if being segued to.
  3. Outlet setting.
  4. Appearing and disappearing.
  5. Geometry changes.
  6. Low-memory situations (these almost never happens and it's really part of the lifecycle).

View Controller State and Lifecycle

View controllers have a set of methods that will called at appropriate points in their lifecycle.

All of these can be overridden but if not the empty or default methods will be called. To override a default view controller functions preface the func keyword with override. Misspelling a function like viewWillAppear will not result in the application crashing, rather if will be treated as any other function and the default will be called.
view controller lifecycle

viewDidLoad

The viewDidLoad() method is very important in setting up the MVC and will only be called once, and it gets called right after the view is loaded onto the screen.

If you have some code that updates the UI with data from the model it will often times live here.

It's very important to note that the geometry of the view (it's bounds) is not yet set when viewDidLoad is called, so we should no initialize things that are geometry-dependent here.

override func viewDidLoad() {  
    super.viewDidLoad() // always let super have a chance in all lifecycle methods
    // do some setup of MVC
}

viewWillAppear

viewWillAppear gets called right before the view and view controller is loaded onto the screen. The viewDidLoad only gets called once but viewWillAppear will be called any time the view comes back into the UI.

Here is where we do things if the display is changing while the MVC is off-screen, possibly because of a model update or there is an update from the network.

You could also optimize performance by waiting until this method is called (as opposed to viewDidLoad) to kick off an expensive operation.

When this method is called the geometry is set but there are better places to call geometry-dependent code (most will be done with autolayout), like viewWillLayoutSubviews() and viewDidLayoutSubviews (the only difference is the first is called before autolayout is run and the second after).

awakeFromNib

This method is sent to all objects that come out of a storyboard (including the Controller). This happens before outlets are set. It's important to note that we should put code somewhere else if at all possible, this is kind of like a last resort method.


The view and the view controller are linked by a way that a UIViewController and any subclasses of it will have at least one IBOutlet that always goes to a UIView

  • IBOutlet is used to connect your code to UIElements like buttons
  • In Xcode you drag from the UIElement to the code even though you want the connection to the the other way around (from code to the UIView)

Adding Images

Adding images in done in the Assets.xcassets folder in the project navigator

  • Click on the right plus icon in the bottom left corner of the screen, and select “add image set"
  • Different devices need different size images. Typically three sizes will cover all devices. Xcode will know how to create and group images into sets if all 3 images of the same type and the two larger images contain 2x and 3x in the suffix

new image set

To add an image to a UI element edit the image property from inspector in the storyboard view

Fixing constraints when new elements are added

View constraints can be fixed when UI elements are adjusted in the little yellow disclosure button in the top right of the document outline (storyboard file tree)

You can fix displacement one by one in the pop up form the disclosure button, or to fix all elements at once select the “Apply to all views in container” from the pop up (this is possible because all UIElements on the screen belong to the same view controller)

Documentation on UIElements

There’s not much in UIButton documentation about disabling and enabling, controls for that can be found in the parent class UIControl, specifically the property isEnabled

To open documentation from Xcode option-click or shift-cmd-zero will open the document viewer where you can search for a particular element or class

Multiple Views

Most iOS apps have multiple view controllers. In order to use multiple view controllers another class beyond just the standard “ViewController.swift” is needed that lets you navigate between them. The two most common classes to use for handling multiple view controllers are UINavigationController and UITabBarController.

UINavigationController basics

  • UINavigationController is a call that handles a stack of UIViewControllers. Like any other stack in computer science you can insert and remove items from the stack. UINC can handle as many view controllers as you need.

  • UINC starts out with a single view controller called the rootViewController

uinavigationcontroller

  • In the screen shot below the middle view controller is the rootViewController

root view controller

  • The initial view controller is indicated by the first grey arrow. If you have multiple view controllers you can drag this arrow around to change which view controller your app will start with

  • This is also indicated in the attributes inspector by the “Is initial view controller” checkbox

  • To add a UINavigationController click on the existing view controller in the document outline and go the app navigator at the top of the screen and select “editor” -> "embed in” -> “Navigation Controller”

  • This will make the navigation controller initial view controller and the one that was selected the rootViewController

To add more view controllers, drag from the library pane in the bottom right of the utilities sidebar

To make a basic transition (segue) to a new view controller, crtl-click and drag from the UI element or the story board in the document outline to the new view controller and select the kind of transition you want

When view controllers are added to existing view controllers they will come with default breadcrumbs, in the form of a back button that will return you back to previous view controller.