Assembling Views and Animations - The Core iOS Developer’s Cookbook, Fifth Edition (2014)

The Core iOS Developer’s Cookbook, Fifth Edition (2014)

Chapter 4. Assembling Views and Animations

The UIView class and its subclasses populate the iOS device screens. This chapter introduces views from the ground up. You’ll learn how to build, inspect, and break down view hierarchies and understand how views work together. You’ll discover the role geometry plays in creating and placing views in your interface, and you’ll read about animating views so they move and transform onscreen.

Chapter 5, “View Constraints,” introduces Auto Layout, a view layout system that leverages a declarative constraint-based model. In declarative programming, a developer describes to the SDK how an application or interface behaves, and the system expresses those rules at runtime. You describe your interface by using constraint rules. Constraints produce the geometry and layout of your views.

Whether you work with Auto Layout or not, understanding the foundational geometry of views is critical to building user interfaces.

View Hierarchies

A tree-based hierarchy orders what you see on your iOS screen. Starting with the main window, views are laid out in a specifically hierarchical way. All views may have children, called subviews. Each view, including the window, owns an ordered list of these subviews. Views might own many subviews; they might own none. Your application determines how views are laid out and who owns whom.

Subviews display in order, always from back to front. This works something like a stack of animation cels—the transparent sheets used to create cartoons. Only the parts of the sheets that have been painted show through. The clear parts allow any visual elements behind the sheet to be seen. Views, too, can have clear and painted parts and can be layered to build a complex presentation.

Figure 4-1 shows a little of the layering used in a typical window. Here the window owns a UINavigationController-based hierarchy. The elements layer together. The window (represented by the empty, rightmost element) owns a navigation bar with its buttons and title label, and a table with its own subviews. These items stack together to build the GUI.

Image

Figure 4-1 Subview hierarchies combine to build complex GUIs.

Listing 4-1 shows the view hierarchy of the window in Figure 4-1. The tree starts from the top UIWindow and shows the classes for each of the child views. If you trace your way down the tree, you can see the navigation bar (at level 2) with its two buttons (each at level 3) and the table view (level 4) with its two cells (each at level 6). Some of the items in this listing are private classes, automatically added by the SDK when laying out views. For example, UILayoutContainerView is never used directly by developers. It’s part of the SDK UIWindow implementation.

The only parts missing from this listing are the dozen or so line separators for the table, omitted for space considerations. Each separator is a UITableViewSeparatorView instance. The separators belong to UITableView and would normally display at a depth of 5.

Listing 4-1 To-Do List View Hierarchy


--[ 1] UILayoutContainerView
----[ 2] UINavigationTransitionView
------[ 3] UIViewControllerWrapperView
--------[ 4] UITableView
----------[ 5] UITableViewWrapperView
------------[ 6] UITableViewCell
--------------[ 7] UITableViewCellScrollView
----------------[ 8] UITableViewCellContentView
------------------[ 9] UILabel
----------------[ 8] UITableViewCellDetailDisclosureView
------------------[ 9] UIButton
--------------------[10] UIImageView
------------------[ 9] UIImageView
------------[ 6] UITableViewCell
--------------[ 7] UITableViewCellScrollView
----------------[ 8] UITableViewCellContentView
------------------[ 9] UILabel
----------------[ 8] UITableViewCellDetailDisclosureView
------------------[ 9] UIButton
--------------------[10] UIImageView
------------------[ 9] UIImageView
----------[ 5] UIImageView
----------[ 5] UIImageView
----[ 2] UINavigationBar
------[ 3] _UINavigationBarBackground
--------[ 4] _UIBackdropView
----------[ 5] _UIBackdropEffectView
----------[ 5] UIView
--------[ 4] UIImageView
------[ 3] UINavigationItemView
--------[ 4] UILabel
------[ 3] UINavigationButton
--------[ 4] UIButtonLabel
------[ 3] UINavigationButton
--------[ 4] UIButtonLabel
------[ 3] _UINavigationBarBackIndicatorView


Recipe: Recovering a View Hierarchy Tree

Each view knows both its parent (aView.superview) and its children (aView.subviews). You can build a view tree, like the one shown in Listing 4-1, by recursively walking through a view’s subviews. Recipe 4-1 builds a visual tree by noting the class of each view and increasing the indentation level every time it moves down from a parent view to its children. The results are stored in a mutable string and returned to the calling method.

The code shown in Recipe 4-1 was used to create the tree shown in Listing 4-1. You can use this method to duplicate the results of Listing 4-1, or you can copy it to other applications to view their hierarchies.


Note

UIView includes a “secret” method—recursiveDescription—provided by the UIDebugging category on UIView. Documented in the Apple Tech Note at https://developer.apple.com/library/ios/technotes/tn2239/_index.html, it recursively iterates through child views, appending the description of each view and providing a similar if less configurable and readable output to Recipe 4-1. As a private method, it is easily accessible only from the debugger. Using trickery to access this method from within your code will likely lead to your app being rejected by the App Store and is highly discouraged. Recipe 4-1 provides a cleaner, flexible option without this significant restriction.


Recipe 4-1 Extracting a View Hierarchy Tree


// Recursively travel down the view tree, increasing the
// indentation level for children
- (void)dumpView:(UIView *)aView atIndent:(int)indent
into:(NSMutableString *)outString
{
// Add the indentation dashes
for (int i = 0; i < indent; i++)
[outString appendString:@"--"];

// Follow that with the class description
[outString appendFormat:@"[%2d] %@\n", indent,
[[aView class] description]];

// Recurse through each subview
for (UIView *view in aView.subviews)
[self dumpView:view atIndent:indent + 1 into:outString];
}

// Start the tree recursion at level 0 with the root view
- (NSString *)displayViews:(UIView *)aView
{
NSMutableString *outString = [NSMutableString string];
[self dumpView:aView atIndent:0 into:outString];
return outString;
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Exploring XIB and Storyboard Views

Many Xcode users create views and view controllers in Interface Builder (IB), using storyboards and XIB files rather than building them directly in code. The following snippet demonstrates how to use Recipe 4-1 to deconstruct views loaded from these resources:

UIView *sampleView = [[[NSBundle mainBundle]
loadNibNamed:@"Sample" owner:self options:NULL] objectAtIndex:0];
if (sampleView)
{
NSMutableString *outstring = [NSMutableString string];
[self dumpView:sampleView atIndent:0 into:outstring];
NSLog(@"Dumping sample view: %@", outstring);
}

UIStoryboard *storyboard = [UIStoryboard
storyboardWithName:@"Sample" bundle:[NSBundle mainBundle]];
UIViewController *vc = [storyboard instantiateInitialViewController];
if (vc.view)
{
NSMutableString *outstring = [NSMutableString string];
[self dumpView:vc.view atIndent:0 into:outstring];
NSLog(@"Dumping sample storyboard: %@", outstring);
}

The sample code for Recipe 4-1 includes sample XIB and storyboard files. You can edit them yourself and test the view by dumping code to see how the underlying structure matches the presentation you create in IB.

Recipe: Querying Subviews

A view stores an array of its children. Retrieve the array via the subviews property. The child views are always drawn after the parent, in the order in which they appear in the subviews array. These views draw in order from back to front, and the subviews array mirrors that drawing pattern. Views that appear later in the array are drawn after views that appear earlier.

The subviews property returns just those views that are immediate children of a given view. At times, you might want to retrieve a more exhaustive list of subviews, including the children’s children. Recipe 4-2 introduces allSubviews(), a simple recursive function that returns a full list of subviews for any view. Call this function with a view’s window (via view.window) to return a complete set of views appearing in the UIWindow that hosts that view. This list is useful when you want to search for a particular view, such as a specific slider or button.

Although it is not typical, iOS applications may include several windows, each of which can contain many views, some of which may be displayed on an external screen. Recover an exhaustive list of all application views by iterating through each available window. TheallApplicationViews() function in Recipe 4-2 does exactly that. A call to [[UIApplication sharedApplication] windows] returns the array of application windows. The function iterates through these, adding their subviews to the collection.

In addition to knowing its subviews, each view knows the window it belongs to. The view’s window property points to the window that owns it. Recipe 4-2 also includes a simple function called pathToView() that returns an array of superviews, from the window down to the view in question. It does this by calling superview repeatedly until arriving at a window instance.

Views can also check their superview ancestry in another way. The isDescendantOfView: method on UIView determines whether a view lives within another view, even if that view is not its direct superview. This method returns a simple Boolean value. YES means the view descends from the view passed as a parameter to the method.

Recipe 4-2 Subview Utility Functions


// Return an exhaustive descent of the view's subviews
NSArray *allSubviews(UIView *aView)
{
NSArray *results = aView.subviews;
for (UIView *eachView in aView.subviews)
{
NSArray *subviews = allSubviews(eachView);
if (subviews)
results = [results arrayByAddingObjectsFromArray:subviews];
}
return results;
}

// Return all views throughout the application
NSArray *allApplicationViews()
{
NSArray *results = [[UIApplication sharedApplication] windows];
for (UIWindow *window in
[UIApplication sharedApplication].windows)
{
NSArray *subviews = allSubviews(window);
if (subviews) results =
[results arrayByAddingObjectsFromArray:subviews];
}
return results;
}

// Return an array of parent views from the window down to the view
NSArray *pathToView(UIView *aView)
{
NSMutableArray *array = [NSMutableArray arrayWithObject:aView];
UIView *view = aView;
UIWindow *window = aView.window;
while (view != window)
{
view = [view superview];
[array insertObject:view atIndex:0];
}
return array;
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Managing Subviews

The UIView class offers numerous methods that help build and manage views. These methods let you add, order, remove, and query the view hierarchy. As shown in Figure 4-1, this hierarchy controls the order of views. Updating the way views relate to each other changes the layout your users see in the app. Here are some approaches for typical view-management tasks.

Adding Subviews

You call addSubview: on a parent to add new subviews. This method adds a subview frontmost within the parent view, placed above any existing views. To insert a subview into the view hierarchy at a particular location other than the front, the SDK offers a trio of utility methods:

Image insertSubview:atIndex:

Image insertSubview:aboveSubview:

Image insertSubview:belowSubview:

These methods control where view insertion happens. The insertion can remain relative to another view, or it can move into a specific index of the subviews array. The above and below methods add subviews in front of or behind a given child, respectively. Insertion increases the index of any subsequent views and does not replace any existing views.

Reordering and Removing Subviews

Applications often reorder and remove views as users interact with the screen. The iOS SDK offers several easy ways to do this, allowing you to change the view order and contents:

Image Use [parentView exchangeSubviewAtIndex:i withSubviewAtIndex:j] to exchange the positions of two views.

Image Move subviews to the front or back by using bringSubviewToFront: and sendSubviewToBack:.

Image To remove a subview from its parent, call [childView removeFromSuperview]. If the child view had been onscreen, it disappears.

When you reorder, add, or remove views, the screen automatically redraws to show the new view presentation.

View Callbacks

When the view hierarchy changes, callbacks can be sent to the views in question. The iOS SDK offers six callback methods. These methods may help your application keep track of views that are moving and changing parents:

Image didAddSubview:—Is sent to a parent view after it has successfully added a child view via addSubview: or one of the other subview insertion methods listed earlier. It lets subclasses of UIView perform additional actions when new views are added.

Image didMoveToSuperview:—Informs views that they’ve been reparented to a new superview. A view may want to respond to that new parent in some way. When a view is removed from its superview, the new parent is nil.

Image willMoveToSuperview:—Is sent before a move occurs.

Image didMoveToWindow:—Provides the callback equivalent of didMoveToSuperview but when the view moves to a new Window hierarchy instead of to just a new superview. You most typically use this when working with external displays with AirPlay.

Image willMoveToWindow:—Is sent before a move occurs.

Image willRemoveSubview:—Informs the parent view that a child view is about to be removed.

These methods are rarely used, but when needed, they’re almost always lifesavers, allowing you to add behavior without having to know in advance what kind of subview or superview class is being used. The window callbacks are used primarily for displaying overlay views in a secondary window, such as alerts, and input views, such as keyboards.

Tagging and Retrieving Views

The iOS SDK offers a built-in search feature that lets you retrieve subviews by tagging them. A tag is just a number, usually a positive integer, that identifies a view. Assign tags using a view’s tag property (for example, myView.tag = 101). In IB, you can set a view’s tag in the Attributes inspector. As Figure 4-2 shows, you specify the tag in the View section.

Image

Figure 4-2 Set the tag for any view in IB’s Attributes inspector.

Tags are arbitrary. The only “reserved” tag is 0, which is the default property setting for all newly created views. It’s up to you to decide how you want to tag your views and which values to use. You can tag any instance that is a child of UIView, including windows and controls. So if you have many buttons and switches, adding tags helps tell them apart when users trigger them. You can add to your callback methods a simple switch statement that looks at the tag and determines how to react.

Apple rarely tags subviews. The only instance we have ever found of view tagging has been in UIAlertViews, where the buttons use tags of 1, 2, and so forth, but it has been several years since that happened. (Apple probably left this tagging in there as a mistake.) If you worry about conflicting with Apple tags, start your numbering at 10 or 100, or some other number higher than any value Apple might use.

Using Tags to Find Views

Tags let you avoid passing user interface elements around your program by making them directly accessible from any parent view. The viewWithTag: method recovers a tagged view from a child hierarchy. The search is recursive, so the tagged item need not be an immediate child of the view in question. You can search from the window with [window viewWithTag:101] and find a view that is several branches down the hierarchy tree. When more than one view uses the same tag, viewWithTag: returns the first item it finds.

The only challenge about using viewWithTag: is that it returns a UIView object. This means you often have to cast it to the proper type before you can use it. For example, you can retrieve a label and set its text like this:

UILabel *label = (UILabel *)[self.view.window viewWithTag:101];
label.text = @"Hello World";

Recipe: Naming Views by Object Association

Although tagging offers a handy approach to identifying views, some developers may prefer to work with names rather than numbers. Using names adds an extra level of meaning to your view identification schemes. Instead of referring to “the view with a tag of 101,” a switch named Ignition Switch can describe its role and add a level of self-documentation missing from a plain number:

// Toggle switch
UISwitch *s = (UISwitch *)[self.view viewNamed:@"Ignition Switch"];
[s setOn:!s.isOn];

It’s easy to extend UIView to add a nametag property and retrieve views by name. The secret lies in Objective-C’s runtime associated object functions. If you’ve ever written class categories, you might be thinking, “But if I add new storage, won’t I need to subclass?” Associated objects don’t require new instance variables. Instead, they provide a way to use key/value pairs outside an object’s direct storage, associating that object with information stored elsewhere.

Recipe 4-3 creates a UIView nametag category. It consists of a single property (nametag), which is supported by associated objects and a method (viewNamed:) that works to find any subview by name. The method descends the view hierarchy with a recursive depth-first search and returns the first subview whose name matches a search string.

Naming Views in Interface Builder

Using a named view approach allows you to retrieve subviews without having to declare IBOutlet instance variables. (Whether this is a net benefit to code readability and maintainability is beyond the scope of this section.) Consider the code you saw earlier in this section that toggles a switch from within an interface. You can add that name (Ignition Switch) as a custom runtime attribute in IB.

Figure 4-3 shows how you do this. Select any view and open the Identity Inspector (View > Utilities > Show Identity Inspector). Locate the User Defined Runtime Attributes section and click + to add a new attribute. Set the Key Path to nametag (to match the property defined in Recipe 4-3’sUIView class category), Type to String, and Value to the view’s new name. Save your changes. You can then use the category’s viewNamed: method to retrieve the switch via code and toggle its state.

Image

Figure 4-3 Set the tag for any view in IB’s Attributes inspector. You may assign user-defined runtime attributes for any Key-value coded (KVC) object value. These values are set at the time the XIB file loads.


Note

You can name a view’s layer directly, without associated objects. CALayer instances offer a name property, which helps identify layers when you’re working with them. To use layers, import the Quartz Core module in your source and access each layer via view.layer.


Recipe 4-3 Naming Views


#import <objc/runtime.h>
@implementation UIView (NameExtensions)

// Static variable's address acts as the key
// Thanks, Oliver Drobnik
static const char nametag_key;

- (id)nametag
{
return objc_getAssociatedObject(self, (void *) &nametag_key);
}

- (void)setNametag:(NSString *)theNametag
{
objc_setAssociatedObject(self, (void *) &nametag_key,
theNametag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)viewWithNametag:(NSString *)aName
{
if (!aName) return nil;

// Is this the right view?
if ([self.nametag isEqualToString:aName])
return self;

// Recurse depth first on subviews
for (UIView *subview in self.subviews)
{
UIView *resultView = [subview viewNamed:aName];
if (resultView) return resultView;
}

// Not found
return nil;
}

- (UIView *)viewNamed:(NSString *)aName
{
if (!aName) return nil;
return [self viewWithNametag:aName];
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


View Geometry

While direct control of view geometry is less necessary now than it used to be, thanks to Apple’s introduction of Auto Layout, these tasks continue to play a fundamental role in some situations when working with views. Some of Apple’s new APIs, such as the ones that introduce view physics, do not play nicely with Auto Layout. Because of this, you should master the basic ways you interact and adjust view geometry.

Geometric properties define where each view appears, what their sizes are, and how they are oriented. These properties remain valid, even when using Auto Layout; under Auto Layout, they are managed by the constraint system. You can still query and view these properties to retrieve information about where a view has been placed and what geometric transformations have been applied to it.

When working with dynamic views, views with short life spans, and ones whose geometry changes during presentation, you may need to step away from constraints and focus on immediate handling of the basic layout associated with each view. The UIView class provides two built-in properties that define these layout aspects.

Every view uses a frame to define its boundaries. The frame specifies the outline of the view: its location, width, and height within the coordinate system of its parent view. The associated bounds and center properties, respectively, define the frame rectangle within the view’s owncoordinate system and the geometric center of the frame in the parent’s coordinate system. These three properties are tightly integrated.

If you change a view’s frame, the view updates to match the new frame. If you use a bigger width, the view stretches. If you use a new location, the view moves. The view’s frame delineates each view’s outline. View sizes are not limited to their superview size or even the screen size. A view can be smaller or larger than the screen. It can also be smaller or larger than its parent. When a subview is larger, the subview’s visible area overflows the edges of the parent. You can use clipToBounds on the parent view to restrict the child from displaying outside the parent’s bounds.

Views also use a transform property that updates a view’s presentation, via affine transformations. These are mathematical equations that adjust a view’s 2D geometry. A view might be stretched or squashed by applying a transform, or it might be rotated away from vertical. Together, the frame and transform fully define a view’s core geometry.

Frames

A frame rectangle refers to the outline of a view in terms of its parent’s coordinate system. Frames use a CGRect structure, which is defined as part of the Core Graphics framework, as its CG prefix suggests. A CGRect is made up of an origin (a CGPoint, x and y) and a size (a CGSize, width and height). When you create views outside Auto Layout, you normally allocate them and initialize them with a frame. Here’s an example:

CGRect rect = CGRectMake(0.0f, 0.0f, 320.0f, 416.0f);
myView = [[UIView alloc] initWithFrame:rect];

Views provide two fundamental CGRect properties, which are closely tied together: frame and bounds. Frames are different from bounds in terms of their coordinate system. Frames are defined with respect to the parent’s system. Bounds are defined with respect to the view’s own coordinate system. For that reason, a view’s bounds typically use a zero origin. Its coordinate system normally begins at the top-left corner. For some views, like scroll views, bounds may extend beyond their visual frame.

Rectangle Utility Functions

As you’ve seen, the CGRectMake() function creates its new rectangle using four parameters: the origin’s x and y locations, the width of the rectangle, and the height. This method is a critical utility for creating frames. You may want to be aware of several other convenience functions, in addition to CGRectMake(), that help you work with rectangles and frames:

Image NSStringFromCGRect(aCGRect) converts a CGRect structure to a formatted string. This function makes it easy to log a view’s frame when you’re debugging.

Image CGRectFromString(aString) recovers a rectangle from its string representation. It is useful when you’ve stored a view’s frame as a string in user defaults and want to convert that string back to a CGRect.

Image Although not a function, [NSValue valueWithCGRect:rect] returns a new Objective-C value object that stores the passed rectangle. You can then add the object to dictionaries and arrays as needed. The CGRectValue method retrieves the rectangular structure from theNSValue object. Variations on this approach exist for most Core Graphics types, including points, sizes, and affine transforms.

Image CGRectInset(aRect, xinset, yinset) enables you to create a smaller or larger rectangle that’s centered on the same point as the source rectangle. Use a positive inset for smaller rectangles, and use a negative inset for larger ones.

Image CGRectOffset(aRect, xoffset, yoffset) returns a rectangle that’s offset from the original rectangle by x and y amounts that you specify. This is handy for moving frames around the screen and for creating easy drop-shadow effects.

Image CGRectGetMidX(aRect) and CGRectGetMidY(aRect) recover the x and y coordinates in the center of a rectangle. These functions make it very convenient to recover the midpoints of bounds and frames.

Image CGRectIntersectsRect(rect1, rect2) lets you know whether rectangle structures intersect. Use this function to know when two rectangular objects overlap. You can retrieve the actual intersection via CGRectIntersection(rect1, rect2). This returns the null rectangle if the two rects do not intersect. (Use CGRectIsNull(rect) to check.) The related CGRectContainsPoint(rect, point) returns true when a provided point is located within the (non-null) rectangle.

Image Compare rectangles using CGRectEqualToRect(rect1, rect2). This function checks whether two rectangles are equal in both their size and position. Similar methods include CGSizeEqualToSize(size1, size2) and CGPointEqualToPoint(point1, point2), which allow you to compare CGSize and CGPoint instances.

Image Other handy utilities include CGRectDivide(), which splits a source rectangle into two components, and CGRectApplyAffineTransform(rect, transform), which applies an affine transform to a rectangle and returns the smallest rectangle that can contain the results.

Image CGRectZero is a rectangle constant located at (0,0) whose width and height are zero. You can use this constant when you’re required to create a frame but are unsure what that frame size or location will be at the time of creation. Similar constants are CGPointZero andCGSizeZero.

Points and Sizes

The CGRect structure is made up of two substructures: CGPoint, which defines the rectangle’s origin, and CGSize, which defines its bounds. Points refer to locations defined with x and y coordinates; sizes have width and height. Use CGPointMake(x, y) to create points.CGSizeMake(width, height) creates sizes. Although these two structures appear to be the same (two floating-point values), the iOS SDK differentiates between them semantically. Points refer to locations. Sizes refer to extents. You cannot set myFrame.origin to a size.

Since CGRect, CGPoint, and CGSize are all structs, you can use a range of flexible struct initializations:

CGPoint origin = {0, 0};
CGSize size = {100, 200};
CGRect rect1 = CGRectMake(0, 0, 100, 200);
CGRect rect2 = {{0, 0}, {100, 200}};
CGRect rect3 = {origin, size};
CGRect rect4 = {origin, {100, 200}}
CGRect rect5 = {.size.width = 100, .size.height = 200, .origin = {0, 0}};

All of these CGRects are identical.

As with rectangles, you can convert the other structs to and from strings. NSStringFromCGPoint(), NSStringFromCGSize(),CGPointFromString(), and CGSizeFromString() perform these functions. You can also transform points and sizes to and from dictionaries.

Transforms

The iOS SDK includes affine transformations as part of its Core Graphics implementation. Affine transforms allow points in one coordinate system to transform into another coordinate system. These functions are widely used in both 2D and 3D animations. The version used with UIKit views uses a 3-by-3 matrix to define UIView transforms, making it a 2D-only solution. 3D transforms use a 4-by-4 matrix and are the default for Core Animation layers. With affine transforms, you can scale, translate, and rotate your views in real time. You do so by setting a view’s transformproperty. Here’s an example:

float angle = theta * (PI / 100.0);
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
myView.transform = transform;

The transform is always applied with respect to the view’s center. So when you apply a rotation like this, the view rotates around its center. If you need to rotate around another point, you must first translate the view to the desired point, then rotate, and then return from that translation. There are ways around this, involving working directly with the view’s layer property, but that approach is beyond the scope of this chapter.

To revert any changes, set the transform property to the identity transform. This restores the view back to the last settings for its frame:

myView.transform = CGAffineTransformIdentity;


Note

On iOS, the y coordinate starts at the top and increases downward. This is similar to the coordinate system in PostScript but opposite the Quartz coordinate system historically used on the Mac. On iOS, the origin is in the top-left corner, not the bottom left. iOS continues to move many features originally grounded in Quartz and Core Graphics into the UIKit world. This migration reduces the number of times you need to flip your coordinate system when laying out text or processing images.


Coordinate Systems

As mentioned earlier, views live in two worlds. The frame and center of a view are defined in the coordinate system of its parents. The bounds and subviews of a view are defined in their own coordinate system. The iOS SDK offers several utilities that allow you to move between these coordinate systems, as long as the views involved live within the same UIWindow. To convert a point from another view into your own coordinate system, use convertPoint:fromView:. Here’s an example:

myPoint = [myView convertPoint:somePoint fromView:otherView];

If the original point indicated the location of some object, the new point retains that location but gives the coordinates with respect to myView’s origin. To go the other way, use convertPoint:toView: to transform a point into another view’s coordinate system. Similarly,convertRect:toView: and convertRect:fromView: work with CGRect structures rather than CGPoint ones.

Be aware that the coordinate system for an iOS device may not match the pixel system used to display that system. The discrete 640×960-pixel Retina display on the iPhone 4S, for example, is addressed through a continuous 320×480 coordinate system in the SDK, defined as points. Although you can supply higher-quality art to fill those pixels on Retina display units, any locations you specify in points in your code access the coordinate based on the resolution of the lower pixel–density units. The position (160.0, 240.0) in points remains approximately in the center of the 3.5-inch iPhone or iPod touch screens, regardless of pixel density. That center point moves to (160.0, 284.0) on 4-inch iPhones and iPod touches, which use Retina displays.


Note

The UIScreen class provides a property called scale that defines the relationship between a display’s pixel density and its point system. A screen’s scale is used to convert from the logical coordinate space of the view system (measured in points and approximately equal to 1/160 inch) to the physical pixel coordinates. Retina displays use a scale of 2.0, and non-Retina displays use a scale of 1.0.


Recipe: Working with View Frames

When you change a view’s frame manually (rather than letting Auto Layout do the dirty work), you update its size (that is, its width and height) and its location. For example, you might move a frame as follows:

CGRect initialRect = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
myView = [[UIView alloc] initWithFrame:initialRect];
[topView addSubview:myView];
myView.frame = CGRectMake(0.0f, 30.0f, 100.0f, 100.0f);

This code creates a subview located at (0.0, 0.0) and then moves it down to (0.0, 30.0).

This approach for moving views is fairly uncommon. The iOS SDK does not expect you to move views by changing frames. Instead, it focuses on a view’s position. The preferred way to do this is by setting the view’s center. This is a view property, which you can set directly:

myView.center = CGPointMake(160.0f, 55.0f);

Although you might expect the SDK to offer a way to move a view by updating its origin, no such option exists. It’s easy enough to build your own view class category. Retrieve the view frame, set the origin to the requested point, and then update the frame with the change. This snippet creates a new origin property that lets you retrieve and change the view’s origin:

- (void)setOrigin:(CGPoint)aPoint
{
CGRect newFrame = self.frame;
newFrame.origin = aPoint;
self.frame = newFrame;
}

Because this extension uses such an obvious property name, if Apple eventually implements the features shown here, your code may break due to name overlap. In the examples in this book, we widely use obvious names. This makes code snippets easier to read and reduces any cognitive burden in recognizing what is being demonstrated. Avoid using obvious names in your production code. Using your personal or company initials as a prefix helps distinguish in-house material.

When you move a view, you don’t need to worry about things such as rectangular sections having been exposed or hidden. iOS takes care of the redrawing. This lets you treat your views like tangible objects and delegate rendering issues to Cocoa Touch.

Adjusting Sizes

In the simplest usage patterns, a view’s frame and bounds control its size. A frame, as you’ve already seen, defines the location of a view in its parent’s coordinate system. If the frame’s origin is set to (0.0, 30.0), the view appears in the superview flush with the left side of the view and offset 30 points from the top. On non-Retina displays, this corresponds to 30 pixels down; on Retina displays, it is 60 pixels down.

Bounds define a view within its own coordinate system. Therefore, the origin for a view’s bounds (that is, myView.bounds) is normally (0.0, 0.0). For most views, the size matches the normal extent—that is, the frame’s size property. (This isn’t always true for some classes, likeUIScrollView, whose extent may exceed the visual display.)

You can change a view’s size by adjusting either its frame or its bounds. In practical terms, you’re updating the size component of those structures. As with moving origins, it’s simple to create your own view utility method to do this directly:

- (void)setSize:(CGSize)aSize
{
CGRect newbounds = self.bounds;
newbounds.size = aSize;
self.bounds = newbounds;
}

When a displayed view’s size changes, the view itself updates live. Depending on how the elements within the view are defined and the class of the view itself, subviews may shrink or move to fit, or they may get cropped, depending on a number of flags and whether views are participating in the Auto Layout system:

Image The autoresizesSubviews property determines whether a view automatically resizes its subviews when it updates its bounds.

Image A view’s autoresizingMask property defines how a view reacts to changes in its parent’s bounds. If a view participates in a constraint system, this mask is ignored, and the view will be adjusted by iOS’s Auto Layout system.

Image The clipsToBounds flag determines whether subviews are visible outside a view’s bounds. When clipped, only material within the parent’s bounds are shown. You can use sizeToFit on a view so that it resizes to enclose all its subviews.

Image The contentMode property is related to other view-resizing properties but specifies how a view’s layer (its content bitmap) adjusts when its bounds update. This property, which can be set to a number of scaling, centering, and fitting choices, is best seen when working with image views.


Note

Bounds are affected by a view’s transform, a mathematical component that changes the way the view appears. Do not manipulate a view’s frame when working with transforms because doing so may not produce expected results. (Some workarounds follow later in this chapter.) For example, after a transform, the frame’s origin may no longer correspond mathematically to the origin of the bounds. The normal order of updating a view is to set its frame or bounds, then set its center, and then set its transforms, if applicable.


Sometimes, you need to resize a view before adding it to a new parent. For example, you might have an image view to place into an alert view. To fit that view into place without changing its aspect ratio, you can use a method like this to ensure that both the height and width scale appropriately:

- (void)fitInSize:(CGSize)aSize
{
CGFloat scale;
CGRect newframe = self.frame;

if (newframe.size.height > aSize.height)
{
scale = aSize.height / newframe.size.height;
newframe.size.width *= scale;
newframe.size.height *= scale;
}

if (newframe.size.width > aSize.width)
{
scale = aSize.width / newframe.size.width;
newframe.size.width *= scale;
newframe.size.height *= scale;
}
self.frame = newframe;
}

CGRects and Centers

As you’ve seen, UIView instances use a CGRect structure, composed of an origin and a size, to define their frame. A CGRect structure contains no references to a center point. At the same time, UIViews depend on their center property to update a view’s position when you move a view to a new point. Unfortunately, Core Graphics doesn’t use centers as a primary rectangle concept. As far as centers are concerned, the built-in utilities in Core Graphics are limited to recovering a rectangle’s midpoint along the x- or y-axis.

You can bridge this gap by constructing functions that coordinate between the origin-based CGRect struct and center-based UIView objects. Such a function retrieves the center from a rectangle by building a point from the x and y midpoints. It takes one argument, a rectangle, and returns its center point:

CGPoint CGRectGetCenter(CGRect rect)
{
CGPoint pt;
pt.x = CGRectGetMidX(rect);
pt.y = CGRectGetMidY(rect);
return pt;
}

Moving a rectangle by its center point is another function that may prove helpful, and one that mimics the way UIViews work. Suppose, for example, that you need to move a view to a new position but need to keep it inside its parent’s frame. To test before you move, you could use a function like this to offset the view frame to a new center:

CGRect CGRectMoveToCenter(CGRect rect, CGPoint center)
{
CGRect newrect = CGRectZero;
newrect.origin.x = center.x-(rect.size.width/2.0);
newrect.origin.y = center.y-(rect.size.height/2.0);
newrect.size = rect.size;
return newrect;
}

You could then test that offset frame against the parent (use CGRectContainsRect()) and ensure that the view won’t stray outside its container.

Often you need to center one view in another. You can retrieve a rectangle that corresponds to a centered subrectangle by passing the outer view’s bounds when adding a subview (the subview coordinate system needs to start with 0, 0) or its frame when adding a view to the outer view’s parent:

CGRect CGRectCenteredInRect(CGRect subRect, CGRect mainRect)
{
CGFloat xOffset = CGRectGetMidX(mainRect)-CGRectGetMidX(subRect);
CGFloat yOffset = CGRectGetMidY(mainRect)-CGRectGetMidY(subRect);
return CGRectOffset(rect, xOffset, yOffset);
}

Other Geometric Elements

As you’ve seen, it’s convenient to use a view’s origin and size as well as its center property, which allows you to work more natively with Core Graphics calls. You can build on this idea to expose other properties of the view, including its width and height, as well as basic geometry, such as its left, right, top, and bottom points. In some ways, this breaks Apple’s design philosophy. It exposes items that normally fall into structures without reflecting the structures. At the same time, it can be argued that these elements are true view properties. They reflect fundamental view characteristics and deserve to be exposed as properties.

Recipe 4-4 provides a full view frame utility category for UIView, which lets you make the choice about whether to expose these properties. These properties do not take transforms into account.


Note

While Auto Layout is rendering many of the utility methods in this recipe less critical than in the past, these methods still provide great value, both when you fall back to manual layout or even occasionally when you’re using Auto Layout.


Recipe 4-4 UIView Frame Geometry Category


@interface UIView (ViewFrameGeometry)
@property CGPoint origin;
@property CGSize size;

@property (readonly) CGPoint midpoint;

// topLeft is synonymous with origin so not included here
@property (readonly) CGPoint bottomLeft;
@property (readonly) CGPoint bottomRight;
@property (readonly) CGPoint topRight;

@property CGFloat height;
@property CGFloat width;
@property CGFloat top;
@property CGFloat left;
@property CGFloat bottom;
@property CGFloat right;

- (void)moveBy:(CGPoint)delta;
- (void)scaleBy:(CGFloat)scaleFactor;
- (void)fitInSize:(CGSize)aSize;
@end

@implementation UIView (ViewGeometry)
// Retrieve and set the origin
- (CGPoint)origin
{
return self.frame.origin;
}

- (void)setOrigin:(CGPoint)aPoint
{
CGRect newFrame = self.frame;
newFrame.origin = aPoint;
self.frame = newFrame;
}

// Retrieve and set the size
- (CGSize)size
{
return self.frame.size;
}

- (void)setSize:(CGSize)aSize
{
CGRect newFrame = self.frame;
newFrame.size = aSize;
self.frame = newFrame;
}

// Query other frame locations

- (CGPoint)midpoint
{
// midpoint is with respect to a view's own coordinate system
// versus its center, which is with respect to its parent
CGFloat x = CGRectGetMidX(self.bounds);
CGFloat y = CGRectGetMidY(self.bounds);
return CGPointMake(x, y);
}

- (CGPoint)bottomRight
{
CGFloat x = self.frame.origin.x + self.frame.size.width;
CGFloat y = self.frame.origin.y + self.frame.size.height;
return CGPointMake(x, y);
}

- (CGPoint)bottomLeft
{
CGFloat x = self.frame.origin.x;
CGFloat y = self.frame.origin.y + self.frame.size.height;
return CGPointMake(x, y);
}

- (CGPoint)topRight
{
CGFloat x = self.frame.origin.x + self.frame.size.width;
CGFloat y = self.frame.origin.y;
return CGPointMake(x, y);
}


// Retrieve and set height, width, top, bottom, left, right
- (CGFloat)height
{
return self.frame.size.height;
}

- (void)setHeight:(CGFloat)newHeight
{
CGRect newFrame = self.frame;
newFrame.size.height = newHeight;
self.frame = newFrame;
}

- (CGFloat)width
{
return self.frame.size.width;
}

- (void)setWidth:(CGFloat)newWidth
{
CGRect newFrame = self.frame;
newFrame.size.width = newWidth;
self.frame = newFrame;
}

- (CGFloat)top
{
return self.frame.origin.y;
}

- (void)setTop:(CGFloat)newTop
{
CGRect newFrame = self.frame;
newFrame.origin.y = newTop;
self.frame = newFrame;
}

- (CGFloat)left
{
return self.frame.origin.x;
}

- (void)setLeft:(CGFloat)newLeft
{
CGRect newFrame = self.frame;
newFrame.origin.x = newLeft;
self.frame = newFrame;
}

- (CGFloat)bottom
{
return self.frame.origin.y + self.frame.size.height;
}

- (void)setBottom:(CGFloat)newBottom
{
CGFloat delta = newBottom –
(self.frame.origin.y + self.frame.size.height);
CGRect newFrame = self.frame;
newFrame.origin.y += delta;
self.frame = newFrame;
}

- (CGFloat)right
{
return self.frame.origin.x + self.frame.size.width;
}

- (void)setRight:(CGFloat)newRight
{
CGFloat delta = newRight–
(self.frame.origin.x + self.frame.size.width);
CGRect newFrame = self.frame;
newFrame.origin.x += delta;
self.frame = newFrame;
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Recipe: Retrieving Transform Information

Affine transforms enable you to change an object’s geometry by mapping that object from one view coordinate system into another. The iOS SDK fully supports standard affine 2D transforms. With them, you can scale, translate, rotate, and skew your views however your heart desires and your application demands.

Transforms are defined in Core Graphics and consist of calls such as CGAffineTransformMakeRotation() and CGAffineTransformScale(). These build and modify 3-by-3 transform matrices. After these are built, use UIView’s transform property to assign 2D affine transformations to UIView objects.

For example, you might apply a rotation transform directly. This removes any existing transform and replaces it with a simple rotation. The functions with Make in their name create new transforms:

theView.transform = CGAffineTransformMakeRotation(radians);

Or you might add a scaling transform onto whatever transformations have already been applied to the view. The functions without the word Make take a transform as their first parameter and return an updated transform after applying a transformation according to the function arguments:

CGAffineTransform scaled = CGAffineTransformScale(theView.transform,
scaleX, scaleY);
theView.transform = scaled;

Retrieving Transform Properties

When working with transforms, iOS can provide an affine representation of the transform associated with a view. This representation will not, however, tell you exactly how much the view has been scaled or rotated. Recipe 4-5 addresses this problem by calculating the scale and rotation via a simple UIView category.

An affine matrix is stored in iOS as a structure of six fields: a, b, c, d, tx, and ty. Figure 4-4 shows how these values relate to their positions in the standard affine matrix. Simple math allows you to derive scaling and rotation from these, as shown in Recipe 4-5. Note how you can retrieve the tx and ty values directly from the transform. If linear algebra isn’t in your wheelhouse, don’t worry; you really don’t have to understand how these transforms work in order to use them successfully.

Image

Figure 4-4 The CGAffineTransform structure holds an affine transformation matrix by defining six key values in its fields (left). After applying an affine transform, a view’s origin may no longer coincide with its frame’s origin (right).

In addition to answering the questions “What is the view’s current rotation?” and “By how much is it scaled?” you often need to perform math that relates the current geometry post-transform to the parent coordinate system. To do this, you need to be able to specify where elements appear onscreen.

A view’s center makes the transition from pre-transform to post-transform without incident. The value may change, especially after scaling, but the property remains meaningful, regardless of what transform has been applied. The center property always refers to the geometric center of the view’s frame in the parent’s coordinate system.

The frame is not so resilient. After rotation, a view’s origin may be completely decoupled from the view. Look at Figure 4-4 (right). It shows a rotated view on top of its original frame (the smaller of the two outlines) and the updated frame (the larger outline). The circles indicate the view’s origin before and after rotation.

After the transform is applied, the frame updates to the minimum bounding box that encloses the view. Its new origin (the top-left corner of the outside view) has essentially nothing to do with the updated view origin (the circle at the top-middle). iOS does not provide a way to retrieve that adjusted point.

Recipe 4-5 introduces several methods that perform the math for you. It establishes properties that return a transformed view’s corners: top left, top right, bottom left, and bottom right. These coordinates are defined in the parent view; if you want to add a new view on top of the top circle inFigure 4-4 (right), you place its center at theView.transformedTopLeft.

The recipe also offers the originalFrame method, which returns the inner (original) frame shown in Figure 4-4, even when a transform has been applied. It does so in a rather ham-fisted way, but it works.

Testing for View Intersection

By reader request, Recipe 4-5 adds code to check whether two transformed views intersect. The code also works with views that have not been transformed so that you can use it with any two views, although it’s a bit pointless to do so. (You can use the CGRectIntersectsRect()function for simple untransformed frames.) This custom intersection method works best for views whose frames do not represent their underlying geometry, like the one shown in Figure 4-4.

The intersectsView: method applies an axis separation algorithm for convex polygons. For each edge of each view, it tests whether all the points in one view fall on one side of the edge and whether all the points of the other view fall on the other side. This test is based on the half plane function, which returns a value indicating whether a point is on the left or right side of an edge.

As soon as it finds an edge that satisfies this condition, the intersectsView: method returns NO. The views cannot geometrically intersect if there’s a line that separates all the points in one object from all the points in the other.

If all eight tests fail (four edges on the first view, four edges on the second), the method concludes that the two views do intersect. It returns YES.

Recipe 4-5 Retrieving Transform Values


@implementation UIView (Transform)
- (CGFloat)xScale
{
CGAffineTransform t = self.transform;
return sqrt(t.a * t.a + t.c * t.c);
}

- (CGFloat)yScale
{
CGAffineTransform t = self.transform;
return sqrt(t.b * t.b + t.d * t.d);
}

- (CGFloat)rotation
{
CGAffineTransform t = self.transform;
return atan2f(t.b, t.a);
}

- (CGFloat)tx
{
CGAffineTransform t = self.transform;
return t.tx;
}

- (CGFloat)ty
{
CGAffineTransform t = self.transform;
return t.ty;
}

// The following three methods move points into and out of the
// transform coordinate system whose origin is at the view center

- (CGPoint)offsetPointToParentCoordinates:(CGPoint)aPoint
{
return CGPointMake(aPoint.x + self.center.x,
aPoint.y + self.center.y);
}

- (CGPoint)pointInViewCenterTerms:(CGPoint)aPoint
{
return CGPointMake(aPoint.x - self.center.x, aPoint.y - self.center.y);
}

- (CGPoint)pointInTransformedView:(CGPoint)aPoint
{
CGPoint offsetItem = [self pointInViewCenterTerms:aPoint];
CGPoint updatedItem = CGPointApplyAffineTransform(
offsetItem, self.transform);
CGPoint finalItem =
[self offsetPointToParentCoordinates:updatedItem];
return finalItem;
}

// Return the original frame without transform
- (CGRect)originalFrame
{
CGAffineTransform currentTransform = self.transform;
self.transform = CGAffineTransformIdentity;
CGRect originalFrame = self.frame;
self.transform = currentTransform;

return originalFrame;
}

// These four methods return the positions of view elements
// with respect to the current transform

- (CGPoint)transformedTopLeft
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
return [self pointInTransformedView:point];
}

- (CGPoint)transformedTopRight
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
point.x += frame.size.width;
return [self pointInTransformedView:point];
}

- (CGPoint)transformedBottomRight
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
point.x += frame.size.width;
point.y += frame.size.height;
return [self pointInTransformedView:point];
}

- (CGPoint)transformedBottomLeft
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
point.y += frame.size.height;
return [self pointInTransformedView:point];
}

// Determine if two views intersect, with respect to any
// active transforms

// After extending a line, determine which side of the half
// plane defined by that line, a point will appear
BOOL halfPlane(CGPoint p1, CGPoint p2, CGPoint testPoint)
{
CGPoint base = CGPointMake(p2.x - p1.x, p2.y - p1.y);
CGPoint orthog = CGPointMake(-base.y, base.x);
return (((orthog.x * (testPoint.x - p1.x)) +
(orthog.y * (testPoint.y - p1.y))) >= 0);
}

// Utility test for testing view points against a proposed line
BOOL intersectionTest(CGPoint p1, CGPoint p2, UIView *aView)
{
BOOL tlTest = halfPlane(p1, p2, aView.transformedTopLeft);
BOOL trTest = halfPlane(p1, p2, aView.transformedTopRight);
if (tlTest != trTest) return YES;

BOOL brTest = halfPlane(p1, p2, aView.transformedBottomRight);
if (tlTest != brTest) return YES;

BOOL blTest = halfPlane(p1, p2, aView.transformedBottomLeft);
if (tlTest != blTest) return YES;

return NO;
}

// Determine whether the view intersects a second view
// with respect to their transforms
- (BOOL)intersectsView:(UIView *)aView
{
if (!CGRectIntersectsRect(self.frame, aView.frame)) return NO;

CGPoint A = self.transformedTopLeft;
CGPoint B = self.transformedTopRight;
CGPoint C = self.transformedBottomRight;
CGPoint D = self.transformedBottomLeft;

if (!intersectionTest(A, B, aView))
{
BOOL test = halfPlane(A, B, aView.transformedTopLeft);
BOOL t1 = halfPlane(A, B, C);
BOOL t2 = halfPlane(A, B, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(B, C, aView))
{
BOOL test = halfPlane(B, C, aView.transformedTopLeft);
BOOL t1 = halfPlane(B, C, A);
BOOL t2 = halfPlane(B, C, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(C, D, aView))
{
BOOL test = halfPlane(C, D, aView.transformedTopLeft);
BOOL t1 = halfPlane(C, D, A);
BOOL t2 = halfPlane(C, D, B);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(D, A, aView))
{
BOOL test = halfPlane(D, A, aView.transformedTopLeft);
BOOL t1 = halfPlane(D, A, B);
BOOL t2 = halfPlane(D, A, C);
if ((t1 != test) && (t2 != test)) return NO;
}

A = aView.transformedTopLeft;
B = aView.transformedTopRight;
C = aView.transformedBottomRight;
D = aView.transformedBottomLeft;

if (!intersectionTest(A, B, self))
{
BOOL test = halfPlane(A, B, self.transformedTopLeft);
BOOL t1 = halfPlane(A, B, C);
BOOL t2 = halfPlane(A, B, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(B, C, self))
{
BOOL test = halfPlane(B, C, self.transformedTopLeft);
BOOL t1 = halfPlane(B, C, A);
BOOL t2 = halfPlane(B, C, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(C, D, self))
{
BOOL test = halfPlane(C, D, self.transformedTopLeft);
BOOL t1 = halfPlane(C, D, A);
BOOL t2 = halfPlane(C, D, B);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(D, A, self))
{
BOOL test = halfPlane(D, A, self.transformedTopLeft);
BOOL t1 = halfPlane(D, A, B);
BOOL t2 = halfPlane(D, A, C);
if ((t1 != test) && (t2 != test)) return NO;
}

return YES;
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Display and Interaction Traits

In addition to physical screen layout, the UIView class provides properties that control how your view appears and whether users can interact with it. Every view uses an opaqueness factor (alpha) that ranges between opaque and transparent. Adjust this by issuing [myView setAlpha:value] or setting the myView.alpha property where the alpha values fall between 0.0 (fully transparent) and 1.0 (fully opaque). This is a great way to fade views in and out. Use the hidden property to hide views entirely without animation.

You can assign a color to the background of any view. For example, the following property colors your view red:

myView.backgroundColor = [UIColor redColor];

This property affects different view classes in different ways, depending on whether those views contain subviews that block the background. Create a transparent background by setting the view’s background color to clear, as shown here:

myView.backgroundColor = [UIColor clearColor];

Every view offers a background color property, regardless of whether you can see the background. Using bright, contrasting background colors is a great way to visualize the true extents of views. When you’re new to iOS development, coloring in views provides you a concrete sense of what is and is not onscreen and where each component is located.

Not all colors are solid tints. The UIColor class lets you use tiled patterns just as you would use solid colors. The colorWithPatternImage: method returns a UIColor instance built from a pattern image you supply. This method helps build textures that you can use to color views.

The userInteractionEnabled property controls whether users can touch and interact with a given view. For most views, this property defaults to YES. For UIImageView, it defaults to NO, which can cause a lot of grief among beginning developers. They often place aUIImageView as their backsplash and don’t understand why their switches, text entry fields, and buttons do not work. Make sure to enable the property for any view that needs to accept touches, whether for itself or for its subviews, which may include buttons, switches, pickers, and other controls. If you’re experiencing trouble with items that seem unresponsive to touch, check the userInteractionEnabled property value for that item and for its parents.

Disable this property for any display-only view you layer over your interaction area. To show a noninteractive overlay clock, for example, via a transparent full-screen view, disable its interaction by assigning its userInteractionEnabled flag to NO. This allows touches to pass through the view and fall below to the actual interaction area of your application. A view with its userInteractionEnabled flag set to NO only stops the flagged view from receiving the touches; these touches will continue through the view to any underlying views. To create a please-wait-style blocker, make sure to enable user interaction for your overlay. This catches user taps and prevents users from accessing your primary interface behind that overlay.

You may also want to disable interaction during transitions to ensure that user taps do not trigger actions as views are being animated. Unwanted touches can be problematic, particularly for games and puzzles.

UIView Animations

UIView animation is one of the odd but lovely perks of working with iOS as a development platform. It enables you to create a moving expression of visual changes when updating views, producing smooth animated results that enhance the user experience. Best of all, this occurs without requiring you to do much work.

UIView animations are perfect for building a visual bridge between a view’s current and changed states. With them, you emphasize visual change and create an animation that links together those changes. Changes that can be animated include the following:

Image Changes in location—Moving a view around the screen by updating its center

Image Changes in size—Updating the view’s frame and bounds

Image Changes in stretching—Updating the view’s content stretch regions

Image Changes in transparency—Altering the view’s alpha value

Image Changes in color—Updating a view’s background color

Image Changes in rotation, scaling, and translation—Basically, any affine transforms you apply to a view

Animations underwent a profound redesign between the 3.x and 4.x SDKs. Starting with the 4.x SDK, developers were offered a way to use the new Objective-C blocks paradigm to simplify animation tasks. Although you can still work with the original animation transaction techniques, the new alternatives provide a much easier approach, and the Apple documentation specifically discourages the old-style approach.


Note

Most Apple-native animations last about one-third or one-half second. When working with helper views (playing supporting roles that are similar to Apple’s keyboard or alerts), you may want to match your animation durations to these timings. Call [UIApplication statusBarOrientationAnimationDuration] to retrieve a standard time interval.


Building Animations with Blocks

Blocks constructs simplify the creation of basic animation effects in your code. Consider the following snippet, which produces a fade-out effect for a view with a single statement in an embedded block:

[UIView animationWithDuration: 1.0f
animations:^{contentView.alpha = 0.0f;}];

Adding a completion block lets you tidy up after your animation finishes. The following snippet fades out the content view and then removes it from its superview when the animation completes:

[UIView animationWithDuration: 1.0f
animations:^{contentView.alpha = 0.0f;}
completion:^(BOOL done){[contentView removeFromSuperview];}];

When you need to add further options to your animations, a full-service blocks-based method (animateWithDuration:delay:options:animations:completion:) provides both a way to pass animation options (as a mask) and to delay the animation (allowing a simple approach to animation “chaining”).

When working with animation constants, be sure to use the modern UIViewAnimationOptions varieties, which have the word Options in their names. Older constants like UIViewAnimationCurveEaseInOut will not work with post-iOS 4.x calls.

Occasionally, it is necessary to ensure that a view property change is excluded from an animation. This is particularly useful when you aren’t sure where your view changes will be called, such as from within a developer-created animation block or certain system methods that are already within an animation block. In iOS 7, Apple provides the performWithoutAnimation: method on UIView, which accepts a block, much like the animation methods. Any view property changes inside the block will be excluded from animation, even when called from an encapsulating animation block.

Recipe: Fading a View In and Out

At times, you want to add information to your screen that overlays your view but does not of itself do anything. For example, you might show a top-scores list or some instructions or provide a context-sensitive tooltip. Recipe 4-6 demonstrates how to use a UIView animation block to fade a view into and out of sight. This recipe follows the most basic animation approach. It creates a view animation block that sets the alpha property.

Note how this code controls the behavior of the right bar button item. When tapped, it is immediately disabled until the animation concludes. The animation’s completion block reenables the button and flips the button text and callback selector to the opposite state. This allows the button to toggle the animation from on to off and from off to back on.

Recipe 4-6 Animating Transparency Changes to a View’s Alpha Property


- (void)fadeOut:(id)sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
[UIView animateWithDuration:1.0f
animations:^{
// Here's where the actual fade out takes place
imageView.alpha = 0.0f;
}
completion:^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Fade In", @selector(fadeIn:));
}];
}


- (void)fadeIn:(id)sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
[UIView animateWithDuration:1.0f
animations:^{
// Here's where the fade in occurs
imageView.alpha = 1.0f;
}
completion:^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Fade Out", @selector(fadeOut:));
}];
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Recipe: Swapping Views

The UIView animation block doesn’t limit you to a single change. Place as many animation differences as needed in an animation block. Recipe 4-7 combines size transformations with transparency changes to create a more compelling animation. It does this by adding several directives simultaneously to the animation block. This recipe performs five actions at a time. It zooms and fades one view into place while zooming out and fading away another and then exchanges the two in the subview array list.

You’ll want to prepare the back object for its initial animation by shrinking it and making it transparent. When the swap: method first executes, that view will be ready to appear and zoom to size. As with Recipe 4-6, the completion block reenables the bar button on the right, allowing successive presses.

Recipe 4-7 Combining Multiple View Changes in Animation Blocks


@implementation TestBedViewController
- (void)swap:(id)sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
[UIView animateWithDuration:1.0f
animations:^{
frontObject.alpha = 0.0f;
backObject.alpha = 1.0f;
frontObject.transform = CGAffineTransformMakeScale(0.25f, 0.25f);
backObject.transform = CGAffineTransformIdentity;
[self.view exchangeSubviewAtIndex:0
withSubviewAtIndex:1];
}
completion:^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;

// Swap the view references
UIImageView *tmp = frontObject;
frontObject = backObject;
backObject = tmp;
}];
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Recipe: Flipping Views

Transitions extend UIView animation blocks to add even more visual flair. Several transition styles do just what their names suggest. You can flip views to their backs and curl views up and down in the manner of the Maps application. Recipe 4-8 demonstrates how to include these transitions in your interfaces.

Here’s a list of the set of transitions in iOS 7.0. You can see that there are four flips, two curls, a cross dissolve, and a “do nothing” no-op choice:

Image UIViewAnimationOptionTransitionNone

Image UIViewAnimationOptionTransitionFlipFromLeft

Image UIViewAnimationOptionTransitionFlipFromRight

Image UIViewAnimationOptionTransitionFlipFromTop

Image UIViewAnimationOptionTransitionFlipFromBottom

Image UIViewAnimationOptionTransitionCurlUp

Image UIViewAnimationOptionTransitionCurlDown

Image UIViewAnimationOptionTransitionCrossDissolve

Recipe 4-8 uses the block-based transitionFromView:toView:duration:options:completion: API. This method replaces a view by removing it from its superview and adding the new view to the initial view’s parent. It animates this over the supplied duration, using the transition specified in the options flags. Recipe 4-8 uses a flip-from-left transition, although you can use any of the other transitions as desired.

The related transitionWithView:duration:options:animations:completion: method provides even more flexibility. It takes an animations block as a parameter, allowing for a completely custom transition. You can use it to create shrink/grow, flip, and other complex view transition animations.

If you use constraints (see Chapter 5), you must redefine them as well. Removing a subview invalidates and removes from the superview all constraints that refer to that view.

Recipe 4-8 Using Transitions with UIView Animations


- (void)flip:(id)sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
UIView *toView = fromPurple ? maroon : purple;
UIView *fromView = fromPurple ? purple : maroon;
[UIView transitionFromView: fromView
toView: toView
duration: 1.0f
options: UIViewAnimationOptionTransitionFlipFromLeft
completion: ^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;
fromPurple = !fromPurple;
CENTER_VIEW(self.view, toView);
}];
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Recipe: Using Core Animation Transitions

In addition to UIView animations, iOS supports Core Animation as part of its Quartz Core framework. The Core Animation API offers highly customizable animation solutions for your iOS applications. Specifically, it offers built-in transitions that provide the same kind of view-to-view changes available in Recipe 4-8, as well as a vast wealth of other fundamental animation possibilities that are beyond the scope of this chapter.


Note

With each release of iOS, Apple has continually added more Core Animation functionality to UIKit directly. iOS 7 introduced key frame animation to UIView—a feature that previously required Core Animation.


Core Animation transitions expand your UIView animation vocabulary with just a few small differences in implementation. CATransitions work on layers rather than on views. Layers are the Core Animation rendering surfaces associated with UIViews. When working with Core Animation, you apply CATransitions to a view’s default layer (myView.layer) rather than to the view itself.

With these transitions, you don’t set your parameters through UIView the way you do with UIView animation. Create a Core Animation object, set its parameters, and then add the parameterized transition to the layer:

CATransition *animation = [CATransition animation];
animation.delegate = self;
animation.duration = 1.0f;
animation.type = kCATransitionMoveIn;
animation.subtype = kCATransitionFromTop;

// Perform some kind of view exchange or removal here

[myView.layer addAnimation:animation forKey:@"move in"];

An animation uses both a type and a subtype. The type specifies the kind of transition used. The subtype sets its direction. Together the type and subtype tell how the views should act when you apply the animation to them.

Core Animation transitions are distinct from the UIViewAnimationTransitions discussed in previous recipes. Cocoa Touch offers four types of Core Animation transitions, which are highlighted in Recipe 4-9. The available types are cross-fades, pushes (one view pushes another offscreen), reveals (one view slides off another), and covers (one view slides onto another). The last three types enable you to specify the direction of motion for the transition by using subtypes. For obvious reasons, cross-fades do not have a direction, and they do not use subtypes.

Because Core Animation is part of the Quartz Core framework, you must use @import QuartzCore in your code when using these features.


Note

Apple’s Core Animation features 2D and 3D routines built around Objective-C classes. These classes provide graphics rendering and animation for your iOS and Mac applications. Core Animation avoids many low-level development details associated with, for example, direct Open GL, while retaining the simplicity of working with hierarchical view layers.


Recipe 4-9 Animating Transitions with Core Animation


- (void)animate:(id)sender
{
// Set up the animation
CATransition *animation = [CATransition animation];
animation.delegate = self;
animation.duration = 1.0f;

switch ([(UISegmentedControl *)self.navigationItem.titleView
selectedSegmentIndex])
{
case 0:
animation.type = kCATransitionFade;
break;
case 1:
animation.type = kCATransitionMoveIn;
break;
case 2:
animation.type = kCATransitionPush;
break;
case 3:
animation.type = kCATransitionReveal;
break;
default:
break;
}
animation.subtype = kCATransitionFromLeft;

// Perform the animation
[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[self.view.layer addAnimation:animation forKey:@"animation"];
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Recipe: Bouncing Views as They Appear

Apple often uses two animation blocks, one called after another finishes, to add bounce to animations. For example, a view might zoom into a view a bit more than needed and then use a second animation to bring that enlarged view down to its final size. Bounces add a little more life to your animation sequences, providing an extra physical touch.

When calling one animation after another, be sure that the animations do not overlap. The easiest way to ensure this is to use a nested set of animation blocks with chained animations in the completion blocks. Recipe 4-10 uses this approach to bounce views slightly larger than their end size and then shrink them back down to the desired frame.

This recipe uses two simple typedefs to simplify the declaration of each animation and completion block. Notice that the animation block stages that do the work of scaling the view in question are defined in order. The first block shrinks the view, the second one zooms it extra large, and the third restores it to its original size.

The completion blocks go the opposite way. Because each block depends on the one before it, you must create them in reverse order. Start with the final side effects and work your way back to the original. In Recipe 4-10, bounceLarge depends on shrinkBack, which in turn depends onreenable. This reverse definition can be a bit tricky to work with, but it certainly beats laying out all your code in nested blocks.

The sample project for this recipe contains an additional helper class (AnimationHelper), which wraps the behavior you see in Recipe 4-10 in a slightly less-awkward package. As Recipe 4-10 demonstrates, trying to layout an animation sequence backward, so each bit can be referenced by the item that called it, gets very clumsy very fast.

The helper class builds the whole block sequence and returns animation blocks similar to those in Recipe 4-10, which are ready to be executed and contain embedded completion blocks.

Recipe 4-10 Bouncing Views


typedef void (^AnimationBlock)(void);
typedef void (^CompletionBlock)(BOOL finished);

- (void)bounce
{
// Prepare for animation
self.navigationItem.rightBarButtonItem.enabled = NO;
bounceView.transform = CGAffineTransformMakeScale(0.0001f, 0.0001f);
bounceView.center = RECTCENTER(self.view.bounds);

// Define the three stages of the animation in forward order
AnimationBlock makeSmall = ^(void){
bounceView.transform = CGAffineTransformMakeScale(0.01f, 0.01f);};
AnimationBlock makeLarge = ^(void){
bounceView.transform = CGAffineTransformMakeScale(1.15f, 1.15f);};
AnimationBlock restoreToOriginal = ^(void) {
bounceView.transform = CGAffineTransformIdentity;};

// Create the three completion links in reverse order
CompletionBlock reenable = ^(BOOL finished) {
self.navigationItem.rightBarButtonItem.enabled = YES;};
CompletionBlock shrinkBack = ^(BOOL finished) {
[UIView animateWithDuration:0.3f
animations:restoreToOriginal completion:reenable];};
CompletionBlock bounceLarge = ^(BOOL finished){
[NSThread sleepForTimeInterval:0.5f]; // wee pause
[UIView animateWithDuration:0.3f
animations:makeLarge completion:shrinkBack];};

// Start the animation
[UIView animateWithDuration: 0.1f
animations:makeSmall completion:bounceLarge];
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Recipe: Key Frame Animations

While nested animation blocks and completion blocks can be used to create fairly advanced animations, the complexity can become overwhelming quickly. In iOS 7, Apple introduced key frame animations to UIKit, powerful animations that are suited for advanced needs. These animations vastly simplify creating complex effects such as the bounce in Recipe 4-10. Key frame animations previously required diving into Core Animation, but now, this powerful animation tool is available directly from UIView.

In traditional key frame animation, you provide the important frames of the animation sequence and their expected timestamps within the animation, and the system is responsible for rendering all the in-between frames to provide for a smooth animation. The simplest form of key frame animation is simply providing the start and end frames and letting the animation system handle the rest.

To use key frame animation with UIView, begin a key frame animation block with animate-KeyframesWithDuration:delay:options:animations:completion:. Within the animation block, set each important reference frame—or, in this case, each animatable UIViewproperty—along with the start time and duration of that specific frame with addKeyframeWithRelative-StartTime:relativeDuration:animations:. The key frame animation blocks will animate in sequence at the appropriate times and durations.

One critical difference from between key frame and other animation methods: With key frame animation, start times and durations range between 0.0 and 1.0, indicating the percentage of progress through the animation. 0.0 represents the beginning of the overall animation, and 1.0 represents the end of the complete animation. For a 2-second animation, a start time of 0.5 corresponds to 1 full second into the animation.

Recipe 4-11 replicates the functionality of Recipe 4-10, this time using key frame animations. No helper class is required, and the code is significantly easier to write, understand, and manage.

Recipe 4-11 Key Frame Animation


- (void)bounce
{
// Prepare for animation
self.navigationItem.rightBarButtonItem.enabled = NO;
bounceView.transform = CGAffineTransformMakeScale(0.0001f, 0.0001f);
bounceView.center = RECTCENTER(self.view.bounds);

// Begin the key frame animation
[UIView animateKeyframesWithDuration:0.6
delay:0.0
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
// Implied first key frame – current view (tiny)
// Second key frame – make view big
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
bounceView.transform =
CGAffineTransformMakeScale(1.15f, 1.15f);
}];

// Third key frame – shrink to normal
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
bounceView.transform =
CGAffineTransformIdentity;
}];
}
completion:^(BOOL finished) {
[self enable:YES];
}];
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Recipe: Image View Animations

In addition to displaying static pictures, the UIImageView class supports built-in animation sequences. After loading an array of image cels, you can tell instances to animate them. Recipe 4-12 shows how.

Start by creating an array populated by individual images loaded from files and assign this array to the UIImageView instance’s animationImages property. Set animationDuration to the total loop time for displaying all the images in the array. Finally, begin animating by sending the startAnimating message. (There’s a matching stopAnimating method available for use as well.)

After you add the animating image view to your interface, you can place it into a single location, or you can animate it just as you would animate any other UIView instance.

Recipe 4-12 Using UIImageView Animation


NSMutableArray *butterflies = [NSMutableArray array];

// Load the butterfly images
for (int i = 1; i <= 17; i++)
[butterflies addObject:[UIImage imageWithContentsOfFile:
[[NSBundle mainBundle]
pathForResource: [NSString stringWithFormat:@"bf_%d", i]
ofType:@"png"]]];

// Create the view
UIImageView *butterflyView = [[UIImageView alloc]
initWithFrame:CGRectMake(40.0f, 300.0f, 100.0f, 51.0f)];

// Set the animation cells and duration
butterflyView.animationImages = butterflies;
butterflyView.animationDuration = 0.75f;
[butterflyView startAnimating];

// Add the view to the parent
[self.view addSubview:butterflyView];



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-7-Cookbook and go to the folder for Chapter 4.


Summary

UIViews provide the components your users see and interact with. As this chapter shows, even in their most basic form, UIViews offer incredible flexibility and power. You have discovered how to use views to build up elements on a screen, retrieve views by tag or name, and introduce eye-catching animation. Here’s a collection of thoughts about the recipes you saw in this chapter that you might want to consider before moving on:

Image When you’re dealing with multiple views, hierarchy should always remain in your mind. Use your view hierarchy vocabulary to take charge of your views and always present the proper visual context to your users.

Image Don’t let the Core Graphics/UIKit center dichotomy stand in your way. Use functions that help you move between these structures to produce the results you need, especially when you’re working with simple views that don’t use transforms.

Image Make friends with tags, whether numeric or custom nametags. They provide immediate access to views in the same way that a program’s symbol table provides access to variables. They are not evil or wrong, and they can play a useful role in your development vocabulary.

Image Take control of transforms. They’re just math. Transforms shouldn’t keep you from retrieving information about your views, whether determining the current rotation or scaling value, or the position of your view’s corners. Transforms provide incredible power in many iOS development arenas, and the recipes in this chapter add tweaks that ensure that the information and control you need are ready to use when you need them.

Image Blocks are wonderful. Use them to simplify your life, your code, and your animations.

Image Animate everything. Animations don’t have to be loud, splashy, or badly designed. The iOS SDK’s strong animation support enables you to add smooth transitions between user tasks. The iOS experience is characterized by subtle, short, smooth transitions.

Image Much of this chapter focuses on direct view hierarchies and placement, for when you take charge of view layout yourself. Read about declarative constraints and Auto Layout in Chapter 5 for a better, more powerful way to manage your view layouts.