Tag Archives: Xcode 8

Where is Update Frames in Xcode 8.1?

A tech author’s work is never done. As soon as he or she completes manuscript and gets it published, the manuscript almost immediately becomes obsolete. In my case, Practical Autolayout for Xcode 8 went obsolete  a day before I published, but I had no idea about a major change in Xcode 8.1.

Until Xcode 8.1, if you wanted to update a frame with new constraints, you had two possibilities. The first was in the pinpinMenuButton and align alignment iconmenu to update as you were setting the constraints.

2016-11-29_06-05-32

The second was a selection in the resolver resolver button 2016-10-01_13-27-48

It seems everyone, including me was not ready for a change Apple made in Xcode 8.1. If you go to look for Update Frames in the resolver resolver button, it is missing:

2016-11-29_06-11-52

So where did it go?

Apple moved this to an icon on the auto layout toolbar and deleted it from the menus.

2016-11-28_07-29-22

If it were me, I wouldn’t have deleted it from the menus in such an abrupt way. Apple did. This Update Frame button  has some different behaviors  from its predecessor on the menu, and I’d like to explain that using some examples from Chapter 3 of Practical Autolayout for Xcode 8

Set up a storyboard that looks something like this with a label Hello Pizza, a text view, and  three  buttons, Pepperoni, Cheese, and Done:

2016-11-29_05-54-14

Select the Hello Pizza label.  Click the pin buttonpinMenuButton in the auto layout toolbar. In the popup, set the top to 0 points, the left to 0 points and the left to 0 points.  Leave Update Frames as None

2016-11-29_05-56-06

Add the 3 constraints. The Hello Pizza Label will show misplacement constraints.

2016-11-29_05-56-32

Press the Update Frames button update frames  and the frame updates.

2016-11-29_06-37-41

This is not always the result. You must have all constraints satisfied before the button will update frames. For example, select the text view. Press the align button alignment iconand center the text view by checking on Horizontally in Container and Vertically in Container.

2016-11-29_05-54-38

Again don’t update frames, but click  Add 2 constraints. You’ll see an ambiguous constraint in red.

2016-11-29_05-55-13

If you click the update frames button nothing happens. Until a frame has no ambiguity(i.e. no red constraint errors), you cannot update it. Most often that is setting a size. For the text box, set an absolute size in the pin menu pinMenuButton  of 175 points in both directions.

2016-11-29_05-57-41

Add the constraints. The errors all turn to misplacements.

 2016-11-29_05-58-39

Once all misplacements, you can update the frame with update frames.

2016-11-29_07-15-35

Priorities are not assumed with the new update frames button. When there is an ambiguity in size between two frames that depend on each other for size, you must specify the a priority for them or set a size.  Take for example these two buttons.

2016-11-29_05-45-35

Pepperoni is pinned to the left margin, the label above it and the text view below it. Cheese is pinned 10 points from Pepperoni, aligned to the top of Pepperoni, and pinned 10 points from the right margin. We’d like to have two buttons that fill the available space.

The option used in Practical Auto Layout for these buttons is to make them the same size. Control drag from Pepperoni to Cheese. A menu appears.

2016-11-29_06-56-57

Shift select Equal Width and Equal Heights, then hit the Add Constraints selection. The ambiguity changes to misplacements.

2016-11-29_06-57-14

Select both the Pepperoni and Cheese buttons. Hit the Update Frame button update frames and two equally sized buttons appear

2016-11-29_06-58-00

The other, more advanced option is to change priority of one of the buttons so they are not equal. Both are by default 250.  Going back to the original ambiguous layout,

2016-11-29_05-45-35

changing the content hugging priority of Pepperoni from 250 to 251 tells auto layout for Pepperoni to keep its size and Cheese to stretch to make up the difference.

2016-11-29_06-56-19

Priorities are covered in detail in Chapter 12 of Practical Autolayout for Xcode 8.

I’ll be updating the book shortly. Until then or if you cannot update your book,  consider this an errata to the versions now available.

practical-autolayout-x8-newsletterPurchase the book for  Kindle and iTunes  here:

get_it_on_ibooks_badge_us_1114

Using The Navigation Bar Title and Back Button in Swift 3.0

In writing the Swift Swift View Controllers book, it came to my attention many people don’t understand the functionality of the navigation toolbar’s title and Back button. In an early part of writing, I planned to skip the topic as a minor detail  so I could get the book done and published. However, the built-in features of the navigation toolbar make it a powerful and useful feature that can’t be missed.

Setting Up the Storyboard

Open up a new single view project named NavBarDemo using Swift as the language and Universal device. Go to the storyboard and click the view controller icon in the scene. In the drop down menu, select Editor>Embed in>Navigation controller. Drag two buttons, one labeled Pizza and the other labeled Pasta out to the scene. Set the font size on both to 26 Point. Make the Pizza button White(#FFFFFF) text on a Red(#FF0000) background. Make the Pasta button White(#FFFFFF) text on a Blue(#0000FF) background. Arrange them like this on the storyboard:

2016-06-22_06-17-09

Select the Pizza button. Click the  auto layout pin button pinMenuButton. Pin the Pizza button 0 points up, 0 left, 0 right, and 0 down like this:

2016-06-22_06-22-30

Add the four constraints without updating. Select the Pasta button. Click the pin button pinMenuButtonand repeat the constraints of   0 up, 0 left, 0 right, and 0 down. Add the four constraints. Now Control-drag from the Pizza button to the Pasta button. Select Equal Widths in the menu that appears.

2016-06-22_06-30-19

On the auto layout resolver menu resolver button, select Update Frame in the All Frames in View section.

2016-06-22_06-31-40

Your controller should look like this:

2016-06-22_06-34-58

Drag two more view controllers on to the storyboard. Make the background of one Blue(#0000FF) and the background of the other Red(#FF0000).

Control drag from the Pizza button to the red scene. Select a show segue.

2016-06-22_06-38-52

In the attributes inspector, set the segue identifier to pizza. Control-drag from the Pasta button to the blue scene. Select a show segue. In the properties inspector, set the segue identifier to pasta. Your storyboard should look like this:

2016-06-22_06-41-09

In this lesson we will do all the coding in ViewController, so there is no need of code in the two new controllers.

Setting the Navigation Title Bar

There is a property on UIViewController called navigationItem. When used with navigation controllers, this controls the navigation bar at the top of the view. The navigationItem property is an instance of UINavigationItem, which has four major properties: a title, a left bar button, a right bar button and a prompt. To set the title on the toolbar , you set the string for the title property. For example add this to the ViewController class

override func viewWillAppear(_ animated: Bool) {
        navigationItem.title = "One"
    }

Build and run. The root view now has One as a title.

2016-06-22_06-59-59

We used viewWillAppear and not viewDidLoad. ViewController is the root view controller in the navigation stack. It only loads once and stays active in the background. It will execute viewDidload only once. To make sure we update, we use viewWillAppear instead.
The title is dynamic. As a simple example, We’ll place a count in the title of the navigation bar. Add the following code:

var vcCount:Int = 0{
    didSet{
      navigationItem.title = "Count: \(vcCount)"
    }
  }

We used the  didSet property observer feature of Swift. Any time vcCount changes, the title changes with it. We change the count on any segue so we can increment in prepare for segue:. Add this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount+=1
    }

It’s rare to not have a statement within an if clause in a prepare for segue:. We want any segue to increment the count, so we don’t need the if.

We want to show the count when we load. Change the viewWillAppear to this:

 override func viewWillAppear(_ animated: Bool) {
        // navigationItem.title = "One"
        navigationItem.title = "Count: \(vcCount)"
    }

Build and run. Go back and forth in the views. The title changes every time you come back to it.

2016-06-22_07-04-28

Programming the Back Button

You’ll notice once you have a title, the navigation Back button disappears, to be replaced by the previous view’s title.

2016-06-22_07-08-42

The Back button reads the controller underneath the current controller for its title. You cannot set the back button title directly. Instead, you set the title of the current controller before you leave for the destination controller. If the previous view controller’s title is nil, the button titles itself Back. Change the prepare for segue: to this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil
    }

Build and run.
You have the count on the root controller

2016-06-22_07-14-16

You have a Back button on the child controllers

2016-06-22_07-14-26

If you wanted to add your own text to the Back button, you have to change the title of the controller directly under it on the navigation stack. The simplest way is change the title just before you segue to the new controller. Change prepare for segue: to this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil

        if segue.identifier == "pizza"{
            navigationItem.title ="Pizza to One"
        }
        if segue.identifier == "pasta"{
            navigationItem.title = "Pasta to One"
        }
    }

Before we segue to the pizza and pasta controller, we change the title of the current controller’s navigationItem. The Back button reads the title of navigationItem and sets the button’s title accordingly. The viewWillAppear method will reset the title to the count in the first view controller when we pop the Pizza or Pasta view controller. Build and Run. Select to the Pizza and Pasta buttons:

2016-06-22_07-30-59

2016-06-22_07-30-05

To see this happening, you can comment out this in viewWillAppear.

//navigationItem.title = "Count: \(vcCount)"

Build and Run. When you go back, the title remains:

2016-06-22_07-33-30

Uncomment the line before you go on.

The Size Sensitive Back Button

The Back button is sensitive to the space around it. The button’s title responds to not having enough space to place itself. Change prepare for segue: to this:

    override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil
        if segue.identifier == "pizza"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Pizza One"
            navigationItem.title = "Pizza to One"
        }
        if segue.identifier == "pasta"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Linguine all’arrabbiata"
            navigationItem.title = "Pasta to One"
        }
    }

We get the destination view controller and assign it to vc. We then send the title for the destination view controller  to vc.navigationItem.title. We’ve picked some long labels to test size restrictions.

Set the simulator to iPad Air 2. Build and run. Select the Pizza button.

2016-06-22_07-58-45

Go back to select the Pasta button.

2016-06-22_07-45-03

That works as we expected. Now try an iPhone 6 in the simulator. Run the demo, select the Pasta controller and you get this:

2016-06-22_07-57-14

The navigation controller title gets replaced with Back. Go back and try the Pasta Button.

2016-06-22_07-51-03

There is only the  2016-06-22_08-10-17 icon and no text for the back button. Rotate the device by pressing Command-Left Arrow:

2016-06-22_08-08-46

The text re-appears with more space to place the text.

The back button is intelligent. If it has enough space, it displays the title of the previous controller. If there is not enough space, it displays Back. If there is not enough space for the word Back, it displays only the 2016-06-22_08-10-17 icon.
There is an important design rule in play here that I am intentionally breaking to make the point: Keep your titles short in a navigation bar. If you keep titles short, you will have the full functionality of the back button.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  NavBarDemo for Swift 3.0
//
//  Created by Steven Lipton on 6/22/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    var vcCount:Int = 0{
        didSet{
            navigationItem.title = "Count: \(vcCount)"
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        //navigationItem.title = "One"
        navigationItem.title = "Count: \(vcCount)"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil
        if segue.identifier == "pizza"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Pizza One"
            navigationItem.title = "Pizza to One"
        }
        if segue.identifier == "pasta"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Linguine all’arrabbiata"
            navigationItem.title = "Pasta to One"
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}