UIKit contains basic user interface objects. The basic unit used for displaying content on the screen in iOS is called a view. The parent object for almost all UI elements in iOS apps is called UIView. As mentioned in Chapter 1, Getting Started with iOS, one of the basic principles of iOS programming is subclassing. Because the parent class for all UI objects is a UIView, these UI objects inherit the basic properties found in a view. These properties include the size, location, opacity, and position in the view hierarchy.
UIView defines a rectangle of a specific height, width, and location. This rectangle has basic properties such as opacity (visibility), background color, a record of its superview (parent), and an array containing a reference to any subviews (children) that may have been added.
In Chapter 3, Physical Hardware, we discussed the iOS coordinate system. The coordinate system of a view follows the same principle, and for good reason. When an app starts, it creates a UIWindow. Unlike desktop applications, each app in iOS is restricted to one window, and the entire app exists as subviews of that window. UIWindow is actually a subclass of UIView that has overridden the origin to be the top left corner of the screen and the size to the bounds of the device’s screen.
When views are added to the screen, they exist in what is referred to as the view hierarchy. Much like <div> tags are used as containers in HTML documents, views can contain other view objects in the view hierarchy. This hierarchy defines the layout of views on the screen in relation to other views. If views overlap, the topmost view in the hierarchy displays over the underlying views.
Managing the View Hierarchy
When a view is first created, it exists outside the view hierarchy. You can add a view to the screen by calling addSubview: or insertSubview: on a visible view. Additionally, you can build up your view hierarchy on a hidden view, and display after your hierarchy is complete.
Remember that iOS uses a retain-release strategy for managing memory. Removing a view from the view hierarchy decreases the retain count by one. If you intend to reuse your view after removing it from the hierarchy, retain the view before removing. Otherwise, it is possible for the view to reach a retain count of zero while you are working with it, resulting in an application crash. After you are finished using the view, remember to call release to avoid a memory leak.
drawRect vs. setNeedsDisplay
When a view is refreshed, the drawRect function for that view is called. This function draws content to the view every time it is called. Because drawRect is called often, it should be a very lightweight. Don’t allocate memory in drawRect, and never call drawRect directly from your code. (We will discuss overriding drawRect further in Chapter 8, Creating Custom UIViews and UIViewControllers.)
So, if you can’t call drawRect from your code, how do you refresh a view? The answer is to call the function setNeedsDisplay. Because resources are scarce on mobile devices, iOS attempts to optimize resource intensive processes whenever possible. Drawing content to the screen can require a lot of resources. Instead of manually calling drawRect to refresh a view, set the view as setNeedsDisplay. When a view has the setNeedsDisplay flag set, iOS automatically refreshes the view when it is most efficient. The time delay between drawRect and setNeedsDisplay is unnoticeable, on the order of milliseconds. But, by allowing iOS to call drawRect on its own schedule, iOS can optimize multiple drawRect calls and determine the most efficient way to execute the command.
Frame vs. Bounds
Every view has two properties, frame and bounds. Both of these properties are from a simple data structure known as CGRect, which defines an origin (x,y), and a size (width, height). While similar, the frame and bounds of a view actually differ in their use and definition.
The frame of a view defines the height and width of the rectangle with the origin as the origin location in the view’s superview. The bounds of a view also defines the height and width, but the origin is with respect to the current view and is usually (0,0).
Now that you have a basic understanding of how views work, let’s look back at our “Hello, World!” app in the Part I iOS App Blueprint. In this example, we created a couple of view objects and then added them to our window.
1 UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 2 myLabel.text = @"Hello, World!"; 3 [myLabel sizeToFit]; 4 [myViewController.view addSubview:myLabel]; 5 [window addSubview:myViewController.view];
In our first line of code, we create a UILabel called myLabel with the height and width of zero. UILabel is a subclass of UIView, so we know it has properties like height, width, opacity, and so on. In this example, we create a height and width of zero because we want to automatically size the view to fit the text, as seen in line 3. In line 2, we set the text of myLabel to “Hello, World!”
This is an important distinction between UIViews and their subclasses. A UIView does not have the text property. In this case, UILabel is a subclass of UIView, which gives UILabel things like height, width, opacity, and background color. But UILabel adds additional properties like text, font, and textAlignment.
Finally, in lines 4 and 5, we add our newly created views to the view hierarchy by adding them as subviews. In line 4, we add our label as a subview of the view of myViewController. After line 4, our label is not yet visible because only our window is visible on the screen. In line 5, we add the view for myViewController as a subview of our window. This adds myViewController and all of its subviews—including our UILabel—to the app window’s view hierarchy.