Views and Frames

When you initialize a view programmatically, you use its init(frame:) designated initializer. This method takes one argument, a CGRect, that will become the view’s frame, a property on UIView.

    var frame: CGRect

A view’s frame specifies the view’s size and its position relative to its superview. Because a view’s size is always specified by its frame, a view is always a rectangle.

A CGRect contains the members origin and size. The origin is a structure of type CGPoint and contains two CGFloat properties: x and y. The size is a structure of type CGSize and has two CGFloat properties: width and height (Figure 3.5).

Figure 3.5  CGRect

CGRect

When the application is launched, the view for the initial view controller is added to the root-level window. This view controller is represented by the ViewController class defined in ViewController.swift. We will discuss what a view controller is in Chapter 4, but for now it is sufficient to know that a view controller has a view and that the view associated with the main view controller for the application is added as a subview of the window.

Before you create the views for WorldTrotter, you are going to add some practice views programmatically to explore views and their properties and see how the interfaces for applications are created.

Open ViewController.swift and delete any methods that the template created. Your file should look like this:

    import UIKit

    class ViewController: UIViewController {

    }

(UIKit, which you also saw in Chapter 1, is a framework. A framework is a collection of related classes and resources. The UIKit framework defines many of the UI elements that your users see, as well as other iOS-specific classes. You will be using a few different frameworks as you go through this book.)

Right after the view controller’s view is loaded into memory, its viewDidLoad() method is called. This method gives you an opportunity to customize the view hierarchy, so it is a great place to add your practice views.

In ViewController.swift, override viewDidLoad(). Create a CGRect that will be the frame of a UIView. Next, create an instance of UIView and set its backgroundColor property to UIColor.blue. Finally, add the UIView as a subview of the view controller’s view to make it part of the view hierarchy. (Much of this will not look familiar. That is fine. We will explain more after you enter the code.)

Listing 3.1  Overriding viewDidLoad() (ViewController.swift)

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let firstFrame = CGRect(x: 160, y: 240, width: 100, height: 150)
        let firstView = UIView(frame: firstFrame)
        firstView.backgroundColor = UIColor.blue
        view.addSubview(firstView)
    }

}

To create a CGRect, you use its initializer and pass in the values for origin.x, origin.y, size.width, and size.height. The initializer will create the underlying origin and size and return the associated CGRect.

To set the backgroundColor, you use the UIColor class property blue. This is a computed property that initializes an instance of UIColor that is configured to be blue. There are a number of UIColor class properties for common colors, such as green, black, and clear.

Finally, to add your new view as a subview of the view controller’s view, you use UIView’s addSubview(_:) instance method.

Build and run the application (Command-R) on the iPhone 11 Pro simulator. You will see a blue rectangle that is the instance of UIView. Because the origin of the UIView’s frame is (160, 240), the rectangle’s top-left corner is 160 points to the right of and 240 points down from the top-left corner of its superview. The view stretches 100 points to the right and 150 points down from its origin, in accordance with its frame’s size (Figure 3.6).

Figure 3.6  WorldTrotter with one UIView

WorldTrotter with one UIView

Note that these values are in points, not pixels. If the values were in pixels, then they would not be consistent across displays of different resolutions (i.e., Retina versus non-Retina). A point is a relative unit of a measure; it will be a different number of pixels depending on how many pixels are in the display. Sizes, positions, lines, and curves are always described in points to allow for differences in display resolution.

Figure 3.7 represents the view hierarchy that you have created.

Figure 3.7  Current view hierarchy

Current view hierarchy

Every instance of UIView has a superview property. When you add a view as a subview of another view, the inverse relationship is automatically established. In this case, the UIView’s superview is the UIWindow.

Let’s experiment with the view hierarchy. First, in ViewController.swift, create another instance of UIView with a different frame and background color.

Listing 3.2  Updating viewDidLoad() (ViewController.swift)

override func viewDidLoad() {
    super.viewDidLoad()

    let firstFrame = CGRect(x: 160, y: 240, width: 100, height: 150)
    let firstView = UIView(frame: firstFrame)
    firstView.backgroundColor = UIColor.blue
    view.addSubview(firstView)

    let secondFrame = CGRect(x: 20, y: 30, width: 50, height: 50)
    let secondView = UIView(frame: secondFrame)
    secondView.backgroundColor = UIColor.green
    view.addSubview(secondView)
}

Build and run again. In addition to the blue rectangle, you will see a green square near the top-left corner of the window. Figure 3.8 shows the updated view hierarchy.

Figure 3.8  Updated view hierarchy with two subviews as siblings

Updated view hierarchy with two subviews as siblings

Now you are going to adjust the view hierarchy so that one instance of UIView is a subview of the other UIView instead of the view controller’s view. In ViewController.swift, add secondView as a subview of firstView.

Listing 3.3  Modifying the view hierarchy (ViewController.swift)

let secondView = UIView(frame: secondFrame)
secondView.backgroundColor = UIColor.green
view.addSubview(secondView)
firstView.addSubview(secondView)

Your view hierarchy is now four levels deep, as shown in Figure 3.9.

Figure 3.9  One UIView as a subview of the other

One UIView as a subview of the other

Build and run the application. Notice that secondView’s position on the screen has changed (Figure 3.10). A view’s frame is relative to its superview, so the top-left corner of secondView is now inset (20, 30) points from the top-left corner of firstView.

Figure 3.10  WorldTrotter with new hierarchy

WorldTrotter with new hierarchy

(If the green instance of UIView looks smaller than it did previously, that is just an optical illusion. Its size has not changed.)

Now that you have seen the basics of views and the view hierarchy, you can start working on the interface for WorldTrotter. Instead of building up the interface programmatically, you will use Interface Builder to visually lay out the interface, as you did in Chapter 1.

Start by removing your practice code from ViewController.swift.

Listing 3.4  Deleting viewDidLoad() (ViewController.swift)

override func viewDidLoad() {
    super.viewDidLoad()

    let firstFrame = CGRect(x: 160, y: 240, width: 100, height: 150)
    let firstView = UIView(frame: firstFrame)
    firstView.backgroundColor = UIColor.blue
    view.addSubview(firstView)

    let secondFrame = CGRect(x: 20, y: 30, width: 50, height: 50)
    let secondView = UIView(frame: secondFrame)
    secondView.backgroundColor = UIColor.green
    firstView.addSubview(secondView)
}

Now let’s add some views to the interface and set their frames.

Open Main.storyboard. At the bottom of the canvas, make sure the View as button is configured to display an iPhone 11 Pro device.

Open the library (with the Deleting viewDidLoad() (ViewController.swift) button or Command-Shift-L) and drag five labels onto the canvas. (Remember that if you Option-drag the first label, the library will stay open until you close it.) Set their text to match Figure 3.11. As shown, space them out vertically on the top half of the interface and center them horizontally. (Do not use constraints; just position the labels manually.)

Figure 3.11  Adding labels to the interface

Adding labels to the interface

Select the top label so you can see its frame in Interface Builder. Open its size inspector – the sixth tab in the inspector area to the right of the editor. (The keyboard shortcuts for the inspectors are Command-Option plus the tab number. The size inspector is the sixth tab, so its keyboard shortcut is Command-Option-6.)

In the View section, find Frame Rectangle. (If you do not see it, you might need to select it from the Show pop-up menu.) The values shown are the view’s frame, and they dictate the position of the view onscreen (Figure 3.12).

Figure 3.12  View frame values

View frame values

Build and run the application on the iPhone 11 Pro simulator. The interface on the simulator will look identical to the interface that you laid out in Interface Builder.

Customizing the labels

Let’s make the interface look a little bit better by customizing the view properties.

In Main.storyboard, select the background view. Open the attributes inspector (the fifth inspector tab) and give the app a new background color: Find and click the Background pop-button and choose Custom.... In the Colors pop-up, select the second tab (the Color Sliders tab) and choose RGB Sliders from the menu. In the box near the bottom, enter a Hex Color # of F5F4F1 (Figure 3.13). This will give the background a warm gray color.

Figure 3.13  Changing the background color

Changing the background color

Next you will give some of the labels a larger font size as well as a burnt orange text color. You can customize attributes common to selected views simultaneously.

Select the top two and bottom two labels by Command-clicking them in the document outline. Make sure the attributes inspector is open and update the text color: In the Label section, find Color and open the pop-up menu. Click Custom and select the Color Sliders tab again. Enter a Hex Color # of E15829.

Now let’s update the font size. Select the 212 and 100 labels. In the Label section in the attributes inspector, find Font and click the text icon next to the current font. In the popover that appears, change the Size to 70 (Figure 3.14). Select the remaining three labels and change their Size to 36.

Figure 3.14  Customizing the labels’ font

Customizing the labels’ font

Now that the font size is larger, the labels are no longer positioned correctly. Move the labels so that they are again nicely aligned vertically and centered horizontally. Also, confirm that the labels do not overlap one another. One way to help with this is to turn on bounds rectangles, shown in Figure 3.15, which will give each view in Interface Builder a blue outline. To see the bounds rectangles, select EditorCanvasBounds Rectangles.

Figure 3.15  Viewing the bounds rectangles

Viewing the bounds rectangles

Note that these outlines do not show when the app is running. We will not show the bounds rectangles going forward. You can leave them on or, if you prefer, turn them off with EditorCanvasBounds Rectangles.

Build and run the application on the iPhone 11 Pro simulator. Now build and run the application on the iPhone 11 Pro Max simulator. Notice that the labels are no longer centered – instead, they appear shifted slightly to the left.

You have just seen a major problem with absolute frames: The view does not look equally good on different sizes of screens.

In general, you should not use absolute frames – whose position and size are fixed – for your views. Instead, as you saw in Chapter 1, you should use Auto Layout to flexibly compute the frames for you based on constraints that you specify for each view.

What you really want for WorldTrotter is for the labels to remain the same distance from the top of the screen and to remain horizontally centered within their superview. They should also update if the font or text of the labels change. You will accomplish these goals in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset