10.1. Common Controls

Controls are diverse, multipurpose utility classes to augment a user interface. UI Kit supports many different types of controls, derived from a standard UIControl class. Controls provided in the SDK include switches, sliders, segmented controls, buttons, page controls, and text field controls. Controls are practical enhancements to classes derived from UIView, and can be attached directly to navigation bars, table cells, and even larger objects.

The controls implemented on the iPhone are noticeably different from those used in desktop applications. Desktop controls such as checkboxes and radio buttons won't cut it on a high-resolution device with limited touch screen precision (e.g., no stylus). For each desktop control in common use, a similar control has been designed specifically for the iPhone.

10.1.1. The UIControl Base Class

Controls are derived from a base class named UIControl, which provides a uniform interface for events notification, state changes, and display properties. The UIControl class is derived from the UIView class, so each control shares many properties of views, including the ability to attach to other views. Controls all share a common set of properties and methods.

10.1.1.1. Properties


enabled

Controls are enabled by default. To disable a control, set its enabled property to NO, which will cause the control to ignore any touch events. When disabled, the control might also render itself differently so that it appears grayed out. While the actual rendering is left to the control's subclass, the property for this exists in UIControl.



selected

When the user selects a control, the UIControl class sets its selected property to YES. Subclasses sometimes use this to cause a control to select itself or behave differently.



contentVerticalAlignment

Specifies how the control's content should be positioned vertically. The default behavior is to top-align the content, so you may want to change this to UIControlContentVerticalAlignmentCenter for text fields. Acceptable values for this field follow:

UIControlContentVerticalAlignmentCenter
UIControlContentVerticalAlignmentTop
UIControlContentVerticalAlignmentBottom
UIControlContentVerticalAlignmentFill


contentHorizontalAlignment

Specifies how the text field's content should be positioned horizontally. Acceptable values for this field follow:

UIControlContentHorizontalAlignmentCenter
UIControlContentHorizontalAlignmentLeft
UIControlContentHorizontalAlignmentRight
UIControlContentHorizontalAlignmentFill
10.1.1.2. Event notifications

The UIControl class provides a standard mechanism to register and receive events. This lets you instruct your control to notify a method in your delegate class whenever a certain event occurs. To register an event, use the addTarget method:

[ myControl addTarget: myDelegate action: @selector(myActionMethod:)
    forControlEvents: UIControlEventValueChanged
];

Events can be logically OR'd together, allowing you to specify multiple events with a single call to addTarget. The events to follow are supported by the base UIControl class, and therefore apply to all controls unless otherwise specified:



UIControlEventTouchDown

Notify all individual touch down events. These occur whenever the user taps down on the screen, or when additional fingers are placed down.



UIControlEventTouchDownRepeat

Notify all multi-touch tap down events whenever the tap count is greater than 1. These occur whenever a user taps down a second, third, or fourth finger.



UIControlEventTouchDragInside

Notify when a touch is dragged inside the control's window.



UIControlEventTouchDragOutside

Notify when a touch is dragged outside the control's window.



UIControlEventTouchDragEnter

Notify when a touch is dragged from the outside to the inside of a control's window.



UIControlEventTouchDragExit

Notify when a touch is dragged from the inside to the outside of a control's window.



UIControlEventTouchUpInside

Notify all touch up events occurring inside the control.



UIControlEventTouchUpOutside

Notify all touch up events originating outside the control. Notifications will not be sent unless the tap originated from inside the control.



UIControlEventTouchCancel

Notify all touch cancel events, where a touch was canceled by adding too many fingers or being interrupted by a lock or phone call.



UIControlEventValueChanged

Notify when the value of the control has changed. Used in sliders, segmented controls, and other value-based controls. You can configure slider controls to notify when the slider is dropped, or as it is being dragged.



UIControlEventEditingDidBegin

Notify when editing begins inside a text-based control.



UIControlEventEditingChanged

Notify when text is being changed inside a text-based control.



UIControlEventEditingDidEnd

Notify when editing ends inside a text-based control.



UIControlEventEditingDidEndOnExit

Notify when editing ends inside a text-based control by pressing the return key (or equivalent).



UIControlEventAllTouchEvents

Notify all touch events.



UIControlEventAllEditingEvents

Notify all text-based editing events.



UIControlEventAllEvents

Notify all events.

In addition to the default events, custom control classes may use the value range 0x0F000000 through 0x0FFFFFFF to define their own events.

To remove an action for one or more events, use the UIControl class's removeTarget method. Using the nil value will remove all actions for the given event target:

[ myControl removeTarget: myDelegate action: nil
    forControlEvents: UIControlEventAllEvents
];

To obtain a list about all actions specified for a control, use the allTargets method. This returns an NSSet containing the complete list of events:

NSSet *myActions = [ myControl allTargets ];

Alternatively, you can obtain a list of all actions for a given event target using the actionsForTarget method:

NSArray *myActions = [ myControl actionsForTarget: UIControlEventValueChanged ];

                                          

If you're designing a custom control class, you can end notifications for the base UIControl events or your own custom events using the sendActionsForControlEvents method. For example, if the value of your control is being changed, you can send the appropriate event notification, which will be propagated to the event targets specified by the code using your control. An example of this follows:

[ self sendActionsForControlEvents: UIControlEventValueChanged ];

When the delegate class is notified of the event, it will receive a pointer to the sender of the event. Your action method should follow the construction of the example below, which handles an event for a segmented control:

-(void) myAction: (id)sender
{
    UISegmentedControl *control = (UISegmentedControl *) sender;
    if (control == myControl1) {
        /* Query the control for a value */

        /* Respond to action for myControl1 */
    }
}

10.1.2. Segmented Controls

The segmented control replaces the radio button used on desktop operating systems with an interface similar to the front of a modern kitchen appliance, such as a dishwasher or microwave oven. The user sees a pushbutton bar; pressing one button causes all others to pop out. Segmented controls are useful where a limited number of related selections are available for one option.

10.1.2.1. Creating the control

As is the case with other view classes, you'll initialize a segmented control with a frame. The frame's coordinates are offset to the view hosting the control, which is usually the cell of a preferences table or a navigation bar:

UISegmentedControl *segmentedControl = [ [ UISegmentedControl alloc ]
     initWithItems: nil
];

segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;

You can choose one of three different styles for segmented controls, depending on where the control is being used.

Style Description
UISegmentedControlStylePlain Large white buttons with gray border, appropriate for preference cells
UISegmentedControlStyleBordered Large white buttons with black border, appropriate for table cells
UISegmentedControlStyleBar Small buttons ideal for a navigation bar

If you are using the UISegmentedControlStyleBar style, you can also set a tint for the entire control by using the control's tintColor property:

UIColor *myTint = [ [ UIColor alloc ] initWithRed: 0.75
    green: 1.0
    blue: 0.75
    alpha: 1.0
];

segmentedControl.tintColor = myTint;

10.1.2.2. Adding segments

Each segment within a segmented control is represented by a button containing a label or image. You'll need to create a segment for each selectable item in your control. You can have as many segments as will fit on the screen, but the user can select only one segment at a time. The PageDemo example from Chapter 3 illustrated buttons for bunnies and ponies:

[ segmentedControl insertSegmentWithTitle: @"Bunnies"
    atIndex: 0 animated: NO
];

[ segmentedControl insertSegmentWithTitle: @"Ponies"
    atIndex: 1 animated: NO
];

Each button is assigned an index number (0, 1, 2, and so on). This numeric value serves for ordering and as a button identifier for your application.

To add a segment containing an image, use the insertSegmentWithImage method:

UIImage *myBunnies = [ UIImage applicationImageNamed: @"bunnies.png" ];

[ segmentedControl insertSegmentWithImage: myBunnies
    atIndex: 0 animated: NO
];

You can also remove a segment. To remove an individual segment, use the removeSeg⁠ment method:

[ segmentedControl removeSegmentAtIndex: 1 animated: YES ];

To remove all segments at once, invoke removeAllSegments. This causes the control to visibly shed its buttons:

[ segmentedControl removeAllSegments ];

10.1.2.3. Segment titles

If, at any time, it's necessary to change the title of a button, use the setTitle method:

[ segmentedControl setTitle:@"Unicorms" forSegment: 0 ];

You can also read the titles using the titleForSegmentAtIndex method:

NSString *myTitle = [ segmentedControl titleForSegmentAtIndex: 0 ];

10.1.2.4. Images

In addition to text, segmented controls can display images inside their buttons. Any images used should be included in the application's program folder by dragging the image into the Xcode project's Resources folder. You can add an image to an existing segment using the setImage method. This is similar to the insertSegmentWithImage method you've already learned about, but operates on an existing segment:

[ segmentedControl setImage: [ UIImage applicationImageNamed:@"unicorns.png" ]
   forSegmentAtIndex: 0
];

                                          

You can also read a segment's image using the imageForSegmentAtIndex method:

UIImage *myImage = [ segmentedControl imageForSegmentAtIndex: 0 ];

The control itself will not perform any image scaling, so it will try to display your image on the button even if the image is too large. This requires care in designing button images to ensure they fit into the button space. You can manually set the segment's width using the control's setWidth method:

[ segmentedControl setWidth: 64.0 forSegmentAtIndex: 0 ];

10.1.2.5. Momentary clicks

The default behavior of the segmented control is to retain the selected button until another is selected. You can change this behavior to automatically release the button shortly after it is pressed. Set the control's momentary property to YES to enable this functionality:

segmentedControl.momentary = YES;

10.1.2.6. Initializing default segment

By default, no segment will be selected unless you specify one. To set the default segment, set the selectedSegmentIndex property:

segmentedControl.selectedSegmentIndex = 0;

10.1.2.7. Displaying the control

Once you have configured the control, display it by adding it as a subview to any type of object that can host it or by specifying it as the view to a navigation title or other object. Examples follow.

Adding to parent view:

[ parentView addSubview: segmentedControl ];

Adding to navigation bar (via view controller):

self.navigationItem.titleView = segmentedControl;

10.1.2.8. Reading the control

To read the current value of a segmented control, read the selectedSegmentIndex property. This provides a value corresponding to the segment number that is currently selected. The value is set based on the number assigned to it when it was first inserted into the control:

int x = segmentedControl.selectedSegmentIndex;

To receive a notification when a button is pressed, use the UIControl class's addTarget method to add an action for the UIControlEventValueChanged event. Your target class can then read the control's selected index:

[ segmentedControl addTarget: self action:
    @selector(controlPressed:)
    forControlEvents: UIControlEventValueChanged
];

Your action class will be called whenever a new segment is selected:

-(void) controlPressed: (id)sender
{
    UISegmentedControl *control = (UISegmentedControl *) sender;
    if (control == mySegmentedControl) {

        int x = control.selectedSegmentIndex;

      /* Additional code to respond to segment change */
    }
}

10.1.3. Switches

In the same way that the segmented control replaced the radio button, the switch replaced the checkbox. Switch controls are used to turn features on and off. The switch control is by far the simplest control to use, but can still be customized to a degree.

10.1.3.1. Creating the control

A switch is initialized using the standard initWithFrame method, but the size of the frame is ignored. Instead, the switch determines its own best size. This method allows you to define only the offset relative to the class it will be anchored to, such as a table cell or parent view. You can set the size as 0x0, acknowledging that it will be automatically set:

UISwitch *switch = [ [ UISwitch alloc ]
    initWithFrame: CGRectMake(170.0, 5.0, 0.0, 0.0)
];

10.1.3.2. Alternate colors

While not supported by the SDK, you can set a destructive switch to use a bright orange warning color when activated, rather than the standard blue. Features that could result in a performance impact or have other consequences can display these warning colors by using the hidden setAlternateColors method. This is particularly useful when distributing applications internally for debugging or review, identifying controls that will be removed when the application is released:

[ switch setAlternateColors: YES ];

This undocumented API is subject to change at any time. Your application could also potentially be rejected from listing in the iTunes store if you use undocumented APIs, so be sure to remove this feature before going into production.


10.1.3.3. Displaying the control

Once you have initialized the switch, you can display it by adding it as a subview to any type of object that can host it or by specifying it as the view to a navigation title or other object. Examples follow.

Adding to parent view:

[ parentView addSubview: switch ];

Adding to navigation bar (via view controller):

self.navigationItem.titleView = switch;

10.1.3.4. Switch position

The switch control can be read through its on property. This provides a Boolean value identifying whether the switch has been activated:

BOOL switchPosition = switch.on;

You can also activate the switch in your code using the setOn method. An example follows:

[ switch setOn: YES animated: YES ];

To receive a notification when the switch is toggled, use the UIControl class's addTarget method to add an action for the UIControlEventValueChanged event. Your action class then reads the switch's value:

[ switch addTarget: self action: @selector(switchStatusChanged:)
    forControlEvents:UIControlEventValueChanged
];

Your action class will be called whenever the switch is toggled:

-(void) switchStatusChanged: (id)sender
{
    UISwitch *control = (UISwitch *) sender;
    if (control == mySwitch) {

        BOOL on = control.on;

      /* Additional code to respond to switch state */
    }
}

10.1.4. Sliders

Sliders provide a visual range that the user can change by using a slide bar, and are configurable to accommodate a range of values. You can set ranges for the slider values, add images at the ends, and make various other aesthetic tweaks. The slider is ideal for presenting options with large (yet imprecise) ranges of numeric values, such as a volume setting, sensitivity controls, and the like. Commonly seen on the desktop, sliders must have been determined good enough for Apple to port them to the iPhone, too. The iPhone's version of a slider is more finger-friendly.

10.1.4.1. Creating the control

The slider control is a standard UIControl object and you can initialize it by invoking the control's initWithFrame method to specify its offset and width. The height of the frame is ignored, and can be set to 0x0:

UISlider *slider = [ [ UISlider alloc ] initWithFrame:
    CGRectMake(0.0, 0.0, 200.0, 0.0)
];

You should set the value range for the control on creation so you know what data to expect in return. If you provide no default range, values between 0.0 and 1.0 are used. The UISlider class provides two properties to set these—minimumValue and maximumValue:

slider minimumValue: 0.0;
slider maximumValue: 100.0;

You can also set a default value for the slider at this time by setting the slider's value property:

slider.value = 50.0;

The slider can display images at either end of the control. You can set these in a similar fashion to images in a segmented control. Copy the images into the Resources folder in Xcode. This will cause then to be copied into the application's program directory when installed on the iPhone. Adding images will cause a reduction in the control's slider bar, so be sure to increase the width of the control to accommodate the images:

[ slider setMinimumTrackImage:
    [ UIImage applicationImageNamed:@"min.png" ]
    forState: UIControlStateNormal
];

[ slider setMaximumTrackImage:
    [ UIImage applicationImageNamed:@"max.png" ]
    forState: UIControlStateNormal
];

You can display different images for each state of the slider. The following states are available:

UIControlStateNormal
UIControlStateHighlighted
UIControlStateDisabled
UIControlStateSelected

For debugging, an undocumented API exists to display the value of the slider within the control. Invoke the setShowValue method to display this next to the slider:

[ slider setShowValue: YES ];

This undocumented API is subject to change at any time. Your application could also potentially be rejected from listing in the iTunes store if you use undocumented APIs, so be sure to remove this feature before going into production.


10.1.4.2. Displaying the control

Once you have initialized the slider, display it by adding it as a subview to any type of object that can host it or by specifying it as the view to a navigation title or other object. Examples follow.

Adding to parent view:

[ parentView addSubview: slider ];

Adding to navigation bar (via view controller):

self.navigationItem.titleView = slider;

10.1.4.3. Reading the control

The slider control reads as a floating-point value within the range you specified at the control's creation. You can query the value using its value property:

float value = slider.value;

To receive a notification whenever the slider value is changed, use the UIControl class's addTarget method to add an action for the UIControlEventValueChanged event. Your action class then reads the slider's value:

[ slider addTarget: self action: @selector(sliderValueChanged:)
    forControlEvents:UIControlEventValueChanged
];

Your action class will be called whenever the slider is dropped in a new position:

-(void) sliderValueChanged: (id)sender
{
    UISlider *control = (UISlider *) sender;
    if (control == mySlider) {

        float value = control.value;

      /* Additional code to respond to slider value */
    }
}

To have this event trigger as the slider is being dragged, set the slider's continuous property:

slider.continuous = YES;

10.1.5. Text Field Controls

In Chapter 3, you were introduced to the UITextField class, which can be used to add text cells to tables and other objects. The UITextField class inherits from UIControl, and you can use so many of the properties of the UIControl class to further tailor the behavior of a UITextField object.

10.1.5.1. Style options

In addition to the text styling options discussed in Chapter 3, the UITextField control allows you to set alignment, border style, and a number of different aesthetic options. These include the properties to follow:



textAlignment

Specifies how the text within the control should be positioned. The default behavior is to left-align its content:

UITextAlignmentLeft
UITextAlignmentRight
UITextAlignmentCenter


borderStyle

Specifies the style of the border surrounding the text control. The default behavior is to use no border. You may use the following values. The style will be ignored if a custom background image is being used:

UITextBorderStyleNone
UITextBorderStyleLine
UITextBorderStyleBezel
UITextBorderStyleRoundedRect


placeholder

Draws a string as a gray placeholder for empty text fields. This value is displayed when a text field has not yet been edited and is without a value. Accepts an NSString object.



clearsOnBeginEditing

If the text field should be cleared when the user taps on it, set this Boolean value to YES. By default, the text field moves the cursor to the position tapped within the text field, and does not delete text.



adjustsFontSizeToFitWidth

When set to YES, this property causes the text to automatically shrink to fit the size of the text window. The default behavior is to retain the original font size, allowing long text to scroll out of view.



background

Accepts a UIImage object and sets it as the text field's background. This causes the border style property to be ignored.



clearButtonMode

Defines the behavior of the clear button. A clear button is a small "X" button appearing to the right of the text field, allowing the user to clear all text by tapping. The default behavior is set to UITextFieldViewNever, which hides the clear button. You can set the following modes:

UITextFieldViewModeNever
UITextFieldViewModeWhileEditing
UITextFieldViewModeUnlessEditing
UITextFieldViewModeAlways


LeftView, leftViewMode, rightView, rightViewMode

These properties allow you to attach derivatives of the UIView class to the right and left of the text field. UIButton objects such a magnifying glasses or bookmarks buttons are commonly attached to text fields. Each view has an accompanying mode, which you can set using the same values as the clearButtonmode property.

10.1.5.2. Rendering overrides

In addition to the styling options for a UITextField object, you can add many different overrides to custom UITextField objects, which affect how the text field is rendered.

These methods return a CGRect structure, specifying the boundaries for each component of a text field. If you are creating a custom UITextField class, you can override these methods to change one or more boundaries. Never call these methods directly; they are callbacks for the iPhone runtime to invoke. An example follows:

- (CGRect)clearButtonRectForBounds: (CGRect) bounds {
    return CGRectMake(bounds.origin.x + bounds.size.x - 50,
        bounds.origin.y + bounds.size.y - 20,
        16, 16);
}

The following overrides are available when creating a subclass of UITextField:



borderRectForBounds

Specifies the border rectangle.



textRectForBounds

Specifies the boundaries of the displayed text.



placeholderRectForBounds

Specifies the boundaries of the placeholder text.



editingRectForBounds

Specifies the boundaries of the text when edited.



clearButtonRectForBounds

Specifies the boundaries in which to render the clear button.



leftViewRectForBounds

Specifies the boundaries in which to render the left view.



rightViewRectForBounds

Specifies the boundaries in which to render the right view.

10.1.5.3. Delegate methods

You can assign a delegate to a UITextField using the class's delegate property. This delegate receives a number of different events that can be overridden to receive notification of certain events occurring in the text field.

Each method is provided a pointer to the text field in which the action occurred, allowing you to handle multiple text fields within one delegate. The following delegate methods are available:



-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField;

Returns a Boolean value specifying whether the text field should be permitted to begin editing. To disable editing for the text field entirely, this method should always return NO (although it's easier to just disable the control). This is useful when a custom text field requires other actions to be taken prior to making it read-write.



-(void)textFieldDidBeginEditing:(UITextField *)textField;

Invoked whenever the user taps inside of the text field to enable editing. The text field becomes the first responder when it begins editing.



-(BOOL)textFieldShouldEndEditing:(UITextField *)textField;

Returns a Boolean value specifying whether the text field should be permitted to end editing. When editing is ended, the text field resigns as first responder. To prevent a field from disappearing when the user finishes editing it, return NO. This can be useful in messaging applications, where the text field should always remain active.



-(void)textFieldDidEndEditing:(UITextField *)textField;

Invoked whenever editing has been completed within the given text field, and the text field has resigned as first responder.



-(BOOL)textField:(UITextField *)textField, shouldChangeCharactersInRange:(NSRange)range, replacementString:(NSString *)string;

Invoked when the user invokes autocorrect to change the given text to the suggested text. This is useful if you're adding an undo option to your application and want to keep track of the last change made within the field, or to record a log of all edits for auditing purposes. To prevent the text from being changed, return NO. The method's parameters provide an NSRange object identifying the position of the text to be changed. The suggested replacement text is also provided.



-(BOOL)textFieldShouldClear:(UITextField *)textField;

Returns a Boolean value specifying whether the text field should be allowed to clear at the user's request. This is useful if certain events must occur before the text field can clear, or if the text field should not be permitted to clear unless certain other conditions are met.



-(BOOL)textFieldShouldReturn:(UITextField *)textField;

Returns a Boolean value specifying whether pressing the return key should allow editing to end. If you want to end editing when the user presses the return key, invoke the resignFirstResponder method here. This will cause the text field to end editing and the keyboard will be dismissed:

- (BOOL) textFieldShouldReturn:(UITextField *)textField {

    [ textField resignFirstResponder ];
    return YES;
}

The application may take other actions when the user presses the return key. For example, you may choose to begin a search or verify the information entered into the text field.

10.1.5.4. Notifications

Since the UITextField class derives from the UIControl class, the notification system used in the UIControl class is also available to text fields. In addition to the UIControl class's standard events, you may also use the following custom events, specific to the UITextField class:



UITextFieldTextDidBeginEditingNotification

Triggered when a text field enters editing mode. The object property of the notification provides the text being edited.



UITextFieldTextDidChangeNotification

Triggered when text within the field has been changed. The object property of the notification stores the changed text.



UITextFieldTextDidEndEditingNotification

Triggered when a text field exits edit mode. The object property of the notification stores the final text.

Because a text field uses the keyboard for text entry, action notifications may also be sent when one or more of the following events occur:



UIKeyboardWillShowNotification

Sent before the keyboard is displayed.



UIKeyboardDidShowNotification

Sent after the keyboard is displayed.



UIKeyboardWillHideNotification

Sent before the keyboard is hidden.



UIKeyboardDidHideNotification

Sent after the keyboard is hidden.

10.1.5.5. Scrolling text fields

When editing a text field near the bottom of the screen, the action of the keyboard popping up can sometimes hide the field. As of iPhoneOS version 2.2, scrolling has been automated. To add the same level of functionality to earlier versions of firmware, the view containing the control can be scrolled up to bring the text field into view. This relies on two text field delegate methods, textFieldDidBeginEditing and textFieldShouldReturn.

For a given text field, set its delegate to the containing view class:

textControl.delegate = self;

When the user taps on the text field, the textFieldDidBeginEditing delegate method will be notified. Use this to change the view class's origin and size by a fixed amount. You can animate this to give the appearance of scrolling; Core Animation handles all of the frames in between (the "tweening"), so you just tell it where you want the view to end up, and Core Animation fills in the blanks for you.

The following code will scroll the view up by 240 pixels when the user taps the text field:

- (void)textFieldDidBeginEditing:(UITextView *)textview {

    [ UIView beginAnimations: nil context: NULL ];
    [ UIView setAnimationDuration: 0.3 ];

    CGRect frame = self.view.frame;
    frame.origin.y -= 240.0;
    frame.size.height += 240.0;
    self.view.frame = frame;

    [ UIView commitAnimations ];
}

When the user presses the return key, the frame can be scrolled back to the way it was. You can also dismiss the keyboard by resigning first responder. The same type of transaction is created to animate this change and give the appearance of scrolling:

- (BOOL)textFieldShouldReturn:(UITextView *) textView {

    [ UIView beginAnimations: nil context: NULL ];
    [ UIView setAnimationDuration: 0.3 ];

    CGRect frame = self.view.frame;
    frame.origin.y += 240.0;
    frame.size.height -= 240.0;
    self.view.frame = frame;

    [ UIView commitAnimations ];
    [ textView resignFirstResponder ];
    return YES;
}

If you are using multiple text fields in the same view, it may be necessary to compare the UITextView pointer passed into the method, so that you can make different scroll adjustments depending on the position of the text field and based on the position of the scroll bars. The example you've just read used a scrolling value of 240.0, however you will need different values here depending on where your particular text fields are located on the screen. See the section on Scroll Views for more information about scrolling.

10.1.6. Buttons

Buttons are simple controls capable of displaying text or an image, which notify an application when pressed. You can attach button controls to UIView objects, table cells, and a number of other objects. Because they derive from the UIControl class, they share the same notification structure as the base class. In addition to this, the UIButton class provides a number of additional features.

10.1.6.1. Creating the control

The button control is a standard UIControl object and you can initialize it by invoking the control's initWithFrame method to specify its offset and width. Additionally, the UIButton class provides a static buttonWithType method for creating a number of predefined styles on the fly:

UIButton *myButton = [ UIButton buttonWithType: UIButtonTypeRoundedRect ];

You can supply the following button styles as the type argument with the buttonWithType method.

Style Description
UIButtonTypeCustom Custom button; no styling is provided
UIButtonTypeRoundedRect White button with rounded edges, like a preferences table cell or address book card
UIButtonTypeDetailDisclosure Blue disclosure next to any text
UIButtonTypeInfoLight Info circle next to any text, for widgets
UIButtonTypeInfoDark Dark info circle for white backgrounds
UIButtonTypeContactAdd Blue plus button (+) next to any text

After creating a button, you can set its offset and size by assigning a CGRect structure to the button's frame property:

CGRect *myButtonFrame = CGRectMake(25.0, 25.0, 100.0, 100.0);
myButton.frame = myButtonFrame;

Button titles can be set for any given button state. Use the setTitle method to set this:

[ myButton setTitle: @"Click Here" forState: UIControlStateNormal ];

You can also set button images for a given button state. Use the setImage method to assign these:

[ myButton setImage:
    [ UIImage applicationImageNamed: @"button.png" ]
    forState: UIControlStateNormal
];

Additionally, you can set title color and shadow for each button state, as well as the background for the button. The setTitleColor and setTitleShadowColor methods both require a UIColor object as an argument. The setBackgroundImage method requires a UIImage object:

[ myButton setTitleColor:
    [ UIColor redColor ] forState: UIControlStateNormal
];

[ myButton setTitleShadowColor:
    [ UIColor grayColor ] forState: UIControlStateNormal
];

[ myButton setBackgroundImage:
    [ UIImage applicationImageNamed: @"background.png" ]
    forState: UIControlStateNormal
];

Each of the five methods you've just read about contains an argument named forState. This determines in which state the button's title, image, or other properties will appear. You can program the button to change its appearance when the state changes. These states are the same you've already learned about for the UIControl class:

UIControlStateNormal
UIControlStateHighlighted
UIControlStateDisabled
UIControlStateSelected

When a button is highlighted or disabled, the UIButton class can tweak its appearance. The following properties allow you to fine tune how you'd like the button to appear:



adjustsImageWhenHighlighted

By default, an image is drawn lighter when the button is pressed (highlighted). To disable this functionality, set this property to NO.



adjustImageWhenDisabled

By default, an image is drawn darker when the button is disabled. To disable this functionality, set this property to NO.



showsTouchWhenHighlighted

To cause the button to glow when pressed, set this property to YES. This is useful for info buttons or buttons that are somehow important.

10.1.6.2. Displaying the control

Once you have initialized the button, display it by adding it as a subview to any type of object that can host it. An example follows:

[ myView addSubview: button ];

10.1.6.3. Rendering overrides

In addition to the numerous styling options for a UIButton object, you can add different overrides to custom UIButton objects, which affect how a button is rendered.

These methods return a CGRect structure, specifying the boundaries for each component of a button. If you are creating a custom UIButton class, you can override these methods to change one or more boundaries. Never call these methods directly. An example follows:

- (CGRect) imageRectForContentRect: (CGRect) bounds {
    return myButtonImageBounds;
}

The following overrides are available when subclassing UIButton:



backgroundRectForBounds

Specifies the boundaries in which to render the background image.



contentRectForBounds

Specifies the boundaries in which to render the button's content.



titleRectForContentRect

Specifies the boundaries in which to render the button's title text.



imageRectForContentRect

Specifies the boundaries in which to render the button's image.

10.1.7. Page Controls

Page controls provide a visual indicator for applications that "flick" between pages using the thumb, rather than navigation buttons, or need to otherwise display a page indicator. You'll learn about page flicking in Chapter 13, which incorporates this control. A page control is rendered as a series of dots across the top or bottom of the screen, and is updated by your application as the user flips between pages. They are most commonly found at the bottom of the iPhone's home screen (springboard) when adding more icons than fit on a single screen, and in Safari, when the page selection window is opened. Page controls are ideal for custom view classes in which the developer seeks to display information across multiple pages.

10.1.7.1. Creating the control

To create a page control, use the standard initWithFrame method to specify the offset and size of the page control. Specifying a size of 0 will automatically set the horizontal size, vertical size, or both, based on the number of pages. Specifying a width of the screen's width will center the control horizontally within its display region:

UIPageControl *pageControl = [ [ UIPageControl alloc ]
    initWithFrame: CGRectMake(0.0, 400.0, 320.0, 0.0)
];

To set the number of pages the control will infer, set the numberOfPages property:

pageControl.numberOfPages = 5;

By default, the first page is selected. To select a different page, set the currentPage property. Pages are zero-indexed, so the first page is specified as page 0:

pageControl.currentPage = 0;

By default, the indicator will be displayed even if there is only one page. To hide the indicator when only one page is configured, set the hidesForSinglePage value to NO:

pageControl.hidesForSinglePage = NO;

Finally, if you'd like the indicator to avoid updating the current page until after you've had time to perform your own operations, set the defersCurrentPageDisplay to YES. This will require that you call the control's updateCurrentPageDisplay in order to update the current page:

pageControl.defersCurrentPageDisplay = YES;
[ pageControl updateCurrentPageDisplay ];

10.1.7.2. Displaying the control

Once you have initialized the page control, display it by adding it as a subview to any type of object that can host it. An example follows:

[ myView addSubview: pageControl ];

The page control is configured to use a transparent background, so if you want the control to respond to taps like it does on the Springboard, you'll need to ensure it has another view object behind it.

10.1.7.3. Notifications

When the user taps the page control, an event is created for UIControlEventValueChanged. You can specify an action using the UIControl class's addTarget method:

[ myView addTarget: self action:@selector(pageChanged:)
    forControlEvents: UIControlEventValueChanged
];

The action method is then notified, which can read the new page value and take the appropriate action:

- (void) pageChanged: (id) selector {
    UIPageControl *control = (UIPageControl *) selector;
    NSInteger page = control.currentPage;

    /* Additional code to handle page change */
}

10.1.8. Further Study

Now that you've had a full introduction to controls, try your hand at adding some to your code:

  • Create an application using various controls. Attach controls to UIView classes, navigation bars, and other objects. Which objects can you attach controls to?

  • Check out the following prototypes in your SDK's header files: UIControl.h, UISwitch.h, UISlider.h, UIButton.h, UITextField.h, and UISegmentedControl.h. You'll find these deep within /Developer/Platforms/iPhoneOS.platform, inside the UI Kit framework's Headers directory.