Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (12.71 MB, 929 trang )
Figure 12-1. A large activity indicator
You can assign an activity indicator a color; this overrides the color assigned through
the style. An activity indicator is a UIView, so you can set its backgroundColor; a nice
effect is to give an activity indicator a contrasting background color and to round its
corners by way of the view’s layer (Figure 12-1).
Here’s some code from a UITableViewCell subclass in one of my apps. In this app, it
takes some time, after the user taps a cell to select it, for me to construct the next view
and navigate to it; to cover the delay, I show a spinning activity indicator in the center
of the cell while it’s selected:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (selected) {
UIActivityIndicatorView* v =
[[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:
UIActivityIndicatorViewStyleWhiteLarge];
v.color = [UIColor yellowColor];
dispatch_async(dispatch_get_main_queue(), ^{
// cell tries to change background color match selection color
v.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.6];
});
v.layer.cornerRadius = 10;
CGRect f = v.frame;
f = CGRectInset(f, -10, -10);
v.frame = f;
CGRect cf = self.frame;
cf = [self.contentView convertRect:cf fromView:self];
v.center = CGPointMake(CGRectGetMidX(cf), CGRectGetMidY(cf));
v.tag = 1001;
[self.contentView addSubview:v];
[v startAnimating];
} else {
UIView* v = [self viewWithTag:1001];
if (v) {
[v removeFromSuperview];
}
}
}
If activity involves the network, you might want to set UIApplication’s networkActivityIndicatorVisible to YES. This displays a small spinning activity indicator in the status
564
| Chapter 12: Controls and Other Views
www.it-ebooks.info
Figure 12-2. A progress view
bar. The indicator is not reflecting actual network activity; if it’s visible, it’s spinning. Be
sure to set it back to NO when the activity is over.
An activity indicator is simple and standard, but you can’t change the way it’s drawn.
One obvious alternative would be a UIImageView with an animated image, as described
in Chapter 4.
UIProgressView
A progress view (UIProgressView) is a “thermometer,” graphically displaying a per‐
centage. It is often used to represent a time-consuming process whose percentage of
completion is known (if the percentage of completion is unknown, you’re more likely
to use an activity indicator). But it’s good for static percentages too. In one of my apps,
I use a progress view to show the current position within the song being played by the
built-in music player; in another app, which is a card game, I use a progress view to
show how many cards are left in the deck.
A progress view comes in a style, its progressViewStyle; if the progress view is created
in code, you’ll set its style with initWithProgressViewStyle:. Your choices are:
• UIProgressViewStyleDefault
• UIProgressViewStyleBar
UIProgressViewStyleBar is intended for use in a UIBarButtonItem, as the title view of
a navigation item, and so on. In iOS 7, both styles by default draw the thermometer
extremely thin — just 2 pixels and 3 pixels, respectively. (Figure 12-2 shows a
UIProgressViewStyleDefault progress view.) Changing a progress view’s frame height
directly has no visible effect on how the thermometer is drawn. Under autolayout, to
make a thicker thermometer, supply a height constraint with a larger value (thus over‐
riding the intrinsic content height).
The fullness of the thermometer is the progress view’s progress property. This is a value
between 0 and 1, inclusive; you’ll usually need to do some elementary arithmetic in
order to convert from the actual value you’re reflecting to a value within that range. For
example, to reflect the number of cards remaining in a deck of 52 cards:
prog.progress = [[deck cards] count] / 52.0;
A change in progress value can be animated by calling setProgress:animated:.
UIProgressView
www.it-ebooks.info
|
565
Figure 12-3. A thicker progress view using a custom progress image
In iOS 7, the default color of the filled portion of a progress view is the tintColor (which
may be inherited from higher up the view hierarchy). The default color for the unfilled
portion is gray for a UIProgressViewStyleDefault progress view and transparent for
a UIProgressViewStyleBar progress view. You can customize the colors; set the pro‐
gress view’s progressTintColor and trackTintColor, respectively. This can also be
done in the nib.
Alternatively, you can customize the image used to draw the filled portion of the progress
view, the progress view’s progressImage. If you do that, you can optionally customize
also the image used to draw the unfilled portion, the trackImage. This can also be done
in the nib. Each image must be stretched to the length of the filled or unfilled area, so
you’ll want to use a resizable image.
Here’s a simple example from one of my apps (Figure 12-3):
self.prog.backgroundColor = [UIColor blackColor];
self.prog.trackTintColor = [UIColor blackColor];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(10,10), YES, 0);
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(con, [UIColor yellowColor].CGColor);
CGContextFillRect(con, CGRectMake(0, 0, 10, 10));
CGRect r = CGRectInset(CGContextGetClipBoundingBox(con),1,1);
CGContextSetLineWidth(con, 2);
CGContextSetStrokeColorWithColor(con, [UIColor blackColor].CGColor);
CGContextStrokeRect(con, r);
CGContextStrokeEllipseInRect(con, r);
self.prog.progressImage =
[UIGraphicsGetImageFromCurrentImageContext()
resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
UIGraphicsEndImageContext();
For maximum flexibility, you can design your own UIView subclass that draws some‐
thing similar to a thermometer. Figure 12-4 shows a simple custom thermometer view;
it has a value property, and you set this to something between 0 and 1 and call setNeedsDisplay to make the view redraw itself. Here’s its drawRect: code:
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
[[UIColor whiteColor] set];
CGFloat ins = 2.0;
CGRect r = CGRectInset(self.bounds, ins, ins);
CGFloat radius = r.size.height / 2.0;
CGMutablePathRef path = CGPathCreateMutable();
566
|
Chapter 12: Controls and Other Views
www.it-ebooks.info
Figure 12-4. A custom progress view
CGPathMoveToPoint(path, nil, CGRectGetMaxX(r)-radius, ins);
CGPathAddArc(path, nil,
radius+ins, radius+ins, radius, -M_PI/2.0, M_PI/2.0, true);
CGPathAddArc(path, nil,
CGRectGetMaxX(r)-radius, radius+ins, radius,
M_PI/2.0, -M_PI/2.0, true);
CGPathCloseSubpath(path);
CGContextAddPath(c, path);
CGContextSetLineWidth(c, 2);
CGContextStrokePath(c);
CGContextAddPath(c, path);
CGContextClip(c);
CGContextFillRect(c, CGRectMake(
r.origin.x, r.origin.y, r.size.width * self.value, r.size.height));
}
UIPickerView
A picker view (UIPickerView) displays selectable choices using a rotating drum meta‐
phor. It has a standard legal range of possible heights, which is undocumented but seems
to be between 162 and 180; its width is largely up to you. Each drum, or column, is called
a component.
Your code configures the UIPickerView’s content through its data source (UIPicker‐
ViewDataSource) and delegate (UIPickerViewDelegate), which are usually the same
object. Your data source and delegate must answer questions similar to those posed by
a UITableView (Chapter 8):
numberOfComponentsInPickerView: (data source)
How many components (drums) does this picker view have?
pickerView:numberOfRowsInComponent: (data source)
How many rows does this component have? The first component is numbered 0.
pickerView:titleForRow:forComponent:
pickerView:attributedTitleForRow:forComponent:
pickerView:viewForRow:forComponent:reusingView: (delegate)
What should this row of this component display? The first row is numbered 0. You
can supply a simple string, an attributed string (Chapter 10), or an entire view such
as a UILabel; but you should supply every row of every component the same way.
The reusingView parameter, if not nil, is a view that you supplied for a row now
UIPickerView
www.it-ebooks.info
|
567
Figure 12-5. A picker view
no longer visible, giving you a chance to reuse it, much as cells are reused in a table
view.
Here’s the code for a UIPickerView (Figure 12-5) that displays the names of the
50 U.S. states, stored in an array. We implement pickerView:viewForRow:forComponent:reusingView: just because it’s the most interesting case; as our views, we
supply UILabel instances. The state names appear centered because the labels are cen‐
tered within the picker view:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
return 50;
}
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row
forComponent:(NSInteger)component reusingView:(UIView *)view {
UILabel* lab;
if (view)
lab = (UILabel*)view; // reuse it
else
lab = [UILabel new];
lab.text = self.states[row];
lab.backgroundColor = [UIColor clearColor];
[lab sizeToFit];
return lab;
}
The delegate may further configure the UIPickerView’s physical appearance by means
of these methods:
• pickerView:rowHeightForComponent:
• pickerView:widthForComponent:
568
|
Chapter 12: Controls and Other Views
www.it-ebooks.info
Figure 12-6. A search bar with a search results button
The delegate may implement pickerView:didSelectRow:inComponent: to be notified
each time the user spins a drum to a new position. You can also query the picker view
directly by sending it selectedRowInComponent:.
You can set the value to which any drum is turned using selectRow:inComponent:animated:. Other handy picker view methods allow you to request that the
data be reloaded, and there are properties and methods to query the picker view’s con‐
tents (though of course they do not relieve you of responsibility for knowing the data
model from which the picker view’s contents are supplied):
• reloadComponent:
• reloadAllComponents
• numberOfComponents
• numberOfRowsInComponent:
• viewForRow:forComponent:
By implementing pickerView:didSelectRow:inComponent: and using reloadComponent:, you can make a picker view where the values displayed by one drum depend
dynamically on what is selected in another. For example, one can imagine expanding
our U.S. states example to include a second drum listing major cities in each state; when
the user switches to a different state in the first drum, a different set of major cities
appears in the second drum.
UISearchBar
A search bar (UISearchBar) is essentially a wrapper for a text field; it has a text field as
one of its subviews, though there is no official access to it. It is displayed by default as a
rounded rectangle containing a magnifying glass icon, where the user can enter text
(Figure 12-6). It does not, of itself, do any searching or display the results of a search; a
common interface involves displaying the results of a search as a table, and the
UISearchDisplayController class makes this easy to do (see Chapter 8).
A search bar’s current text is its text property. It can have a placeholder, which appears
when there is no text. A prompt can be displayed above the search bar to explain its
UISearchBar
www.it-ebooks.info
|
569
purpose. Delegate methods (UISearchBarDelegate) notify you of editing events; for
their use, compare the text field and text view delegate methods discussed in Chapter 10:
• searchBarShouldBeginEditing:
• searchBarTextDidBeginEditing:
• searchBar:textDidChange:
• searchBar:shouldChangeTextInRange:replacementText:
• searchBarShouldEndEditing:
• searchBarTextDidEndEditing:
A search bar has a barStyle, for which your choices and their default appearances are:
• UIBarStyleDefault, a flat light gray background and a white search field
• UIBarStyleBlack, a black background and a black search field
In iOS 7, both styles are translucent. In addition, iOS 7 introduces a searchBarStyle
property:
• UISearchBarStyleDefault, as already described
• UISearchBarStyleProminent, identical to UISearchBarStyleDefault
• UISearchBarStyleMinimal, transparent background and dark transparent search
field
Alternatively, you can set a UISearchBarStyleDefault search bar’s barTintColor to
change its background color; if the bar style is UIBarStyleBlack, the barTintColor will
also tint the search field itself. An opaque barTintColor is a way to make a search bar
opaque. This property is new in iOS 7; the old tintColor property, whose value may
be inherited from higher up the view hierarchy, now governs the color of search bar
components such as the Cancel button title and the flashing insertion cursor.
A search bar can also have a custom backgroundImage; this will be treated as a resizable
image. The full setter method in iOS 7 is setBackgroundImage:forBarPosition:barMetrics:; I’ll talk later about what bar position and bar metrics are. The backgroundImage overrides all other ways of determining the background, and the search bar’s
backgroundColor, if any, appears behind it — though under some circumstances, if the
search bar’s translucent is NO, the barTintColor may appear behind it instead.
The search field area where the user enters text can be offset with respect to its back‐
ground, using the searchFieldBackgroundPositionAdjustment property; you might
do this, for example, if you had enlarged the search bar’s height and wanted to position
570
|
Chapter 12: Controls and Other Views
www.it-ebooks.info
the search field within that height. The text can be offset within the search field with the
searchTextPositionAdjustment property.
You can also replace the image of the search field itself; this is the image that is normally
a rounded rectangle. To do so, call setSearchFieldBackgroundImage:forState:. Ac‐
cording to the documentation, the possible state: values are UIControlStateNormal
and UIControlStateDisabled; but the API provides no way to disable a search field,
so what does Apple have in mind here? The only way I’ve found is to cycle through the
search bar’s subviews, find the search field, and disable it:
for (UIView* v in [self.searchbar.subviews[0] subviews]) {
if ([v isKindOfClass: [UITextField class]]) {
UITextField* tf = (UITextField*)v;
tf.enabled = NO;
break;
}
}
The search field image will be drawn in front of the background and behind the contents
of the search field (such as the text); its width will be adjusted for you, but its height will
not be — instead, the image is placed vertically centered where the search field needs
to go. It’s up to you choose an appropriate height, and to ensure an appropriate color in
the middle so the user can read the text.
A search bar displays an internal cancel button automatically (normally an X in a circle)
if there is text in the search field. Internally, at its right end, a search bar may display a
search results button (showsSearchResultsButton), which may be selected or not
(searchResultsButtonSelected), or a bookmark button (showsBookmarkButton); if
you ask to display both, you’ll get the search results button. These buttons vanish if text
is entered in the search bar so that the cancel button can be displayed. There is also an
option to display a Cancel button externally (showsCancelButton, or call setShowsCancelButton:animated:). The internal cancel button works automatically to remove
whatever text is in the field; the other buttons do nothing, but delegate methods notify
you when they are tapped:
• searchBarResultsListButtonClicked:
• searchBarBookmarkButtonClicked:
• searchBarCancelButtonClicked:
You can customize the images used for the search icon (a magnifying glass, by default)
and any of the internal right icons (the internal cancel button, the search results button,
and the bookmark button) with setImage:forSearchBarIcon:state:. The images will
be resized for you, except for the internal cancel button, for which about 20×20 seems
to be a good size. The icons are specified with constants:
UISearchBar
www.it-ebooks.info
|
571
• UISearchBarIconSearch
• UISearchBarIconClear (the internal cancel button)
• UISearchBarIconBookmark
• UISearchBarIconResultsList
The documentation says that the possible state: values are UIControlStateNormal
and UIControlStateDisabled, but this is wrong; the choices are UIControlStateNormal and UIControlStateHighlighted. The highlighted image appears while the
user taps on the icon (except for the search icon, which isn’t a button). If you don’t supply
a normal image, the default image is used; if you supply a normal image but no high‐
lighted image, the normal image is used for both. Setting searchResultsButtonSelected to YES reverses this button’s behavior: it displays the highlighted image, but
when the user taps it, it displays the normal image.
The position of an icon can be adjusted with setPositionAdjustment:forSearchBar-
Icon:.
A search bar may also display scope buttons (see the example in Chapter 8). These are
intended to let the user alter the meaning of the search; precisely how you use them is
up to you. To make the scope buttons appear, use the showsScopeBar property; the
button titles are the scopeButtonTitles property, and the currently selected scope
button is the selectedScopeButtonIndex property. The delegate is notified when the
user taps a different scope button:
• searchBar:selectedScopeButtonIndexDidChange:
A background image applied with setBackgroundImage:forBarPosition:barMetrics: using UIBarMetricsDefault will not appear
when the scope bar is showing. In iOS 7, when the scope bar is show‐
ing, the bar metrics value becomes UIBarMetricsDefaultPrompt (or
UIBarMetricsLandscapePhonePrompt).
The overall look of the scope bar can be heavily customized. Its background is the scopeBarBackgroundImage, which will be stretched or tiled as needed. To set the background
of the smaller area constituting the actual buttons, call setScopeBarButtonBackgroundImage:forState:; the states are UIControlStateNormal and UIControlStateSelected. If you don’t supply a separate selected image, a darkened version of the normal
image is used. If you don’t supply a resizable image, the image will be made resizable
for you; the runtime decides what region of the image will be stretched behind each
button.
572
|
Chapter 12: Controls and Other Views
www.it-ebooks.info
Figure 12-7. A horrible search bar
The dividers between the buttons are normally vertical lines, but you can customize
them as well: call setScopeBarButtonDividerImage:forLeftSegmentState:rightSegmentState:. A full complement of dividers consists of three images, one when the
buttons on both sides of the divider are normal (unselected) and one each when a button
on one side or the other is selected; if you supply an image for just one state combination,
it is used for the other two state combinations. The height of the divider image is adjusted
for you, but the width is not; you’ll normally use an image just a few pixels wide.
The font attributes of the titles of the scope buttons can customized by calling setScopeBarButtonTitleTextAttributes:forState:. In iOS 7, the attributes: argument is
an NSAttributedString attributes dictionary; in earlier systems, you could set only the
font, text color, and text shadow, but in iOS 7 it is legal to provide other attributes, such
as an underline.
It may appear that there is no way to customize the external Can‐
cel button, but in fact, although you’ve no official direct access to it
through the search bar, the Cancel button is a UIBarButtonItem and
you can customize it using the UIBarButtonItem appearance proxy,
discussed later in this chapter.
By combining the various customization possibilities, a completely unrecognizable
search bar of inconceivable ugliness can easily be achieved (Figure 12-7). Let’s be careful
out there.
The problem of allowing the keyboard to appear without hiding the search bar is exactly
as for a text field (Chapter 10). Text input properties of the search bar configure its
keyboard and typing behavior like a text field as well:
• keyboardType
• autocapitalizationType
• autocorrectionType
UISearchBar
www.it-ebooks.info
|
573
• spellCheckingType
• inputAccessoryView
When the user taps the Search key in the keyboard, the delegate is notified, and it is
then up to you to dismiss the keyboard (resignFirstResponder) and perform the
search:
• searchBarSearchButtonClicked:
A common interface is a search bar at the top of the screen. On the iPad, a search bar
can be embedded as a bar button item’s view in a toolbar at the top of the screen. On
the iPhone, a search bar can be a navigation item’s titleView. In Chapter 8, I discussed
UISearchDisplayController’s displaysSearchBarInNavigationBar property. A search
bar used in this way, however, has some limitations: for example, there may be no room
for a prompt, scope buttons, or an external Cancel button, and you might not be able
to assign it a background image or change its barTintColor.
On the other hand, in iOS 7, a UISearchBar can itself function as a top bar, like a navi‐
gation bar without being in a navigation bar. If you use a search bar in this way, you’ll
want its height to be extended automatically under the status bar; I’ll explain later in
this chapter how to arrange that.
UIControl
UIControl is a subclass of UIView whose chief purpose is to be the superclass of several
further built-in classes and to endow them with common behavior. These are classes
representing views with which the user can interact (controls).
The most important thing that controls have in common is that they automatically track
and analyze touch events (Chapter 5) and report them to your code as significant control
events by way of action messages. Each control implements some subset of the possible
control events. The full set of control events is listed under UIControlEvents in the
Constants section of the UIControl class documentation:
• UIControlEventTouchDown
• UIControlEventTouchDownRepeat
• UIControlEventTouchDragInside
• UIControlEventTouchDragOutside
• UIControlEventTouchDragEnter
• UIControlEventTouchDragExit
• UIControlEventTouchUpInside
574
|
Chapter 12: Controls and Other Views
www.it-ebooks.info