Building and Using Controls - The Core iOS Developer’s Cookbook, Fifth Edition (2014)

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

Chapter 2. Building and Using Controls

The UIControl class is the basis for many iOS interactive elements, such as buttons, text fields, sliders, and switches. These view objects have more in common than just deriving from their ancestor class. Controls all use similar layout paradigms and target-action triggers. Learning to create a single control, no matter how specialized, teaches you how all controls work. Controls may appear visually unique and specialized but use a single design pattern. This chapter introduces controls and their use. You’ll discover how to build and customize controls in a variety of ways. This chapter introduces a range of control recipes—from the prosaic to the obscure—you can reuse in your apps.

The UIControl Class

In iOS, controls refer to the members of a library of prebuilt objects designed for user interaction. Controls consist of buttons and text fields, sliders and switches, along with other Apple-supplied objects. A control’s role is to transform user interactions into callbacks. Users touch and manipulate controls and in doing so communicate with your application.

The UIControl class lies at the root of the control class tree. Controls are subclasses of UIView, from which they inherit all attributes for display and layout. The subclass adds a response mechanism that enhances views with interactivity.

All controls implement ways to dispatch messages when users interact with their interface. Controls send messages using a target-action pattern. When you define a new control, you tell it who receives messages (the target), what messages to send (the action), and when to send those messages (the triggering condition, such as a user completing a touch within its bounds).

Target-Action

The target-action design pattern offers a low-level way of responding to user interactions. You encounter this pattern almost exclusively with children of the UIControl class. With target-action, you tell the control to message a given object when a specific user event takes place. Forexample, you’d specify which object receives a selector when a user presses a button or adjusts a slider.

You supply an arbitrary selector. The selector is not checked at runtime, so use caution in preparing your code. The compiler will warn you if the selector specified is not declared, hopefully preventing a typo in the selector from going unnoticed and leading to a crash at runtime. The following snippet sets a target-action pair that calls the playSound: selector when a user releases a touch inside a button. If the target (self) does not implement that method, the application crashes at runtime with an undefined method call error:

[button addTarget:self action:@selector(playSound:)
forControlEvents:UIControlEventTouchUpInside];

Target-actions do not rely on an established method vocabulary the way delegates do. Unlike with delegates and their required protocols, there are no guarantees about a playSound: implementation. It’s up to the developer to make sure that the callback refers to an existing method. A cautious programmer will test a target before assigning a target-action pair with a given selector. Here’s an example:

if ([someObject respondsToSelector:@selector(playSound:)])
[button addTarget:someObject action:@selector(playSound:)
forControlEvents:UIControlEventTouchUpInside];

Standard UIControl target-action pairs always pass either zero, one, or two arguments. These optional arguments offer the interaction object (such as a button, slider, or switch that has been manipulated) and a UIEvent object that represents the user’s input. Your selector can choose to pass the interaction object or the interaction object and the event. In the preceding example, the selector uses one argument: the UIButton instance that was tapped. This self-reference, where the triggered object is included with the call, enables you to build more general action code that knows which control produced the callback.

Kinds of Controls

System-supplied members of the UIControl family include buttons, segmented controls, switches, sliders, page controls, and text fields. Each of these controls can be found in Interface Builder’s (IB’s) Object Library (Command-Control-Option-3, View > Utilities > Show Object Library), as shown in Figure 2-1.

Image

Figure 2-1 Interface Builder provides its available controls in the Object Library. From the top-left, these are labels (UILabel), buttons (UIButton), segmented controls (UISegmentedControl), text fields (UITextField), sliders (UISlider), switches (UISwitch), activity indicators and progress indicators (UIActivityIndicatorView and UIProgressView, although these are not technically controls), page controls (UIPageControl), and steppers (UIStepper).

Control Events

Controls respond primarily to three kinds of events: those based on touch, those based on value, and those based on edits. Table 2-1 lists the full range of event types available to controls.

Image

Image

Table 2-1 UIControl Event Types

For the most part, events break down along the following lines. Buttons use touch events; the single UIControlEventTouchUpInside event accounts for nearly all button interaction and is the default event created by IB connections. Value events (for example,UIControlEventValueChanged) correspond to user-initiated adjustments to segmented controls, switches, sliders, and page controls. Refresh controls for tables also trigger value events. When users switch, slide, or tap those objects, the control value changes. UITextField objects trigger editing events. Users cause these events by tapping into (or out from) the text field, or by changing the text field contents.

As with all iOS GUI elements, you can lay out controls in Xcode’s IB screen or instantiate them in code. This chapter discusses some IB approaches but focuses more intently on code-based solutions. IB layout, once mastered, remains pretty much the same regardless of the item involved. You place an object into the interface, customize it with inspectors and Auto Layout constraints, and connect it to other IB objects.

Buttons

UIButton instances provide simple buttons. Users can tap them to trigger a callback via target-action programming. You specify how the button looks, what art it uses, and what text it displays.

iOS offers two ways to build buttons. You can use a typed button, selected from several predesigned styles, or build a custom button from scratch. The current iOS SDK offers very limited precooked types. In iOS 7, the entire UI has been redesigned with a flat, minimalistic design. One of the most noteworthy results of this redesign is the deprecation of the timeworn rounded rectangle. The upshot is the addition of a very new style of button, one that is plain (but bolded) text—the UIButtonTypeSystem type. In addition, many of the remaining button types no longer have a distinguishing UI.

The typed buttons available are not general purpose. They were added to the SDK primarily for Apple’s convenience, not yours. As a rule, Apple does not add UI features that it does not primarily consume itself. Nonetheless, you can use them in your programs as needed if you follow Apple’s Human Interface Guidelines (HIG). Figure 2-2 shows the five buttons.

Image

Figure 2-2 iOS SDK offers five typed buttons with three visually distinct interfaces. You can access them in IB or add them to your applications directly with code. The first symbol here represents three buttons: Detail Disclosure, Info Light, and Info Dark. The other two buttons are the Contact Add button and the System button.

Image Detail Disclosure—This is a blue outlined circle encasing the letter i, as seen when you add a detail disclosure accessory to table cells. Detail disclosures are used in tables to lead to a screen that shows details about the currently selected cell. Prior to iOS 7, this button was represented by an encircled chevron.

Image Info Light and Info Dark—These two buttons are identical to the symbol provided by the detail disclosure, offering a blue outlined, circled i—as you see on a Macintosh’s Dashboard widget—and they are meant to provide access to an information or settings screen. These are used in many basic applications to flip the view from one side to the other.

Image Contact Add—This blue outlined circle with a corresponding blue + in its center can be seen in the Mail application for adding new recipients to a mail message.

Image System—This button provides a transparent background and a text label. The System button contains no bezel or background appearance but can accept a custom image or text title.

Strictly speaking, UIButtonTypeCustom is also a “precooked” button in that it adds a label. As it offers no further appearance support, most developers can treat it as a fully custom button.

To use a typed button in code, allocate it, set its frame or Auto Layout constraints, and add a target. Don’t worry about adding custom art or creating the overall look of the button. The SDK takes care of all that. For example, here’s how to build a simple System button:

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setFrame: CGRectMake(0.0f, 0.0f, 80.0f, 30.0f)];
[button setCenter: self.view.center];
[button setTitle:@"Beep" forState:UIControlStateNormal];
[button addTarget:self action:@selector(playSound)
forControlEvents:UIControlEventTouchUpInside];
[contentView addSubview:button];

To build one of the other standard button types, omit the title line. The System button is the only precooked button type that uses a title.

In iOS 7, all UIViews, including UIButtons, support a tintColor property. This property is special: Without any action, subviews will inherit the color from their parent view. In this way, setting tintColor on the application’s root view can change the tint appearance throughout the application. Set tintColor directly on a child view to override the inherited property.

For the typed buttons, tintColor allows the replacement of the default blue for a color of your choosing. Throughout the Apple-provided views and controls in iOS 7, tintColor is most often used to define a color that signifies interactivity or selection of the view.

Most buttons use the “touch up inside” trigger, where the user’s touch ends inside the button’s bounds. iOS UI standards allow a user to cancel a button press by moving his or her finger off a button before releasing the finger from the screen. The UIControlEventTouchUpInsideevent choice mirrors that standard.

When using a precooked button, you must conform to Apple’s mobile HIG on how these buttons can be used. Adding a detail disclosure, for example, to lead to an information page can get your application rejected from the App Store. This might seem a proper extrapolation of the button’s role, but if it does not meet the exact wording of how Apple expects the button to be used, it may not pass review. (Obviously, this depends on the reviewer, but you’ll be hard pressed to defend an application that violates the HIG.) To avoid potential issues, you might want to use System and custom buttons wherever possible.

Buttons in Interface Builder

Buttons appear by default in the IB library as System button objects (see Figure 2-1). To use them, drag them into your interface. You can then change them to another button type via the Attributes inspector (View > Utility > Show Attributes Inspector, Command-Option-4). A button-type pop-up appears at the top of the inspector. Use this pop-up menu to select the button type you want to use.

If your button uses text, you can enter that text in the Title field. The Image and Background pull-downs let you choose a primary and background image for the button. Each button provides four configuration settings. The four button states are Default (the button in its normal state), Highlighted (when a user is currently touching the button), Selected (an “on” version of the button, for buttons that support toggled states), and Disabled (when the button is unavailable for user interaction).

Changes in the Object Attributes > Button > State Config section apply to the currently selected configuration. You might, for example, use a different button text color for a button in its default state than for its disabled state.

To preview each state, locate the three check boxes in Object Attributes > Control > Content. The Highlighted, Selected, and Enabled options let you set the button state. After previewing, and before you compile, make sure you return the button to the actual state it needs to be in when you first run the application.

Connecting Buttons to Actions

When you Control-drag (right-drag) from a button to an IB object such as the File’s Owner view controller in the IB editor, IB presents a pop-up menu of actions to choose from. These actions are polled from the target object’s available IBActions. Connecting to an action creates a target-action pair for the button’s Touch Up Inside event. You can also Control-drag from the button to your code, and Xcode will add empty function definitions to your implementation file.

Alternatively, you can Control-click (right-click) the button in the document outline, scroll down to Touch Up Inside, and drag from the unfilled dot to the target you want to connect to (in this case, the File’s Owner object). The same pop-up menu appears, with its list of available actions.


Note

In IB, you also encounter buttons that look like button views and act like views but are not, in fact, views. Bar button items (UIBarButtonItem) store the properties of toolbar and navigation bar buttons but are not buttons themselves. The toolbars and navigation bars build buttons internally to represent these logical entities.


Recipe: Building Buttons

When using the UIButtonTypeCustom style, you supply all button art. The number of images depends on how you want the button to work. For a simple pushbutton, you might add a single background image and vary the label color to highlight when the button is pushed.

For a toggle-style button, you might use four images: for the “off” state in a normal presentation, the off state when highlighted (that is, pressed), and two more for the “on” state. You choose and design the interaction details, making sure to add local state (the Boolean isOn instance variable in Recipe 2-1) to extend a simple pushbutton to a toggle. If you supply a normal image to buttons and do not specify highlight or disabled images, iOS automatically generates these variants for you.

Recipe 2-1 builds a button that toggles on and off, demonstrating the basic detail that goes into building custom buttons. When tapped, the button switches its art from green (on) to red (off), or from red to green. This allows your (noncolorblind) users to instantly identify a current state. The displayed text reinforces the state setting. Figure 2-3 (left) shows the button created by this recipe.

Image

Figure 2-3 Use UIImage stretching to resize art for arbitrary button widths. Set the left cap width to specify where the stretching can take place.

The UIImage resizable image calls in this recipe play an important role in button creation. Resizable images enable you to create buttons of arbitrary width and turn circular art into lozenge-shaped buttons. You specify the caps (that is, the art that should not be stretched). In this case, the cap is 110 pixels wide on the left and right. If you were to change the button width from the 300 pixels used in this recipe to 220, the button would lose the middle stretch, as shown in Figure 2-3 (right).

Buttons can assign image and background image by state. Images set the actual content of the button. Background images provide resizable backdrops over which images and title text may appear. Recipe 2-1 uses background images, letting the button’s built-in title field float over the supplied art.


Note

You can round the corners of your views and buttons to different degrees by adjusting layer properties. Adding the Quartz Core framework to your project lets you access view layers, where you can set the layer’s cornerRadius property programmatically. Then set the view’sclipsToBounds property to YES to achieve that Apple look.


Recipe 2-1 Building a UIButton That Toggles On and Off


#define CAPWIDTH 110.0f
#define INSETS (UIEdgeInsets){0.0f, CAPWIDTH, 0.0f, CAPWIDTH}
#define BASEGREEN [[UIImage imageNamed:@"green-out.png"] \
resizableImageWithCapInsets:INSETS]
#define PUSHGREEN [[UIImage imageNamed:@"green-in.png"] \
resizableImageWithCapInsets:INSETS]
#define BASERED [[UIImage imageNamed:@"red-out.png"] \
resizableImageWithCapInsets:INSETS]
#define PUSHRED [[UIImage imageNamed:@"red-in.png"] \
resizableImageWithCapInsets:INSETS]

- (void)toggleButton:(UIButton *)aButton
{
self.isOn = !self.isOn;
if (self.isOn)
{
[self setBackgroundImage:BASEGREEN
forState:UIControlStateNormal];
[self setBackgroundImage:PUSHGREEN
forState:UIControlStateHighlighted];
[self setTitle:@"On" forState:UIControlStateNormal];
[self setTitle:@"On" forState:UIControlStateHighlighted];
}
else
{
[self setBackgroundImage:BASERED
forState:UIControlStateNormal];
[self setBackgroundImage:PUSHRED
forState:UIControlStateHighlighted];
[self setTitle:@"Off" forState:UIControlStateNormal];
[self setTitle:@"Off" forState:UIControlStateHighlighted];
}
}

+ (instancetype)button
{
PushButton *button =
[PushButton buttonWithType:UIButtonTypeCustom];

// Set up the button alignment properties
button.contentVerticalAlignment =
UIControlContentVerticalAlignmentCenter;
button.contentHorizontalAlignment =
UIControlContentHorizontalAlignmentCenter;

// Set the font and color
[button setTitleColor:
[UIColor whiteColor] forState:UIControlStateNormal];
[button setTitleColor:
[UIColor lightGrayColor] forState:UIControlStateHighlighted];
button.titleLabel.font = [UIFont boldSystemFontOfSize:24.0f];

// Set up the art
[button setBackgroundImage:BASEGREEN
forState:UIControlStateNormal];
[button setBackgroundImage:PUSHGREEN
forState:UIControlStateHighlighted];
[button setTitle:@"On" forState:UIControlStateNormal];
[button setTitle:@"On" forState:UIControlStateHighlighted];
button.isOn = YES;

// Add action. Client can add one too.
[button addTarget:button action:@selector(toggleButton:)
forControlEvents: UIControlEventTouchUpInside];

return button;
}


Multiline Button Text

The button’s titleLabel property allows you to modify title attributes such as its font and line break mode. Here, the font is set to a very large value (basically ensuring that the text needs to wrap to display correctly) and used with word wrap and centered alignment:

button.titleLabel.font = [UIFont boldSystemFontOfSize:36.0f];
[button setTitle:@"Lorem Ipsum Dolor Sit" forState:
UIControlStateNormal];
button.titleLabel.textAlignment = UITextAlignmentCenter;
button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;

By default, button labels stretch from one end of your button to the other. This means that text may extend farther out than you might otherwise want, possibly beyond the edges of your button art. To fix this problem, you can force carriage returns in word wrap mode by embedding new line literals (that is, \n) into the text. This allows you to control how much text appears on each line of the button title.

Adding Animated Elements to Buttons

When working with buttons, you can creatively layer art in front of or behind the buttons. Use the standard UIView hierarchy to do this, making sure to disable user interaction for any view that might otherwise obscure your button (setUserInteractionEnabled:NO). The image view contents “leak” through to the viewer, enabling you to add live animation elements to the button.

The sample art used in Recipe 2-1 is translucent, allowing you to experiment with this approach. The sample code for this recipe adds optional butterfly art that you can layer behind the button and animate.

Animated elements are particularly helpful when you’re trying to show state, such as an operation in progress. They can communicate to users why a button has become unresponsive or creates a different reaction to being pressed. For example, a turbo-enhanced button in a game might provide extra force when tapped. An animated visual helps users identify the change in functionality.

Separating art and text away from button implementation can play other roles in your development. Adding these elements behind or on top of an otherwise empty button allows you to localize both graphic design and phrasing based on your intended deployment without having to redesign the button directly.

Adding Extra State to Buttons

Recipe 2-1 creates a two-state button, providing visuals for on and off states. At times, you may want to implement buttons with further easy-to-distinguish states. Games provide the most common example of this. Many developers implement buttons that typically showcase four states: locked levels, unlocked-but-not-played, unlocked-and-partially-played, and unlocked-and-mastered.

Recipe 2-1 uses a simple Boolean toggle (the isOn instance variable) to store the on/off state and to select the art used (in the toggleButton: method) based on that state. You can easily expand this example for a wider range of art and button states by storing the state as an integer and providing a switch statement for art selection.


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 2.


Recipe: Animating Button Responses

There’s more to UIControl instances than frames and target-action. All controls inherit from the UIView class. This means you can use UIView animation blocks when working with controls just as you would with standard views. Recipe 2-2 builds a toggle switch that zooms itself whenever a user touches it and returns to its original size when the touch leaves the control.

This recipe creates a livelier interaction element that helps focus greater attention on the control in question.


Note

To add a little flare to your instances, note that buttons support delicious NSAttributedString values via setAttributedTitleForState:. Recipe 2-4, which follows later in this chapter, updates a segmented control’s text color using this method.


Recipe 2-2 Adding UIView Animation Blocks to Controls


- (void)zoomButton:(id)sender
{
// Slightly enlarge the button
[UIView animateWithDuration:0.2f animations:^{
button.transform =
CGAffineTransformMakeScale(1.1f, 1.1f);}];
}

- (void)relaxButton:(id)sender
{
// Return the button to its normal size
[UIView animateWithDuration:0.2f animations:^{
button.transform = CGAffineTransformIdentity;}];
}

- (void)toggleButton:(UIButton *)button
{
self.isOn = !self.isOn;
if (self.isOn)
{
[button setTitle:@"On" forState:UIControlStateNormal];
[button setTitle:@"On" forState:UIControlStateHighlighted];
[button setBackgroundImage:BASEGREEN
forState:UIControlStateNormal];
[button setBackgroundImage:PUSHGREEN
forState:UIControlStateHighlighted];
}
else
{
[button setTitle:@"Off" forState:UIControlStateNormal];
[button setTitle:@"Off"
forState:UIControlStateHighlighted];
[button setBackgroundImage:BASERED
forState:UIControlStateNormal];
[button setBackgroundImage:PUSHRED
forState:UIControlStateHighlighted];
}
[self relaxButton:button];
}

+ (instancetype)button
{
PushButton *button =
[PushButton buttonWithType:UIButtonTypeCustom];

// Add actions
[button addTarget:button action:@selector(toggleButton:)
forControlEvents: UIControlEventTouchUpInside];
[button addTarget:button action:@selector(zoomButton:)
forControlEvents: UIControlEventTouchDown |
UIControlEventTouchDragInside |
UIControlEventTouchDragEnter];
[button addTarget:button action:@selector(relaxButton:)
forControlEvents: UIControlEventTouchDragExit |
UIControlEventTouchCancel |
UIControlEventTouchDragOutside];

return button;
}



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 2.


Recipe: Adding a Slider with a Custom Thumb

UISlider instances provide a control that allows users to choose a value by sliding a knob (called its thumb) between its left and right extents. You’ve seen sliders in the Music application, where the UISlider class is used to control volume.

Slider values default to 0.0 for the minimum and 1.0 for the maximum, although you can easily customize these in the IB Attributes inspector or by setting the minimumValue and maximumValue properties. To stylize the ends of the control, add a related pair of images (minimumValueImage and maximumValueImage) that reinforce those settings. For example, you might show a snowman on one end and a steaming cup of tea on the other for a slider that controls temperature settings.

You can also set the color of the track before and after the thumb as well as the thumb itself—by adjusting the minimumTrackTintColor, maximumTrackTintColor, and thumbTintColor properties. In iOS 7, setting the minimumTrackTintColor to nil defaults the area before the thumb to the tint color of the parent view. The other properties revert to their default color when set to nil.

The slider’s continuous property controls whether a slider continually sends value updates as a user drags the thumb. When set to NO (the default is YES), the slider sends an action event only when the user releases the thumb.

Customizing UISlider

In addition to setting minimum and maximum images, the UISlider class lets you directly update its thumb component. You can set a thumb to whatever image you like by calling setThumbImage:forState:. Recipe 2-3 takes advantage of this option to dynamically build thumb images on-the-fly, as shown in Figure 2-4. The indicator bubble appears above the user’s finger as part of the custom-built thumb. This bubble provides instant feedback both textually (the number inside the bubble) and graphically (the shade of the bubble reflects the slider value, moving from black to white as the user drags).

Image

Figure 2-4 Core Graphics/Quartz calls enable this slider’s thumb image to dim or brighten based on the current slider value. The text inside the thumb bubble mirrors that value. (As the user slides, the stretched icon adjusts its size to provide additional feedback as a sample of a target-action client in the recipe sample code.)


Note

When compositing a UIView, iOS creates a bitmap for the view’s layer. The performance is approximately the same between using a custom view and using a custom-generated bitmap.


This kind of dynamically built feedback could be based on any kind of data. You might grab values from onboard sensors or make calls out to the Internet just as easily as you use the user’s finger movement with a slider. No matter what live update scheme you use, dynamic updates are certainly graphics intensive—but it’s not as expensive as you might fear. The Core Graphics calls are fast, and the memory requirements for the thumb-sized images are minimal.

This particular recipe assigns two thumb images to the slider. The bubble appears only when the slider is in use, for its UIControlStateHighlighted. In its normal state, namely UIControlStateNormal, only the smaller rectangular thumb appears. Users can tap on the thumb to review the current setting. The context-specific feedback bubble mimics the letter highlights on the standard iOS keyboard.

To accommodate these changes in art, the slider updates its frame at the start and end of each gesture. On being touched (UIControlEventTouchDown), the frame expands by 60 pixels in height. This extra space provides enough room to show the expanded thumb during interaction.

When the finger is removed from the screen (UIControlEventTouchUpInside or UIControlEventTouchUpOutside), the slider returns to its previous dimensions. This restores space to other objects, ensuring that the slider will not activate unless a user directly touches it.

Adding Efficiency

Recipe 2–3 stores a previous value for the slider to minimize the overall computational burden on iOS. It updates the thumb with a new custom image when the slider has changed by at least 0.1, or 10% in value. You can omit this check, if you want, and run the recipe with full live updating. This provided reasonably fast updates even on a first-generation iPod touch unit. On recent iPhones and iPads, it has no performance issues at all.

This recipe also avoids any issues at the ends of the slider—namely when the thumb gets caught at 0.9 and won’t update properly to 1.0. In this recipe, a hard-coded workaround for values above 0.98 handles that particular situation by forcing updates.

Recipe 2-3 Building Dynamic Slider Thumbs


/* Thumb.m */
// Create a thumb image using a grayscale/numeric level
UIImage *thumbWithLevel(float aLevel)
{
float INSET_AMT = 1.5f;
CGRect baseRect = CGRectMake(0.0f, 0.0f, 40.0f, 100.0f);
CGRect thumbRect = CGRectMake(0.0f, 40.0f, 40.0f, 20.0f);

UIGraphicsBeginImageContext(baseRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();

// Create a filled rect for the thumb
[[UIColor darkGrayColor] setFill];
CGContextAddRect(context,
CGRectInset(thumbRect, INSET_AMT, INSET_AMT));
CGContextFillPath(context);

// Outline the thumb
[[UIColor whiteColor] setStroke];
CGContextSetLineWidth(context, 2.0f);
CGContextAddRect(context,
CGRectInset(thumbRect, 2.0f * INSET_AMT, 2.0f * INSET_AMT));
CGContextStrokePath(context);

// Create a filled ellipse for the indicator
CGRect ellipseRect = CGRectMake(0.0f, 0.0f, 40.0f, 40.0f);
[[UIColor colorWithWhite:aLevel alpha:1.0f] setFill];
CGContextAddEllipseInRect(context, ellipseRect);
CGContextFillPath(context);

// Label with a number
NSString *numString =
[NSString stringWithFormat:@"%0.1f", aLevel];
UIColor *textColor = (aLevel > 0.5f) ?
[UIColor blackColor] : [UIColor whiteColor];
UIFont *font = [UIFont fontWithName:@"Georgia" size:20.0f];
NSMutableParagraphStyle *style =
[[NSMutableParagraphStyle alloc] init];
style.lineBreakMode = NSLineBreakByCharWrapping;
style.alignment = NSTextAlignmentCenter;
NSDictionary *attr = @{NSFontAttributeName:font,
NSParagraphStyleAttributeName:style,
NSForegroundColorAttributeName:textColor};
[numString drawInRect:CGRectInset(ellipseRect, 0.0f, 6.0f)
withAttributes:attr];

// Outline the indicator circle
[[UIColor grayColor] setStroke];
CGContextSetLineWidth(context, 3.0f);
CGContextAddEllipseInRect(context,
CGRectInset(ellipseRect, 2.0f, 2.0f));
CGContextStrokePath(context);

// Build and return the image
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}

// Return a base thumb image without the bubble
UIImage *simpleThumb()
{
float INSET_AMT = 1.5f;
CGRect baseRect = CGRectMake(0.0f, 0.0f, 40.0f, 100.0f);
CGRect thumbRect = CGRectMake(0.0f, 40.0f, 40.0f, 20.0f);

UIGraphicsBeginImageContext(baseRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();

// Create a filled rect for the thumb
[[UIColor darkGrayColor] setFill];
CGContextAddRect(context,
CGRectInset(thumbRect, INSET_AMT, INSET_AMT));
CGContextFillPath(context);

// Outline the thumb
[[UIColor whiteColor] setStroke];
CGContextSetLineWidth(context, 2.0f);
CGContextAddRect(context,
CGRectInset(thumbRect, 2.0f * INSET_AMT, 2.0f * INSET_AMT));
CGContextStrokePath(context);

// Retrieve the thumb
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}

/* CustomSlider.m */
// Update the thumb images as needed
- (void)updateThumb
{
// Only update the thumb when registering significant changes
if ((self.value < 0.98) &&
(ABS(self.value - previousValue) < 0.1f)) return;

// create a new custom thumb image for the highlighted state
UIImage *customImg = thumbWithLevel(self.value);
[self setThumbImage:customImg
forState:UIControlStateHighlighted];
previousValue = self.value;
}

// Expand the slider to accommodate the bigger thumb
- (void)startDrag:(UISlider *)aSlider
{
self.frame = CGRectInset(self.frame, 0.0f, -30.0f);
}

// At release, shrink the frame back to normal
- (void)endDrag:(UISlider *)aSlider
{
self.frame = CGRectInset(self.frame, 0.0f, 30.0f);
}

- (instancetype)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
// Initialize slider settings
previousValue = CGFLOAT_MIN;
self.value = 0.0f;

[self setThumbImage:simpleThumb()
forState:UIControlStateNormal];

// Create the callbacks for touch, move, and release
[self addTarget:self action:@selector(startDrag:)
forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:@selector(updateThumb)
forControlEvents:UIControlEventValueChanged];
[self addTarget:self action:@selector(endDrag:)
forControlEvents:UIControlEventTouchUpInside |
UIControlEventTouchUpOutside];
}
return self;
}

+ (instancetype)slider
{
CustomSlider *slider = [[CustomSlider alloc]
initWithFrame:(CGRect){.size=CGSizeMake(200.0f, 40.0f)}];

return slider;
}



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 2.


Recipe: Creating a Twice-Tappable Segmented Control

The UISegmentedControl class presents a multiple-button interface, where users can choose one choice out of a group. The control provides two styles of use (not to be confused with UISegmentedControlStyle, which alters the control UI and has been deprecated in iOS 7). In its normal radio-button-style mode, a button once selected remains selected. Users can tap on other buttons, but they cannot generate a new event by retapping their existing choice. The alternative style, provided by the momentary Boolean property, lets users tap on each button as many times as desired but stores no state about a currently selected item. It provides no highlights to indicate the most recent selection.

Recipe 2-4 builds a hybrid approach. It allows users to see their currently selected option and to reselect that choice if needed. This is not the way segmented controls normally work. There are times, though, when you want to generate a new result on reselection (as in momentary mode) while visually showing the most recent selection (as in radio button mode).

Unfortunately, “obvious” solutions to create this desired behavior don’t work. You cannot add target-action pairs that detect UIControlEventTouchUpInside. UIControlEventValueChanged is the only control event generated by UISegmentedControl instances. (You can easily test this yourself by adding a target-action pair for touch events.)

Here is where subclassing comes in to play. It’s relatively simple to create a new class based on UISegmentedControl that does respond to that second tap. Recipe 2-4 defines that class. Its code works by detecting when a touch has occurred, operating independently of the segmented control’s internal touch handlers that are subclassed from UIControl.

Segment switches remain unaffected; they’ll continue to update and switch back and forth as users tap them. Unlike with the parent class, touches on an already-touched segment continue to do something. In this case, they request that the object’s delegate produce theperformSegmentAction method.

Don’t add target-action pairs to your segmented controllers the way you would normally. Since all touch down events are detected, target-actions for value-changed events would add a second callback and trigger twice whenever you switched segments. Instead, implement the delegate callback and let object delegation handle the updates.

Second-Tap Feedback

With the ability to detect a second tap, the user can be provided feedback of a reselection, such as changing the title in the navigation bar. Another alternative is modifying the attributes of the text in the segmented control. UIKit’s text attribute feature (first introduced in iOS 5.x) offers an excellent match to this challenge. Segment controls provide optional attributes based on state. The setTitleTextAttributes:forState: method lets you introduce a visual flourish limited to the selected segment. Recipe 2-4 uses this method to change the selected text color from white to red after a second tap, and it resets that change after the user selects an alternate segment, as shown in Figure 2-5.

Image

Figure 2-5 By detecting the multiple taps, you can provide the user feedback of this normally unrepresented action on a segmented control. Using attributed strings, text can be decorated to further accent the selection.

Controls and Attributes

Beginning with iOS 6, many UIKit classes, including text fields, text views, labels, buttons, and refresh controls allow you to assign attributed (Core Text–style) strings to their text and title properties:

[myButton setAttributedTitle:attributedString forState:UIControlStateNormal]

In iOS 7, Apple expanded the vocabulary of text attributes such as font, color, and shadow that can be configured. A full listing of attributes that can be applied to text in an attributed string can be found in the NSAttributedString class reference, under Character Attributes.

For segmented controls and bar items, set attributes by calling setTitleTextAttributes:forState:. Pass an attribute dictionary using the available dictionary keys and values, such as those in the following abbreviated list:

Image NSFontAttributeName—Provides a UIFont instance.

Image NSForegroundColorAttributeName—Provides a UIColor instance.

Image NSShadowAttributeName—Provides an NSShadow instance that can specify shadow offset, blur radius, and shadow color.

Image NSUnderlineStyleAttributeName—Provides an NSNumber instance that wraps the number of required underlines.

For example, Recipe 2-4 sets a segmented control’s text color to white for its selected state. Whenever the control is selected twice in a row, the text color changes from white to red.

Recipe 2-4 Creating a Segmented Control Subclass That Responds to a Second Tap


@class SecondTapSegmentedControl;

@protocol SecondTapSegmentedControlDelegate <NSObject>
- (void) performSegmentAction: (SecondTapSegmentedControl *) aDTSC;
@end

@interface SecondTapSegmentedControl : UISegmentedControl
@property (nonatomic, weak)
id <SecondTapSegmentedControlDelegate> tapDelegate;
@end

@implementation SecondTapSegmentedControl
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];

if (self.tapDelegate)
[self.tapDelegate performSegmentAction:self];
}
@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 2.


Working with Switches and Steppers

The UISwitch object offers a simple on/off toggle that lets users choose a Boolean value. The switch object contains a single (settable) value property, called on. This property returns either YES or NO, depending on the current state of the control. You can programmatically update a switch’s value by changing the property value directly or calling setOn:animated:, which offers a way to animate the change:

- (void)didSwitch:(UISwitch *)theSwitch
{
self.title = [NSString stringWithFormat:@"%@"
theSwitch.on ? @"On" : @"Off"];
}

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Create the switch
UISwitch *theSwitch = [[UISwitch alloc] init];

// Trigger on value changes
[theSwitch addTarget:self action:@selector(didSwitch:)
forControlEvents:UIControlEventValueChanged];

[self.view addSubview:theSwitch];

// Initialize to "off"
theSwitch.on = NO;
self.title = @"Off";
}

In this example, when the switch updates, it changes the view controller’s title. IB offers relatively few options for working with a switch. You can enable it and set its initial value, but beyond that there’s not much to customize. A switch produces a value-changed event when a user adjusts it.


Note

Do not name UISwitch instances as switch. Recall that switch is a reserved C word; it is used for conditional statements. This simple oversight has tripped up many iOS developers.


The UIStepper class provides an alternative to sliders and switches. Whereas a slider offers a continuous range of values, a switch offers a simple Boolean on/off choice; steppers fall somewhat in the middle. Instances present a pair of buttons, one labeled – and the other labeled +. These iteratively increment or decrement the value property.

You generally want to assign a range to the control by setting its minimumValue and maximumValue to some reasonable bounds so the control ties in more tightly to actual application features such as volume, speed, and other measurable amounts. You do not have to do so, but there are few use cases in which you want to allow user input for unbounded variables. You can make the stepper “wrap” by setting its wraps property to YES. When the value exceeds the maximum or falls below the minimum, the value wraps around from min to max or max to min, depending on the button pressed.

By default, the stepper autorepeats. That is, it continues to change as long as the user holds one of its buttons. You can disable this by setting the autorepeat property to NO. The amount the value changes at each tap is controlled by the stepValue property. Don’t ever set stepValueto 0 or a negative number, or you’ll raise a runtime exception.

You can configure the interfaces of both switches and steppers with tint color properties and custom artwork. For switches, this includes the onTintColor, tintColor, and thumbTintColor properties for custom coloring and onImage and OffImage properties for custom artwork.UIStepper includes tintColor as well as divider, increment, and decrement images that you can configure by state.

Recipe: Subclassing UIControl

UIKit provides many prebuilt controls that you can use directly in your applications. There are buttons and switches and sliders and more. But why stop there? You don’t have to limit yourself to Apple-supplied items. Why not create your own?

Recipe 2-5 demonstrates how to subclass UIControl to build new controls from scratch. This example creates a simple color picker. It lets the user select a color by touching or dragging within the control. As the user traces left and right, the color changes hue. Up and down movements adjust the color’s saturation. The brightness and alpha levels for the color are fixed at 100%.

This is a really simple control to work with because there’s not much interaction involved beyond retrieving the x and y coordinates of the touch. It provides a basic example that demonstrates most of the development issues involved in subclassing UIControl.

So why build custom controls? First, you can set your own design style. Elements that you place into your interface can and should match your application’s aesthetics. If Apple’s prebuilt switches, sliders, and other GUI elements don’t provide a natural fit into your interface, custom-built controls satisfy your application’s needs without sacrificing cohesive design.

Second, you can create controls that Apple didn’t provide. From selecting ratings by swiping through a series of stars, or choosing a color from a set of pop-up crayons, custom controls allow your app to interact with the user beyond the system-supplied buttons and switches in the SDK. It’s easy to build unique eye-catching interactive elements by subclassing UIControl.

Finally, custom controls allow you to add features that you cannot access directly or through subclassing. With relatively little work, you can build your own buttons and steppers from the ground up, which means you can adjust their interaction vocabulary exactly as you wish.

Always keep your custom items visually distinct from system supplied ones. Don’t run afoul of HIG issues. When you do use lookalike items, you may want to add a note to Apple when submitting apps to the App Store. Make it clear that you have created a new class rather than using private APIs or otherwise accessing Apple’s objects in a manner that’s not App Store safe. Even then, you might be rejected for creating items that could potentially “confuse” the end user.

Creating Controls

The process of building a UIControl generally involves four distinct steps. As Recipe 2-5 demonstrates, you begin by subclassing UIControl to create a new custom class. In that class, you lay out the visual look of the control in your initialization. Next, you build methods to track and interpret touches, and you finish by generating events and visual feedback.

Nearly all controls offer value of some kind. For example, switches have isOn, sliders have a floating-point value, and text fields offer text. The kinds of values you provide with a custom control are arbitrary. They can be integers, floats, strings, or even (as in Recipe 2-5) colors.

In Recipe 2-5, the control layout is basically a colored rectangle. More complex controls require more complex layout, but even a simple layout like the one shown here can provide all the touch interaction space and feedback needed for a coherent end-user experience.

Tracking Touches

UIControl instances use an embedded method set to work with touches. These methods allow the control to track touches throughout their interaction with the control object:

Image beginTrackingWithTouch:withEvent:—Called when a touch enters a control’s bounds.

Image continueTrackingWithTouch:withEvent:—Follows the touch with repeated calls as the touch remains within the control bounds.

Image endTrackingWithTouch:withEvent:—Handles the last touch for the event.

Image cancelTrackingWithEvent:—Manages a touch cancellation.

Add your custom control logic by implementing any or all these methods in a UIControl subclass. Recipe 2-5 uses the begin and continue methods to locate the user touch and track it until the touch is lifted or otherwise leaves the control.

Dispatching Events

Controls use target-action pairs to communicate changes triggered by events. When you build a new control, you must decide what kind of events your object will generate and add code to trigger those events.

Add dispatching to your custom control by calling sendActionsForControlEvents:. This method lets you send an event (for example, UIControlEventValueChanged) to your control’s targets. Controls transmit these updates by messaging the UIApplication singleton. As Apple notes, the application acts as the centralized dispatch point for all messages.

No matter how simple your class, make sure your control vocabulary is as complete as possible. You cannot anticipate exactly how the class will be used in the future. Overdesigning your events provides flexibility for future use. Recipe 2-5 dispatches a wide range of events for what is, after all, a very simple control.

Where you dispatch events depends a lot on the control you end up building. Switch controls, for example, are really only interested in touch up events, which is when their value changes. Sliding controls, in contrast, center on touch movement and require continuing updates as the control tracks finger movement. Adjust your coding accordingly and be mindful of presenting appropriate visual changes during all parts of your touch cycle.

Recipe 2-5 Building a Custom Color Control


@implementation ColorControl
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor grayColor];
}
return self;
}

- (void)updateColorFromTouch:(UITouch *)touch
{
// Calculate hue and saturation
CGPoint touchPoint = [touch locationInView:self];
float hue = touchPoint.x / self.frame.size.width;
float saturation = touchPoint.y / self.frame.size.height;

// Update the color value and change background color
self.value = [UIColor colorWithHue:hue
saturation:saturation brightness:1.0f alpha:1.0f];
self.backgroundColor = self.value;
[self sendActionsForControlEvents:UIControlEventValueChanged];
}

// Continue tracking touch in control
- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if drag is currently inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.frame, touchPoint))
{
// Update color value
[self updateColorFromTouch:touch];

[self sendActionsForControlEvents:
UIControlEventTouchDragInside];
}
else
{
[self sendActionsForControlEvents:
UIControlEventTouchDragOutside];
}

return YES;
}

// Start tracking touch in control
- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Update color value
[self updateColorFromTouch:touch];

// Touch Down
[self sendActionsForControlEvents:UIControlEventTouchDown];

return YES;
}

// End tracking touch
- (void)endTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
{
// Update color value
[self updateColorFromTouch:touch];

[self sendActionsForControlEvents:
UIControlEventTouchUpInside];
}
else
{
[self sendActionsForControlEvents:
UIControlEventTouchUpOutside];
}
}

// Handle touch cancel
- (void)cancelTrackingWithEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}
@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 2.


Recipe: Building a Star Slider

Rating sliders allow users to grade items such as movies, software, and so forth by dragging their fingers across a set of images. It’s a common task for touch-based interfaces but one that’s not well served by a simple UISlider instance, with its floating-point values. Instead, a picker like the one built in Recipe 2-6 limits a user’s choice to a discrete set of elements, producing a bounded integer value between zero and the maximum number of items shown. As a user’s finger touches each star, the control’s value updates, and a corresponding event is spawned, allowing your application to treat the star slider like any other UIControl subclass.

The art is arbitrary. The example shown in Figure 2-6 uses stars, but there’s no reason to limit yourself to stars. Use any art you like, as long as you provide both “on” and “off” images. You might consider hearts, diamonds, smiles, and so on. You can easily update this recipe to provide a starting count of the stars before presentation.

Image

Figure 2-6 Recipe 2-6 creates a custom star slider control that animates each star upon selection. A simple animation block causes the star to zoom out and back as the control’s value updates.

In addition to simple sliding, Recipe 2-6 adds animation elements. Upon achieving a new value, a simple animation block is added to the rightmost star to zoom out and back, providing lively feedback to the user in addition to the highlighted visuals. Because the user’s finger lays on top of the stars in real use (rather than in the simulator-based screen shot shown in Figure 2-6), the animation uses exaggerated transforms to provide feedback that extends beyond expected finger sizes. Here, the art is quite small, and the zoom goes to 150% of the original size, but you can easily adapt both in your applications to match your needs.

Apart from the minimal layout and feedback elements, Recipe 2-6 follows the same kind of custom UIControl subclass approach used by Recipe 2-5, tracking touches through their life cycle and spawning events at opportune times. The minimal code needed to add the star elements and feedback in this recipe demonstrates how simple UIControl subclassing really is.

Recipe 2-6 Building a Discrete-Valued Star Slider


@implementation StarSlider
- (instancetype)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
// Lay out five stars, with spacing between and at the ends
float offsetCenter = WIDTH;
for (int i = 1; i <= 5; i++)
{
UIImageView *imageView = [[UIImageView alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, WIDTH, WIDTH)];
imageView.image = OFF_ART;
imageView.center = CGPointMake(offsetCenter,
self.intrinsicContentSize.height / 2.0f);
offsetCenter += WIDTH * 1.5f;
[self addSubview:imageView];
}
}

// Place on a contrasting background
self.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.25f];

return self;
}

- (CGSize)intrinsicContentSize
{
return CGSizeMake(WIDTH * 8.0f, 34.0f);
}

// Handle the value update for the touch point
- (void)updateValueAtPoint:(CGPoint)point
{
int newValue = 0;
UIImageView *changedView = nil;

// Iterate through stars to check against touch point
for (UIImageView *eachItem in [self subviews])
{
if (point.x < eachItem.frame.origin.x)
{
eachItem.image = OFF_ART;
}
else
{
changedView = eachItem; // last item touched
eachItem.image = ON_ART;
newValue++;
}
}

// Handle value change
if (self.value != newValue)
{
self.value = newValue;
[self sendActionsForControlEvents:
UIControlEventValueChanged];

// Animate the new value with a zoomed pulse
[UIView animateWithDuration:0.15f
animations:^{changedView.transform =
CGAffineTransformMakeScale(1.5f, 1.5f);}
completion:^(BOOL done){[UIView
animateWithDuration:0.1f
animations:^{changedView.transform =
CGAffineTransformIdentity;}];}];
}
}

- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Establish touch down event
CGPoint touchPoint = [touch locationInView:self];
[self sendActionsForControlEvents:UIControlEventTouchDown];

// Calculate value
[self updateValueAtPoint:touchPoint];
return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if drag is currently inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.frame, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchDragInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchDragOutside];

// Calculate value
[self updateValueAtPoint:[touch locationInView:self]];
return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchUpOutside];
}

- (void)cancelTrackingWithEvent:(UIEvent *)event
{
// Cancelled touch
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}
@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 2.


Recipe: Building a Touch Wheel

This next recipe creates a touch wheel, like the ones used on older model iPods. Touch wheels provide infinitely scrollable input. Users can rotate their finger clockwise or counterclockwise, and the object’s value increases or decreases accordingly. Each complete turn around the wheel (that is, a traversal of 360 degrees) corresponds to a value change of 1.0. Clockwise changes are positive; counterclockwise changes are negative. The value accumulates on each touch, although it can be reset; simply assign the control’s value property back to 0.0. This property is not a standard part of UIControl instances, even though many controls use values.

This recipe computes user changes by casting out vectors from the control’s center. The code adds differences in the angle as the finger moves, updating the current value accordingly. For example, three spins around the touch wheel add or subtract 3 to or from the current value, depending on the direction of movement.

This basic wheel defined in Recipe 2-7 tracks touch rotation but does little else. The original iPod scroll wheel offered five click points: in the center circle and at the four cardinal points of the wheel. Adding click support and the associated button-like event support (forUIControlEventTouchUpInside) are left as an exercise for you.

Recipe 2-7 Building a Touch Wheel Control


@implementation ScrollWheel

// Layout the wheel
- (instancetype)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
// This control uses a fixed 200x200 sized frame
CGRect f;
f.origin = aFrame.origin;
f.size = self.intrinsicContentSize;
self.frame = f;

// Add the touch wheel art
UIImageView *imageView = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"wheel.png"]];
[self addSubview:imageView];
}
return self;
}

- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
CGPoint point = [touch locationInView:self];

// Center point of view in own coordinate system
CGPoint centerPt = CGPointMake(self.bounds.size.width / 2.0f,
self.bounds.size.height / 2.0f);

// First touch must touch the gray part of the wheel
if (!pointInsideRadius(point, centerPt.x, centerPt)) return NO;
if (pointInsideRadius(point, 30.0f, centerPt)) return NO;

// Set the initial angle
self.theta = getAngle([touch locationInView:self], centerPt);

// Establish touch down
[self sendActionsForControlEvents:UIControlEventTouchDown];

return YES;
}

- (CGSize)intrinsicContentSize
{
return CGSizeMake(200, 200);
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{

CGPoint point = [touch locationInView:self];

// Center point of view in own coordinate system
CGPoint centerPt = CGPointMake(self.bounds.size.width / 2.0f,
self.bounds.size.height / 2.0f);

// Touch updates
if (CGRectContainsPoint(self.frame, point))
[self sendActionsForControlEvents:
UIControlEventTouchDragInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchDragOutside];

// Falls outside too far, with boundary of 50 pixels?
if (!pointInsideRadius(point, centerPt.x + 50.0f, centerPt))
return NO;

float newtheta = getAngle([touch locationInView:self], centerPt);
float dtheta = newtheta - self.theta;

// correct for edge conditions
int ntimes = 0;
while ((ABS(dtheta) > 300.0f) && (ntimes++ < 4))
{
if (dtheta > 0.0f)
dtheta -= 360.0f;
else
dtheta += 360.0f;
}

// Update current values
self.value -= dtheta / 360.0f;
self.theta = newtheta;

// Send value changed alert
[self sendActionsForControlEvents:UIControlEventValueChanged];

return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchUpOutside];
}

- (void)cancelTrackingWithEvent:(UIEvent *)event
{
// Cancel
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}
@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 2.


Recipe: Creating a Pull Control

Imagine a cord at the top of your screen. Pull it hard enough, and it rings a bell or otherwise triggers some sort of event, via a control target-action mechanism. For example, it could roll out a secondary view, start a download, or begin video playback. Recipe 2-8 builds a control that resembles a ribbon. The control updates clients when the interaction, which must start on top of the “ribbon,” pulls down far enough to trigger a request. The ribbon winds itself back up afterward, preparing for the next interaction.

Figure 2-7 shows the control built by this recipe, which is attached in this case to the bottom of a secondary view. Tugs bring the view into place and return it offscreen when finished.

Image

Figure 2-7 The ribbon control must be tugged a minimum distance before it triggers and winds back up. Each success sends out a value-changed message to its target-action clients.

Discoverability

Making the ribbon interaction discoverable presents a particular challenge in this recipe. Users might not immediately make the connection between a hanging red shape and a control they can manipulate.

Developer Matthijs Hollemans suggested a simple approach to address this challenge. Until the user interacts with the ribbon, it wiggles slightly a few times, separated by several seconds between each wiggle. The wiggle draws attention to the nature of the control, and the wiggles stop as soon as the user has correctly worked through the control style. A system preference can override this behavior for repeat application uses:

- (void)wiggle
{
if (wiggleCount++ > 3) return;

// Wiggle slightly
[UIView animateWithDuration:0.25f animations:^(){
pullImageView.center = CGPointMake(
pullImageView.center.x,
pullImageView.center.y + 10.0f);
} completion:^(BOOL finished){
[UIView animateWithDuration:0.25f animations:^(){
pullImageView.center = CGPointMake(
pullImageView.center.x,
pullImageView.center.y - 10.0f);
}];
}];

// Repeat until the count is overridden or it wiggles 3 times
[self performSelector:@selector(wiggle)
withObject:nil afterDelay:4.0f];
}

Adding accelerometer-based movement is another way to draw user attention to a nonobvious interaction control. Developer Charles Choi recommends allowing the ribbon to respond gently to device movements, offering an alternative mechanism for enhancing the control’s discoverability.

With the introduction of motion effects in iOS 7, the implementation for such an interaction has been greatly simplified with a new declarative API. Motion effects, as demonstrated in Listing 2-1, allow the association of accelerometer events from the device with values in your UIKit objects. Simply create an instance of a UIMotionEffect subclass (UIInterpolatingMotionEffect is the only system-supplied option currently), set the keyPath to be modified on the view, and associate the motion effect instance with the targeted view.

Listing 2-1 Adding Motion Effects


- (void)startMotionEffects
{
UIInterpolatingMotionEffect *motionEffectX =
[[UIInterpolatingMotionEffect alloc]
initWithKeyPath:@"center.x"
type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
UIInterpolatingMotionEffect *motionEffectY =
[[UIInterpolatingMotionEffect alloc]
initWithKeyPath:@"center.y"
type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
motionEffectX.minimumRelativeValue = @-15.0;
motionEffectX.maximumRelativeValue = @15.0;
motionEffectY.minimumRelativeValue = @-15.0;
motionEffectY.maximumRelativeValue = @15.0;
motionEffectsGroup = [[UIMotionEffectGroup alloc] init];
motionEffectsGroup.motionEffects =
@[motionEffectX, motionEffectY];
[pullImageView addMotionEffect:motionEffectsGroup];
}


Take your inspiration from Apple itself. Apple integrates discoverability hints throughout iOS, such as using slide to unlock text that suggests how and what to slide through simple animation.

Testing Touches

Recipe 2-8 limits interactions in two ways. First, the user must touch inside the ribbon art to begin interaction. If the user touches anywhere other than the ribbon, the touch falls through, and the control does not respond to it, even though touches may have begun on top of the control’s frame. Second, the recipe tests against the ribbon bitmap to ensure that touches began on a solid (nontransparent) part of the art. As you can see in Figure 2-7, a notch appears at the bottom of the artwork. Touches in this notch won’t start a tracking sequence. The recipe compares the touch position with pixels in the art. If the transparency (alpha level) of the art falls below 85 (about 67% transparent), the touch won’t connect with the ribbon.

The sample code provided for this recipe does not test for stretched art. It assumes a one-to-one relationship between the art and the onscreen presentation. Because of this, you will either have to drop the transparency test or adapt it for stretched art if you choose to resize this control in any way.

Once the tracking begins, the art follows the touch movement, dragging up or down with the user’s finger. If this touch travel exceeds 75 points in this recipe, the control triggers. It sends off a value-changed event to its clients. Strictly speaking, this control does not have a “value,” but touch up inside felt like a poor match to the way the control operates.

Upon reaching the trigger point, the continue tracking method returns NO, indicating that tracking has finished, and the control has finished its business for this particular interaction. If the touch travel fails to exceed the threshold or the user stops interacting without reaching that point, the control scrolls back its artwork to the beginning point. This resets the visual presentation, making it ready for the next interaction.

Recipe 2-8 Building a Draggable Ribbon Control


- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Establish touch down event
CGPoint touchPoint = [touch locationInView:self];
CGPoint ribbonPoint = [touch locationInView:pullImageView];

// Find the data offset in the image
Byte *bytes = (Byte *) ribbonData.bytes;
uint offset = alphaOffset(ribbonPoint.x, ribbonPoint.y,
pullImageView.bounds.size.width);

// Test for containment and alpha value to disallow touches
// outside the ribbon and inside the notched area

if (CGRectContainsPoint(pullImageView.frame, touchPoint) &&
(bytes[offset] > 85))
{
[self sendActionsForControlEvents:UIControlEventTouchDown];
touchDownPoint = touchPoint;
return YES;
}

return NO;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Once the user has interacted, don't wiggle any more
wiggleCount = CGFLOAT_MAX;

// Test for inside/outside touches
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.frame, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchDragInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchDragOutside];

// Adjust art based on the degree of drag
CGFloat dy = MAX(touchPoint.y - touchDownPoint.y, 0.0f);
dy = MIN(dy, self.bounds.size.height - 75.0f);
pullImageView.frame = CGRectMake(10.0f,
dy + 75.0f - ribbonImage.size.height,
ribbonImage.size.width, ribbonImage.size.height);

// Detect if travel has been sufficient to trigger everything
if (dy > 75.0f)
{
// It has. Play a click, trigger the callback, and roll
// the view back up.
[self playClick];
[UIView animateWithDuration:0.3f animations:^(){
pullImageView.frame = CGRectMake(10.0f,
75.0f - ribbonImage.size.height,
ribbonImage.size.width,
ribbonImage.size.height);
} completion:^(BOOL finished){
[self sendActionsForControlEvents:
UIControlEventValueChanged];
}];

// No more interaction needed or allowed
return NO;
}

// Continue interaction
return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchUpOutside];

// Roll back the ribbon, regardless of where the touch ended
[UIView animateWithDuration:0.3f animations:^(){
pullImageView.frame = CGRectMake(10.0f,
75.0f - ribbonImage.size.height,
ribbonImage.size.width, ribbonImage.size.height);
}];
}

// Handle cancelled tracking
- (void)cancelTrackingWithEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}



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 2.


Recipe: Building a Custom Lock Control

We created the lock control you see in Figure 2-8 for a conference after wrapping up the last edition of this Cookbook. At that time, numerous people asked for this recipe to be included in the next edition. It’s surprisingly easy to build from a UIControl point of view. It consists of four elements: a backdrop, the lock image (which switches to an unlocked version on success), the drag track, and the thumb.

Image

Figure 2-8 This simple lock control unlocks and removes itself after the user successfully swipes across at least three-quarters of the way.

Recipe 2-9 shows the code that creates this control’s behavior. In this recipe, interactions have a generous margin. Touches within 20 points of the track and its thumb are considered proper hits. This control is quite Spartan, and the extra space (roughly half the size of a standard fingertip) allows more confident access to the control.

Similarly, users only need to drag over about 75% of the way to complete the action. Again, this margin confirms that the user has intended a full unlock, but it doesn’t require frustrating precision. It took a bit of fiddling and user testing to get the “springiness” right; after releasing the thumb, it’s pulled back to the left if you haven’t finished a successful drag. We ended up using a half second, slightly longer than most interface changes usually take. To compare, a keyboard usually appears in one-third of a second.

Recipe 2-9 Creating a Lock Control


- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test touches for start conditions
CGPoint touchPoint = [touch locationInView:self];
CGRect largeTrack =
CGRectInset(trackView.frame, -20.0f, -20.0f);
if (!CGRectContainsPoint(largeTrack, touchPoint))
return NO;
touchPoint = [touch locationInView:trackView];
CGRect largeThumb =
CGRectInset(thumbView.frame, -20.0f, -20.0f);
if (!CGRectContainsPoint(largeThumb, touchPoint))
return NO;

// Begin tracking
[self sendActionsForControlEvents:UIControlEventTouchDown];
return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Strayed too far out?
CGPoint touchPoint = [touch locationInView:self];
CGRect largeTrack =
CGRectInset(trackView.frame, -20.0f, -20.0f);
if (!CGRectContainsPoint(largeTrack, touchPoint))
{
// Reset on failed attempt
[UIView animateWithDuration:0.2f animations:^(){
NSLayoutConstraint *constraint =
[trackView constraintNamed:THUMB_POSITION_TAG];
constraint.constant = 0;
[trackView layoutIfNeeded];
}];
return NO;
}

// Track the user movement by updating the thumb
touchPoint = [touch locationInView:trackView];
[UIView animateWithDuration:0.1f animations:^(){
NSLayoutConstraint *constraint =
[trackView constraintNamed:THUMB_POSITION_TAG];
constraint.constant = touchPoint.x;
[trackView layoutIfNeeded];
}];
return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if touch ended with unlock
CGPoint touchPoint = [touch locationInView:trackView];
if (touchPoint.x > trackView.frame.size.width * 0.75f)
{
// Complete by unlocking
_value = 0;
self.userInteractionEnabled = NO;
[self sendActionsForControlEvents:
UIControlEventValueChanged];

// Fade away and remove
[UIView animateWithDuration:0.5f animations:
^(){self.alpha = 0.0f;}
completion:
^(BOOL finished){[self removeFromSuperview];
}];
}
else
{
// Reset on failed attempt
[UIView animateWithDuration:0.2f animations:^(){
NSLayoutConstraint *constraint =
[trackView constraintNamed:
THUMB_POSITION_TAG];
constraint.constant = 0;
[trackView layoutIfNeeded];
}];
}

if (CGRectContainsPoint(trackView.bounds, touchPoint))
{
[self sendActionsForControlEvents:
UIControlEventTouchUpInside];
}
else
{
[self sendActionsForControlEvents:
UIControlEventTouchUpOutside];
}
}

- (void)cancelTrackingWithEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:
UIControlEventTouchCancel];
}



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 2.


Adding a Page Indicator Control

The UIPageControl class provides a line of dots that indicates which item of a multipage view is currently displayed. The dots at the bottom of the SpringBoard home screen present an example of this kind of control in action. Sadly, the UIPageControl class is a disappointment in action. Its instances are awkward to handle, hard to tap, and will generally annoy your users. So when using it, make sure you add alternative navigation options so that the page control acts more as an indicator and less as a control.

Figure 2-9 shows a page control with three pages. Taps to the left or right of the bright-colored current page indicator trigger UIControlEventValueChanged events, launching whatever method you set as the control’s action. You can query the control for its new value by callingcurrentPage and set the available page count by adjusting the numberOfPages property. SpringBoard limits the number of dots representing pages to nine, but your application can use a higher number, particularly in landscape mode.

Image

Figure 2-9 The UIPageControl class offers an interactive indicator for multipage presentations. Taps to the left or right of the active dot enable users to select new pages—at least in theory. The page control is hard to tap, requires excessive user precision, and offers poor response performance.

Recipe: Image Gallery Viewer

Recipe 2-10 uses a UIScrollView instance to display multiple images, as shown in Figure 2-10. Users can scroll through the pictures using swipes, and the page indicator updates accordingly. Similarly, users can tap on the page control, and the scroller animates the selected page into place. This two-way relationship is built by adding a target-action callback to the page control and a delegate callback to the scroller. Each callback updates the other object, providing a tight coupling between the two.

Image

Figure 2-10 In iOS 7, content is king. The only interface elements that are visible include the page control and the iOS status bar, both of which overlay the content.

One common mistake with UIPageControl is not sizing the control wide enough to create user navigation tap areas to the right and left of the dots. The intrinsic size of a page control matches closely to the visible size, which severely limits these tap areas. Centering the control without expanding the width creates a page control that is very difficult to use in navigation. Unless the interaction would conflict with other touch elements, expand the page control to the width of its superview through Auto Layout constraints or by setting the frame appropriately.

The design ethos of iOS 7 revolves around content as the primary focus, with limited UI chrome. The images in the gallery fully utilize all of the screen real estate, including under the status bar. Fittingly, the iOS status bar no longer allows translucent or opaque styles, only transparent, providing two styles that toggle the status bar for light and dark content. Change the status bar style by returning a UIStatusBarStyle from the preferredStatusBarStyle method on your view controller.

The status bar style and the page indicator tint colors could be modified to better suit the underlying picture, based on the average color of the image or a similar metric. This advanced treatment is left as an exercise for you.


Note

The implementation of UIScrollView presents unique challenges to arranging UI elements with Auto Layout. Apple provides Technical Note TN2154 (http://developer.apple.com/library/ios/#technotes/tn2154/_index.html), which describes two approaches. Recipe 2-10 uses the mixed approach. Auto Layout is addressed in more detail in Chapter 5, “View Constraints.”


Recipe 2-10 An Image Gallery Viewer


@implementation TestBedViewController
{
PagedImageScrollView *scrollView;
UIPageControl *pageControl;
}

- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}

- (void)loadView
{
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor blackColor];
self.navigationController.navigationBarHidden = YES;

scrollView = [[PagedImageScrollView alloc] init];
scrollView.delegate = self;
[self.view addSubview:scrollView];
PREPCONSTRAINTS(scrollView);
ALIGN_VIEW_LEFT(self.view, scrollView);
ALIGN_VIEW_RIGHT(self.view, scrollView);
ALIGN_VIEW_TOP(self.view, scrollView);
ALIGN_VIEW_BOTTOM(self.view, scrollView);
scrollView.images = @[[UIImage imageNamed:@"bird"],
[UIImage imageNamed:@"ladybug"],
[UIImage imageNamed:@"flowers"],
[UIImage imageNamed:@"sheep"]];

pageControl = [[UIPageControl alloc] init];
pageControl.numberOfPages = scrollView.images.count;
pageControl.currentPage = 0;
pageControl.pageIndicatorTintColor = [UIColor grayColor];
pageControl.currentPageIndicatorTintColor =
[UIColor redColor];
[pageControl addTarget:self
action:@selector(handlePageControlChange:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:pageControl];
PREPCONSTRAINTS(pageControl);
ALIGN_VIEW_LEFT(self.view, pageControl);
ALIGN_VIEW_RIGHT(self.view, pageControl);
ALIGN_VIEW_BOTTOM_CONSTANT(self.view, pageControl, -20);
}

// Update the scrollView after page control interaction
- (void)handlePageControlChange:(UIPageControl *)sender
{
CGFloat offset =
scrollView.frame.size.width * pageControl.currentPage;
[scrollView setContentOffset:CGPointMake(offset, 0)
animated:YES];
}

// Update the page control after scrolling
- (void)scrollViewDidEndDecelerating:(id)sender
{
CGFloat distance = scrollView.contentOffset.x /
scrollView.contentSize.width;
NSInteger page = distance * pageControl.numberOfPages;
pageControl.currentPage = page;
}
@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 2.


Building Toolbars

It’s easy to define and lay out toolbars in code if you’ve supplied yourself with a few handy macro definitions. The following macros return proper bar button items for the four available styles of items, and you can easily adapt them if you need more control options in your code. These macros are intended for automatic reference counting (ARC) use. If you use manual retain-release (MRR) development, make sure to adapt them with appropriate autorelease calls:

#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] \
initWithTitle:TITLE style:UIBarButtonItemStylePlain\
target:self action:SELECTOR]
#define IMGBARBUTTON(IMAGE, SELECTOR) [[UIBarButtonItem alloc] \
initWithImage:IMAGE style:UIBarButtonItemStylePlain \
target:self action:SELECTOR]
#define SYSBARBUTTON(ITEM, SELECTOR) [[UIBarButtonItem alloc] \
initWithBarButtonSystemItem:ITEM \
target:self action:SELECTOR]
#define CUSTOMBARBUTTON(VIEW) [[UIBarButtonItem alloc] \
initWithCustomView:VIEW]

These styles are text items, image items, system items, and custom view items. Each of these macros provides a UIBarButtonItem that can be placed into a UIToolbar. Listing 2-2 demonstrates these macros in action, showing how to add each style, including spacers. You can even add a custom view to your toolbars, as Listing 2-2 does. It inserts a UISwitch instance as one of the bar button items, as shown in Figure 2-11.

Image

Figure 2-11 Custom toolbar items can include views such as this switch.

The fixed-space bar button item represents the only instance where you need to move beyond these handy macros. You must set the item’s width property to define how much space the item occupies. Here are a few final pointers:

Image Fixed spaces can have widths. Of all UIBarButtonItems, only UIBarButtonSystemItemFixedSpace items can be assigned a width. So create the spacer item, set its width, and only then add it to your items array.

Image Use a single flexible space for left or right alignment. Adding a single UIBarButtonSystemItemFlexibleSpace at the start of an item’s list right-aligns all the remaining items. Adding one at the end left-aligns. Use two—one at the start and one at the end—to create center alignments.

Image Take missing items into account. When hiding a bar button item due to context, when you’re not using layout constraints, don’t just use flexible spacing to get rid of the item. Instead, replace the item with a fixed-width space that matches the item’s original size. Doing so preserves the layout and leaves all the other icons in the same position both before and after the item disappears.

Image Navigation bars support multiple items. Navigation bars and their navigation items allow you to add arrays of bar button items. You can create a toolbar effect by adding item arrays rather than adding a toolbar directly (for example,self.navigationItem.rightBarButtonItems = anArray). All the toolbar hints listed here, including flexible spacers, apply to navigation item layout as well.

Listing 2-2 Creating Toolbars in Code


@implementation TestBedViewController
- (void)action
{
// no action actually happens
}

- (void)loadView
{
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor whiteColor];
}

- (void)viewDidLoad
{
[super viewDidLoad];
UIToolbar *tb = [UIToolbar alloc] init];
[self.view addSubview:tb];
PREPCONSTRAINTS(tb);
ALIGN_VIEW_BOTTOM(self.view, tb);
ALIGN_VIEW_LEFT(self.view, tb);
ALIGN_VIEW_RIGHT(self.view, tb);
NSMutableArray *tbItems = [NSMutableArray array];

// Set up the items for the toolbar
[tbItems addObject:
BARBUTTON(@"Title", @selector(action))];

[tbItems addObject:
SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace,
nil)];

[tbItems addObject:
SYSBARBUTTON(UIBarButtonSystemItemAdd,
@selector(action))];

[tbItems addObject:
SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace,
nil)];

[tbItems addObject:
IMGBARBUTTON([UIImage imageNamed:@"star.png"],
@selector(action))];

[tbItems addObject:
SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace,
nil)];

[tbItems addObject:
CUSTOMBARBUTTON([[UISwitch alloc] init])];

[tbItems addObject:
SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace,
nil)];

[tbItems addObject:
IMGBARBUTTON([UIImage imageNamed:@"moon.png"],
@selector(action))];

tb.items = tbItems;
}
@end


Summary

This chapter introduces many ways to interact with and get the most from the controls in your applications. Before you move on to the next chapter, here are a few thoughts for you to ponder:

Image Just because an item belongs to the UIControl class doesn’t mean you can’t treat it like a UIView. Give it subviews, resize it, animate it, move it around the screen, or tag it for later.

Image Core Graphics and Quartz 2D let you build visual elements as needed. Combine the comfort of the SDK classes with a little real-time wow to add punch to your presentation.

Image Use attributed strings and UIKit attribute dictionaries to customize your control’s text features. Pick fonts, line and paragraph styling, colors, shadows, and more, as demanded by your design.

Image If the iOS SDK hasn’t delivered the control you need, consider adapting an existing control or building a new control from scratch.

Image Apple provides top-notch examples of UI excellence. Consider mimicking Apple’s examples when creating new interaction styles, such as adding confirm buttons to safeguard a delete action.

Image IB doesn’t always provide the best solution for creating interfaces. With toolbars, you may save time by using Xcode rather than customizing each element by hand in IB.