Tải bản đầy đủ (.pdf) (89 trang)

iOS 5 Programming Cookbook phần 4 pptx

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 (3.86 MB, 89 trang )

happens when the user decides to flip to the next page. The second method is called
when the page view controller wants to figure out which view controller to display after
the view controller which is being flipped.
Xcode, as you've already seen it, has greatly simplified setting up a page-based appli-
cation. All you really need to do now is to provide content to the data model (Model
Controller) and on you go. If you need to customize the colors and images in your view
controllers, simply do so by either using the Interface Builder to modify the .xib files
directly or write your own code in the implementation of each of the view controllers.
See Also
XXX
2.22 Displaying Popovers with UIPopoverController
Problem
You want to display content on an iPad without blocking the whole screen.
Solution
Use popovers.
Discussion
Popovers are used to display additional information on the iPad screen. An example is
the Safari app on the iPad. When the user taps on the Bookmarks button, she will see
a popover displaying the bookmarks content on the screen (see Figure 2-71).
2.22 Displaying Popovers with UIPopoverController | 251
Figure 2-71. The bookmarks popover in the Safari app on an iPad
The default behaviour of popovers is that when the user taps somewhere outside the
region of the popover, the popover will automatically get dismissed. We can ask the
popover to not get dismissed if the user taps on specific parts of the screen, as we will
see later. Popovers present their content by using a view controller. Note that you can
also navigation controllers inside popovers as navigation controllers subclass UIView
Controller.
Popovers can only be used on iPad devices. If you have a view controller
whose code runs both on an iPad and on an iPhone, you need to make
sure that you are not instantiating the popover on a device other than
the iPad.


Popovers can be presented to the user in two ways:
1. From inside a navigation button, an instance of UIBarButtonItem.
2. From inside a rectangular area in a view.
When a device orientation is changed (the device is rotated), popovers are either dis-
missed or hid temporarily. You need to make sure that you give your users a good
experience by redisplaying the popover after the orientation change has settled, if pos-
sible. In certain cases, your popover might get dismissed automatically after an orien-
tation change. For instance, if the user taps on a navigation button in landscape mode
you might display a popover on the screen. Your app is designed in a way that when
the orientation changes to portrait, you will remove that navigation button from the
252 | Chapter 2: Implementing Controllers and Views
navigation bar, for whatever reason. Now, the correct user experience would be to hide
the popover associated with that navigation bar after the orientation of the device is
changed to portrait. In some instances though, you will need to play with popovers a
bit to give your users a good experience because not in all instances handling a device
orientation is as straightforward as the aforementioned scenario.
To create the demo popover app, we need to first come up with a strategy based on our
requirements: we want to build an app with a view controller loaded inside a navigation
controller. The root view controller will display a + button on the right corner of its
navigation bar. When the + button is tapped on an iPad device, it will display a popover
with two buttons on it. The first button will say "Photo" and the second button will
say "Audio". When the same navigation button is tapped on an iPhone device, we will
display an alert view with three buttons. The two aforementioned buttons and a cancel
button so that the user can cancel the alert view if she wishes to. When these buttons
are tapped (whether on the alert view on an iPhone or the popover on an iPad, we won't
really do anything. We will simply dismiss the alert view or the popover.
Let's go ahead and create a Single View universal project in Xcode and name the project
Displaying_Popovers_with_UIPopoverControllerViewController and then let's go to our
app delegate's header file and define a navigation controller first:
#import <UIKit/UIKit.h>

@class Displaying_Popovers_with_UIPopoverControllerViewController;
@interface Displaying_Popovers_with_UIPopoverControllerAppDelegate
: UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong)
Displaying_Popovers_with_UIPopoverControllerViewController *viewController;
@property (nonatomic, strong) UINavigationController *navigationController;
@end
Next, we will synthesize and instantiate our navigation controller in the app delegate's
implementation file and instead of the view controller, we will display the navigation
controller to the user:
#import "Displaying_Popovers_with_UIPopoverControllerAppDelegate.h"
#import "Displaying_Popovers_with_UIPopoverControllerViewController.h"
@implementation Displaying_Popovers_with_UIPopoverControllerAppDelegate
@synthesize window = _window;
@synthesize viewController = _viewController;
@synthesize navigationController;
- (BOOL) application:(UIApplication *)application
2.22 Displaying Popovers with UIPopoverController | 253
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];

UIUserInterfaceIdiom uiIdiom = [[UIDevice currentDevice] userInterfaceIdiom];

NSString *viewControllerClass =
@"Displaying_Popovers_with_UIPopoverControllerViewController_iPad";

if (uiIdiom == UIUserInterfaceIdiomPhone) {

viewControllerClass =
@"Displaying_Popovers_with_UIPopoverControllerViewController_iPhone";
}

self.viewController =
[[Displaying_Popovers_with_UIPopoverControllerViewController alloc]
initWithNibName:viewControllerClass
bundle:nil];

self.navigationController = [[UINavigationController alloc]
initWithRootViewController:self.viewController];

self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
After this, we need to go into the definition file of our view controller and define a
property of type UIPopoverController:
#import <UIKit/UIKit.h>
@interface Displaying_Popovers_with_UIPopoverControllerViewController
: UIViewController <UIAlertViewDelegate>
@property (nonatomic, strong) UIPopoverController *popoverController;
@property (nonatomic, strong) UIBarButtonItem *barButtonAdd;
@end
You can see that we are also defining a property called barButtonAdd in our view con-
troller. This is the navigation button which we will add on our navigation bar and our
plan is to display our popover when the user taps on this button (you can read more
about navigation buttons in Recipe 2.11). However, we need to make sure we instan-
tiate the popover only if the device is an iPad. Before we go ahead and implement our
root view controller with the navigation button, let's go ahead and create a subclass of

UIViewController and name it PopoverContentViewController. We will display the con-
tents of this view controller inside our popover later. See Recipe 2.7 for information
about view controllers and ways of creating them.
The content view controller displayed inside the popover will have two buttons (as per
our requirements). However, this view controller will need to have reference to the
254 | Chapter 2: Implementing Controllers and Views
popover controller in order to dismiss the popover when the user taps on any of the
buttons. For this, we need to define a property in our content view controller to refer
to the popover:
#import <UIKit/UIKit.h>
@interface PopoverContentViewController : UIViewController
@property (nonatomic, strong) UIButton *buttonPhoto;
@property (nonatomic, strong) UIButton *buttonAudio;
/* We shouldn't define this as strong. That will create a retain cycle
between the popover controller and the content view controller since the
popover controller retains the content view controller and the view controller will
retain the popover controller */
@property (nonatomic, weak) UIPopoverController *popoverController;
@end
Now we will go and synthesize these properties in the implementation file of our con-
tent view controller:
#import "PopoverContentViewController.h"
@implementation PopoverContentViewController
@synthesize buttonPhoto;
@synthesize buttonAudio;
@synthesize popoverController;

After this, we shall go ahead and create our two buttons in the content view controller
and link them to their action methods. These methods shall take care of dismissing the
popover that is displaying this view controller. Remember, the popover controller will

be responsible for assigning itself to the popoverController property of the content view
controller:
#import "PopoverContentViewController.h"
@implementation PopoverContentViewController
@synthesize buttonPhoto;
@synthesize buttonAudio;
@synthesize popoverController;
- (BOOL) isInPopover{

Class popoverClass = NSClassFromString(@"UIPopoverController");

if (popoverClass != nil &&
UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad &&
self.popoverController != nil){
return YES;
} else {
return NO;
2.22 Displaying Popovers with UIPopoverController | 255
}

}
- (void) gotoAppleWebsite:(id)paramSender{

if ([self isInPopover]){
/* Go to website and then dismiss popover */
[self.popoverController dismissPopoverAnimated:YES];
} else {
/* Handle case for iPhone */
}


}
- (void) gotoAppleStoreWebsite:(id)paramSender{

if ([self isInPopover]){
/* Go to website and then dismiss popover */
[self.popoverController dismissPopoverAnimated:YES];
} else {
/* Handle case for iPhone */
}

}
- (void)viewDidLoad{
[super viewDidLoad];

self.view.backgroundColor = [UIColor whiteColor];

self.contentSizeForViewInPopover = CGSizeMake(200.0f, 125.0f);

CGRect buttonRect = CGRectMake(20.0f,
20.0f,
160.0f,
37.0f);

self.buttonPhoto = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.buttonPhoto setTitle:@"Photo"
forState:UIControlStateNormal];
[self.buttonPhoto addTarget:self
action:@selector(gotoAppleWebsite:)
forControlEvents:UIControlEventTouchUpInside];


self.buttonPhoto.frame = buttonRect;

[self.view addSubview:self.buttonPhoto];


buttonRect.origin.y += 50.0f;
self.buttonAudio = [UIButton buttonWithType:UIButtonTypeRoundedRect];

[self.buttonAudio setTitle:@"Audio"
256 | Chapter 2: Implementing Controllers and Views
forState:UIControlStateNormal];
[self.buttonAudio addTarget:self
action:@selector(gotoAppleStoreWebsite:)
forControlEvents:UIControlEventTouchUpInside];

self.buttonAudio.frame = buttonRect;

[self.view addSubview:self.buttonAudio];

}
- (void)viewDidUnload{
[super viewDidUnload];
self.buttonPhoto = nil;
self.buttonAudio = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation
:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
@end

All good? Now let's go back to our root view controller and synthesize our properties:
#import "Displaying_Popovers_with_UIPopoverControllerViewController.h"
#import "PopoverContentViewController.h"
@implementation Displaying_Popovers_with_UIPopoverControllerViewController
@synthesize popoverController;
@synthesize barButtonAdd;

Now in the viewDidLoad method of our root view controller, we will create our navi-
gation button and based on the device type, when the navigation bar is tapped, we will
either display a popover (on the iPad) or an alert view (on the iPhone):
- (void)viewDidLoad{
[super viewDidLoad];

/* See if this class exists on the iOS running the app */
Class popoverClass = NSClassFromString(@"UIPopoverController");

if (popoverClass != nil &&
UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){

PopoverContentViewController *content =
[[PopoverContentViewController alloc] initWithNibName:nil
bundle:nil];

self.popoverController = [[UIPopoverController alloc]
initWithContentViewController:content];
2.22 Displaying Popovers with UIPopoverController | 257

content.popoverController = self.popoverController;

self.barButtonAdd = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:@selector(performAddWithPopover:)];

} else {

self.barButtonAdd = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:@selector(performAddWithAlertView:)];

}

[self.navigationItem setRightBarButtonItem:self.barButtonAdd
animated:NO];


}
- (void)viewDidUnload{
[super viewDidUnload];
self.barButtonAdd = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation
:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
The popover controller sets a reference to itself in the content view con-
troller after its initialization. This is very important. A popover control-
ler cannot be initialized without a content view controller. Once the
popover is initialized with a content view controller, you can go ahead

and change the content view controller in the popover controller, but
not during the initialization.
We have elected the performAddWithPopover: method to be invoked when the + navi-
gation bar button is tapped on an iPad device. If the device isn't an iPad, we've asked
the + navigation bar button to invoke the performAddWithAlertView: method so let's go
ahead and implement these methods and also take care of the delegate methods of our
alert view, so that we know what alert view button the user tapped on, on an iPhone:
- (NSString *) photoButtonTitle{
return @"Photo";
}
- (NSString *) audioButtonTitle{
return @"Audio";
258 | Chapter 2: Implementing Controllers and Views
}
- (void) alertView:(UIAlertView *)alertView
didDismissWithButtonIndex:(NSInteger)buttonIndex{

NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];

if ([buttonTitle isEqualToString:[self photoButtonTitle]]){
/* Adding a photo */
}
else if ([buttonTitle isEqualToString:[self audioButtonTitle]]){
/* Adding an audio */
}

}
- (void) performAddWithAlertView:(id)paramSender{

[[[UIAlertView alloc] initWithTitle:nil

message:@"Add "
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:
[self photoButtonTitle],
[self audioButtonTitle], nil] show];

}
- (void) performAddWithPopover:(id)paramSender{

[self.popoverController
presentPopoverFromBarButtonItem:self.barButtonAdd
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];

}
If you now go ahead and run your app on iPad Simulator and tap on the + button on
the navigation bar, you will see an interface similar to that shown in Figure 2-72:
2.22 Displaying Popovers with UIPopoverController | 259
Figure 2-72. Our simple popover displayed when a navigation button was tapped
If you run the same universal app on the iPhone Simulator and tap the + button on the
navigation bar, you will see results similar to that shown in Figure 2-73:
260 | Chapter 2: Implementing Controllers and Views
Figure 2-73. Popovers are replaced by alert view in a universal app
We used an important property of our content view controller and that was the con
tentSizeForViewInPopover property. The popover, when displaying its content view
controller, will read the value of this property automatically and will adjust its size
(width and height) to this size. Also, we used the presentPopoverFromBarButtonItem:per
mittedArrowDirections:animated: method of our popover in our root view controller
to display the popover over a navigation bar button. The first parameter to this method

is the navigation bar button from which the popover controller has to be displayed.
The second parameter specifies the direction of the popover on appearing, in relation
to the object from which it appears. For example, in Figure 2-72 you can see that our
2.22 Displaying Popovers with UIPopoverController | 261
popover's arrow is pointing up towards the navigation bar button. The value that you
pass to this parameter must be of type UIPopoverArrowDirection:
enum {
UIPopoverArrowDirectionUp = 1UL << 0,
UIPopoverArrowDirectionDown = 1UL << 1,
UIPopoverArrowDirectionLeft = 1UL << 2,
UIPopoverArrowDirectionRight = 1UL << 3,
UIPopoverArrowDirectionAny = UIPopoverArrowDirectionUp |
UIPopoverArrowDirectionDown |
UIPopoverArrowDirectionLeft |
UIPopoverArrowDirectionRight,
UIPopoverArrowDirectionUnknown = NSUIntegerMax
};
typedef NSUInteger UIPopoverArrowDirection;
See Also
XXX
2.23 Displaying Progress with UIProgressView
Problem
You want to display a progress bar on the screen, depicting the progress of a certain
task, for instance, the progress of downloading a file from a URL.
Solution
Instantiate a view of type UIProgressView and place it on another view.
Discussion
A progress view is what programmers know of, as progress bar. An exmaple of a pro-
gress view is depicted in a figure to come.
Progress views are generally displayed to users to show them the progress of a task that

has a well-defined start and ending point. For instance, downloading 30 files is a well-
defined task with a specific start and ending point. This task obviously finishes when
all 30 files have been downloaded. A progress view is an instance of UIProgressView
and is initialized using the designated initializer of this class, the initWithProgressView
Style: method. This method takes in the style of the progress bar to be created, as a
parameter. This parameter is of type UIProgressViewStyle and can therefore be one of
the following values:
UIProgressViewStyleDefault
This is the default style of the progress view. An example of this is the progress
view shown in a figure to come.
262 | Chapter 2: Implementing Controllers and Views
UIProgressViewStyleBar
Is similar to the UIProgressViewStyleDefault but is meant to be used for progress
views that are to be added to a tool-bar.
An instance of UIProgressView defines a property called progress (of type float). This
property tells iOS how the bar inside the progress view should be rendered. This value
must be in the range +0 to +1.0. If the value of +0 is given, the progress will appear to
have not yet started. Value of +1.0 shows the progress of 100%. The progress depicted
in figure to come is 0.5 (or 50%). To get used to creating progress views, let's immedi-
ately go and create one similar to what we saw earlier in a figure to come. First thing's
first, we need to defien a propert for our progress view:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic, strong) UIProgressView *progressView;
@end
On to synthesizing our property :
#import "ViewController.h"
@implementation ViewController
@synthesize progressView;


And eventually we will instantiate an object of type UIProgressView:
- (void)viewDidLoad{

[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];

self.progressView = [[UIProgressView alloc]
initWithProgressViewStyle:UIProgressViewStyleBar];
self.progressView.center = self.view.center;
self.progressView.progress = 0.5f;

[self.view addSubview:self.progressView];
}
- (void)viewDidUnload{
[super viewDidUnload];
self.progressView = nil;
}
Obviously, creating a progress view is very straightforward. All you really need to take
care of is to display your progress correctly because the progress property of a progress
view should be in the range +0 to +1.0 (which is a normalized value) so if you have 30
2.23 Displaying Progress with UIProgressView | 263
tasks to take care of and you have completed 20 of them so far, you need to assign the
result of the following equation to the progress property of your progress view:
self.progressView.progress = 20.0f / 30.0f;
The reason the values 20 and 30 are passed to the equation as floating
point values is to tell the compiler that the division has to happen on
floating point values, producing a value with decimal numbers. How-
ever, you provided the result of 20/30 to the compiler to place inside
the progress property of your progress view, you would get the integral
value of 0 out of the division because your division will all of a sudden

become a division between two integers and clearly, 20 cannot be divi-
ded by 30 so the result will be 0, ultimately setting the progress of your
progress view to zero; not what you'd expect.
See Also
XXX
2.24 Listening and Reacting to Keyboard Notifications
Problem
You are allowing the user to enter some text in your UI, using a text field or a text view
or other components that require the keyboard's presence. However, when the key-
board pops up on the screen, it obstructs a good half of your UI, rendering it useless.
You want to avoid this situation.
Solution
Listen to keyboard notifications and move your UI components up/down or completely
reshuffle your components so that with the keyboard obstructing the screen, what is
essential to the user is still visible to her. For more information about the actual noti-
fications sent by the keyboard, please refer to the Discussion section of this recipe.
Discussion
iOS devices do not have a physical keyboard. They have a software keyboard which
pops up whenever the user has to enter some text into a text entry such as a text field
(UITextField, see Recipe 2.14 for more information) or a text view (UITextView, see
Recipe 2.15 for more information). On the iPad, the user can even split the keyboard
and move it up and down. These are some of the edge cases that you might want to
take care of when designing your user interface. You can work with the UI designers
in your company (if you are working with one) and let them know about the possibility
of the user splitting the keyboard on the iPad. They will need to know about that before
making the artworks and creatives. We will discuss that edge-case in this recipe.
264 | Chapter 2: Implementing Controllers and Views
Let's have a look at the keyboard in iPhone first. The keyboard can get displayed in
portrait and landscape mode. In portrait, the keyboard on an iPhone looks like this:
Figure 2-74. Portrait mode keyboard on an iPhone

The keyboard in landscape mode on an iPhone will look similar to that shown in
Figure 2-75:
2.24 Listening and Reacting to Keyboard Notifications | 265
Figure 2-75. The keyboard in landscape mode on an iPhone
On the iPad however, the keyboard is a bit different. The most obvious difference is
that the keyboard is actually much bigger in size compared to the iPhone, since the iPad
screen is physically bigger. Also the user can split the keyboard if she wants to. Here is
an example of the iPad keyboard in portrait mode:
Figure 2-76. The iPad keyboard in portrait mode
266 | Chapter 2: Implementing Controllers and Views
The landscape keyboard on an iPad is wider obviously but contains the same keys as
the portrait-mode keyboard does:
Figure 2-77. The iPad keyboard in landscape mode
And here is an example of the split keyboard on the iPad, in landscape mode (the
keyboard can be split in landscape mode as well as the portrait mode):
2.24 Listening and Reacting to Keyboard Notifications | 267
Figure 2-78. Split keyboard on the iPad in landscape mode
iOS broadcasts various notifications related to the keyboard on the screen. Here is a
list of these notifications and a brief explanation for each one:
UIKeyboardWillShowNotification
This notification gets sent when the keyboard is about to get displayed on the
screen. This notification carries with it a user-info dictionary that contains various
information about the keyboard, the animation that the keyboard will use to get
displayed on the screen and etc.
UIKeyboardDidShowNotification
This notification gets broadcasted when the keyboard gets displayed on the screen.
UIKeyboardWillHideNotification
This notification is broadcasted when the keyboard is about to get removed from
the screen. This notification will carry with it a user-info dictionary that contains
various bits and pieces of information about the keyboard, the keyboard's anima-

tion when it is hiding, the duration of the animation and etc.
UIKeyboardDidHideNotification
This notification is broadcasted when the keyboard gets fully hidden after it was
being shown on the screen.
268 | Chapter 2: Implementing Controllers and Views
As already mentioned, only the UIKeyboardWillShowNotification and the UIKeyboard
WillHideNotification notifications carry a user-info dictionary with them with valid
keys and values in those dictionaries. Here are the keys in those dictionaries that you
might be interested in:
UIKeyboardAnimationCurveUserInfoKey
The value of this key specifies the type of animation curve the keyboard is using to
show or hide itself. This key contains a value (encapsulated in an object of type
NSValue) of type NSNumber which itself contains an unsigned integer of type NSUIn
teger.
UIKeyboardAnimationDurationUserInfoKey
The value of this key specifies the duration of animation the keyboard is using to
show or hide itself. This key contains a value (encapsulated in an object of type
NSValue) of type NSNumber which itself contains a double value of type double.
UIKeyboardFrameBeginUserInfoKey
The value of this key specifies the frame of the keyboard before the animation
happens. If the keyboard is about to get displayed, this will be the frame before the
keyboard animates up to get displayed. If the keyboard is already displayed and is
about to hide, this will be the frame of the keyboard as it is on the screen, before
it animates out of the screen. This key contains a value (encapsulated in an object
of type NSValue) of type CGRect.
UIKeyboardFrameEndUserInfoKey
The value of this key specifies the frame of the keyboard after the animation hap-
pens. If the keyboard is about to get displayed, this will be the frame after the
keyboard animates up and is fully displayed. If the keyboard is already displayed
and is about to hide, this will be the frame of the keyboard after it is fully hidden.

This key contains a value (encapsulated in an object of type NSValue) of type CGRect.
The frames that get reported by iOS as the beginning and ending frames
of the keyboard do not take into account the orientation of the device.
We will need to convert the reported CGRect values to a relevant ori-
entation-aware coordinate, as we will see soon in this recipe.
Let's have a look at a simple example, shall we? Let's create a simple table view on our
view controller's view and change its content inset (the margins from top, right, bottom
and left side of the table view) when the keyboard gets displayed. We will populate this
table view with 100 cells, enough to fill the entire screen, on both the iPhone and the
iPad (in a Universal app) so let's start with the header file of our view controller:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>
@property (nonatomic, strong) UITableView *myTableView;
2.24 Listening and Reacting to Keyboard Notifications | 269
@end
Now we need to synthesize the table view property in our view controller's implemen-
tation file:
#import "ViewController.h"
@implementation ViewController
@synthesize myTableView;

Next, we will instantiate the table view when our view loads and we will balance the
memory management by setting the table view to nil in the viewDidUnload method:
- (void)viewDidLoad{
[super viewDidLoad];

self.myTableView = [[UITableView alloc]
initWithFrame:self.view.bounds
style:UITableViewStyleGrouped];


self.myTableView.delegate = self;
self.myTableView.dataSource = self;
self.myTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.myTableView];

}
- (void)viewDidUnload{
[self setMyTableView:nil];
[super viewDidUnload];
}
After this, we will need to populate our table view with 100 cells and in each cell, we
will create a text field as the accessory view. We do this to allow the user to trigger the
keyboard to popup. If we don't have a text field or some means for user to enter text,
we will never be able to get the keyboard on the screen so let's do that now:
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
/* Make sure the Done button on the keyboard for each text field
(accessory views of each cell) dismisses the keyboard */
[textField resignFirstResponder];
return YES;
}
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (NSInteger) tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section{
return 100;
270 | Chapter 2: Implementing Controllers and Views
}

- (UITableViewCell *) tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{

UITableViewCell *result = nil;

static NSString *CellIdentifier = @"CellIdentifier";

result = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (result == nil){
result = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
result.selectionStyle = UITableViewCellSelectionStyleNone;
}

result.textLabel.text = [NSString stringWithFormat:
@"Cell %ld", (long)indexPath.row];

CGRect accessoryRect = CGRectMake(0.0f,
0.0f,
150.0f,
31.0f);

UITextField *accesssory = [[UITextField alloc] initWithFrame:accessoryRect];
accesssory.borderStyle = UITextBorderStyleRoundedRect;
accesssory.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
accesssory.placeholder = @"Enter Text";
accesssory.delegate = self;
result.accessoryView = accesssory;


return result;

}
Fantastic. If you now run your app on iPhone Simulator, you will see something similar
to that shown in Figure 2-79:
2.24 Listening and Reacting to Keyboard Notifications | 271
Figure 2-79. A table view with text fields for accessory view of each cell
Go ahead now and tap on the first text field (in the first cell). Now scroll the table view
all the way down to the last cell and see what happens! You cannot see the last 5-6 cells,
can you? What you can see in portrait mode on an iPhone will be similar to that shown
in Figure 2-80:
272 | Chapter 2: Implementing Controllers and Views
Figure 2-80. Keyboard obstructing the bottom half of a table view
What we can now do is to listen for the UIKeyboardWillShowNotification and the UIKey
boardWillHideNotification notifications and adjust our table view's content inset ac-
cordingly:
- (void) viewDidAppear:(BOOL)paramAnimated{
[super viewDidAppear:paramAnimated];

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

[center addObserver:self
selector:@selector(handleKeyboardWillShow:)
2.24 Listening and Reacting to Keyboard Notifications | 273
name:UIKeyboardWillShowNotification
object:nil];
[center addObserver:self
selector:@selector(handleKeyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];


}
- (void) viewDidDisappear:(BOOL)paramAnimated{
[super viewDidDisappear:paramAnimated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
A common mistake programmers make is to keep listening for keyboard
notifications even if their view controller's view is not on the screen.
They start listening for notifications in the viewDidLoad method and re-
move themselves as the observer in viewDidUnload and/or the dealloc
method if they don't have ARC enabled. This is a problematic approach
because when your view is off the screen, you should not be adjusting
any components on your screnen if the keyboard is getting displayed on
some other view/view controller. Keep in mind that keyboard notifica-
tions, just like any other notification, are broadcasted to all observer
objects and therefore you need to take extra care to make sure that you
do not react to keyboard notifications while your view is off screen.
Now that we have started listening for keyboard notifications, we can implement the
obserer methods which we submitted to NSNotificationCenter. The handleKeyboard
WillShow: method will be responsible for setting the content inset of our table view:
- (void) handleKeyboardWillShow:(NSNotification *)paramNotification{

NSDictionary *userInfo = [paramNotification userInfo];

NSValue *animationCurveObject =
[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey];

NSValue *animationDurationObject =
[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey];


NSValue *keyboardEndRectObject =
[userInfo valueForKey:UIKeyboardFrameEndUserInfoKey];

NSUInteger animationCurve = 0;
double animationDuration = 0.0f;
CGRect keyboardEndRect = CGRectMake(0, 0, 0, 0);

[animationCurveObject getValue:&animationCurve];
[animationDurationObject getValue:&animationDuration];
[keyboardEndRectObject getValue:&keyboardEndRect];

[UIView beginAnimations:@"changeTableViewContentInset"
context:NULL];
274 | Chapter 2: Implementing Controllers and Views
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:(UIViewAnimationCurve)animationCurve];

UIWindow *window = [[[UIApplication sharedApplication] delegate] window];

CGRect intersectionOfKeyboardRectAndWindowRect =
CGRectIntersection(window.frame, keyboardEndRect);

CGFloat bottomInset = intersectionOfKeyboardRectAndWindowRect.size.height;

self.myTableView.contentInset = UIEdgeInsetsMake(0.0f,
0.0f,
bottomInset,
0.0f);

NSIndexPath *indexPathOfOwnerCell = nil;

/* Also, make sure the selected text field is visible on the screen */
NSInteger numberOfCells = [self.myTableView.dataSource
tableView:self.myTableView
numberOfRowsInSection:0];

/* So let's go through all the cells and find their accessory text fields.
Once we have the refernece to those text fields, we can see which one of
them is the first responder (has the keyboard) and we will make a call
to the table view to make sure after the keyboard is displayed,
that specific cell is NOT obstructed by the keyboard */
for (NSInteger counter = 0;
counter < numberOfCells;
counter++){
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:counter
inSection:0];
UITableViewCell *cell = [self.myTableView cellForRowAtIndexPath:indexPath];
UITextField *textField = (UITextField *)cell.accessoryView;
if ([textField isKindOfClass:[UITextField class]] == NO){
continue;
}
if ([textField isFirstResponder]){
indexPathOfOwnerCell = indexPath;
break;
}
}

[UIView commitAnimations];

if (indexPathOfOwnerCell != nil){
[self.myTableView scrollToRowAtIndexPath:indexPathOfOwnerCell

atScrollPosition:UITableViewScrollPositionMiddle
animated:YES];
}
}
So here is what we are doing in this method, in that order:
2.24 Listening and Reacting to Keyboard Notifications | 275

×