Using Protocols and Delegates to pass data between views

It’s a common pattern to have a form where the user needs to choose some option, when he clicks in one of the options, it redirects to another view where he can choose between many options. After selecting the correct options you usually want to redirect your user to the previous view with the selected data in place. A common way of doing this is defining a formal protocol.

Understanding correctly how to use delegates in iphone programming is key to achieve clean code. A delegate is a helper object which is used by another object that instead of performing some action it “passes the bucket” to another object, the delegate. This is called “inversion of responsability or dependency inversion“. This is a common abstraction pattern called delegation.

Protocols are vary similar to Java interfaces. They difference is that a class may implement a protocol without declaring that it implements it. At Wikipedia you can read a more in depth explanation of the concept of protocols.

There are a couple of texts like this one that describes a singleton solution to “Passing simple data between views“, in a sense it’s a way of using global variables to share data among all the parts of the application. From a OOP perspective it’s strange, because it gives access to every object on the application to alter these variables.

This question on stack overflow points to three possible solutions: setting a member variable in the child, when it’s passed to the parent, send a message to [self superview] and use a NSNotification.

One interesting thread where auditors of CS 193P iphone application development course on stanford discuss the correct way (from an architecture standpoint) of passing values between views. According to these guys, you should never use the application delegate neither global variables, and point to using delegation.

Apple has a very extensive explanation about the concept of protocols in their developer website.

The Core Data Books example app from apple has a very good implementation of protocols in DetailViewController.m

Adjust UITextField hidden behind Keyboard with UIScrollView

Every time you create a form you’re gonna probably have some UITextField in it. When you have a lot of them, it’s very commom to have the UITextField hidden behind the Keyboard. I’ve searched all over for a solution to the problem of textfields hidden behind the keyboard and found that the solution is to use a UIScrollView. It’s possible to scroll some items over the keyboard and animate them so it goes up as smooth as Apple’s own applications

The first thing worth of note that I could find was a blog post by John Muchow in which he describes a possible solution. According to his text you can solve the problem by implementing UIKeyboardDidShowNotification and UIKeyboardDidHideNotification. These are the main notification events a keyboard will fire. There is also UIKeyboardWillHideNotification that can be used for our purpose.

The problem with his solution is that the field only pops when you actually start typing, not when you touch/click the UITextField. This one was kinda easy to solve, just using scrollRectToVisible, but it still put the UITextField right over the keyboard. As I wanted to mimic as much as Apple Design Guidelines for Moving Content Under the Keyboard, I put a small offset on it. You can download the sample and read through the text to understand it.

Another great text you can found on Sliding UITextFields, where Matt Gallagher describes an approach that splits the screen in three sections.

Creating Base App

From XCode menu choose New Project, IPhone OS Application and finally View Base Application. Let’s name it sample-scrollview. Under Classes, there should be a file named sample_scrollviewViewController.h, open it and create three IBOutlets we’re gonna need to demonstrate this sample.

// sample_scrollviewViewController.h
@interface sample_scrollviewViewController : UIViewController {
    IBOutlet UIScrollView *scrollview;
    IBOutlet UITextField *tx1;
    IBOutlet UITextField *tx2;
}

@property(nonatomic,retain) IBOutlet UIScrollView *scrollview;
@property(nonatomic,retain) IBOutlet UITextField *tx1;
@property(nonatomic,retain) IBOutlet UITextField *tx2;

And then add the synthesize directive in the implementation file:

// sample_scrollviewViewController.m
@implementation sample_scrollviewViewController

@synthesize scrollview,tx1,tx2;

Create Interface Using Interface Builder

Next we’re gonna create the User Interface using XCode’s Interface Builder. Browse the project looking for sample_scrollviewViewController.xib inside the Resources folder.File's Owner Properties

Double-click sample_scrollviewViewController.xib to open interface builder. From the Object Library drag a Scroll View (UIScrollView) into the main view, it should resize appropriately when you drop it over the existing view. Now drag two UITextFields over the UIScrollView. Now it’s time to connect the pieces on the Interface Builder to the IBOutlets inside your viewcontroller. Now right-click the File’s Owner Icon and associate the IBOutlets. You should associate the scrollview with the UIScrollView object, and tx1 and tx2 with the respective UITextFields on the screen. Then you should drag the Referencing Outlets to the TextFields and choose the delegate. Now you may close Interface Builder as we’re not gonna need it again for now. There is a good explanation about doing this on Interface Builder on Jonathan Crowe’s Animating a View in IPhone SDK or if you want to do it programatically use How can I add a UIScrollView to my Application

Implementing Notifications for Keyboard Activity

Now open up again the implementation file and let’s code what we need to receive notifications from keyboard activity. First of all let’s define some constants that we’re gonna need to determine the size of the content. It’s important to note, that if you define a content height taller than the iphone screen you’ll and up with scrolling on the screen when the keyboard is hidden. Our objective is to keep the scroll of when the keyboard is hidden. Next to the top of the implementation file, right before the import statement, let’s define the following:

#define SCROLLVIEW_CONTENT_HEIGHT 460
#define SCROLLVIEW_CONTENT_WIDTH  320

#import "sample_scrollviewViewController.h"

Then we need to implement the methods to listen to the notification of keyboard activity. But first, let’s get back to the header file and create a variable to keep state information about the visibility of the keyboard. You can put it right after the declaration of the IBOutlets.

	IBOutlet UITextField *tx2;

	BOOL keyboardVisible;

There were a couple of questions on stackoverflow about doing this. One of them is an answer by Micheal Baltaks where he explains how to handle this when you’re using tableviews that need to be scrolled, another one is How to Make a UITextField move up when keyboard is present.
Then let’s implement code to handle notifications from keyboard. These methods will allow us to receive the notifications sent from the events.

- (void) viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
	NSLog(@"Registering for keyboard events");

	// Register for the events
	[[NSNotificationCenter defaultCenter]
				addObserver:self
				selector:@selector (keyboardDidShow:)
				name: UIKeyboardDidShowNotification
				object:nil];
	[[NSNotificationCenter defaultCenter]
				addObserver:self
				selector:@selector (keyboardDidHide:)
				name: UIKeyboardDidHideNotification
				object:nil];

	// Setup content size
	scrollview.contentSize = CGSizeMake(SCROLLVIEW_CONTENT_WIDTH,
										SCROLLVIEW_CONTENT_HEIGHT);

	//Initially the keyboard is hidden
	keyboardVisible = NO;
}

-(void) viewWillDisappear:(BOOL)animated {
	NSLog (@"Unregister for keyboard events");
	[[NSNotificationCenter defaultCenter]
				removeObserver:self];
}

Then implement the code to handle these notifications, but first we need to create another state variable, this one, offset, will hold the distance of the content from the top of the scroll, we are gonna need it to restore the scroller to its original position. Just put it right after the keyboardVisible.

	BOOL keyboardVisible;
	CGPoint	offset;

Now the code for the keyboardDidShow, put it in the implementation file.

-(void) keyboardDidShow: (NSNotification *)notif {
	NSLog(@"Keyboard is visible");
	// If keyboard is visible, return
	if (keyboardVisible) {
		NSLog(@"Keyboard is already visible. Ignore notification.");
		return;
	}

	// Get the size of the keyboard.
	NSDictionary* info = [notif userInfo];
	NSValue* aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
	CGSize keyboardSize = [aValue CGRectValue].size;

	// Save the current location so we can restore
	// when keyboard is dismissed
	offset = scrollview.contentOffset;

	// Resize the scroll view to make room for the keyboard
	CGRect viewFrame = scrollview.frame;
	viewFrame.size.height -= keyboardSize.height;
	scrollview.frame = viewFrame;

	CGRect textFieldRect = [tx1 frame];
	textFieldRect.origin.y += 10;
	[scrollview scrollRectToVisible:textFieldRect animated:YES];

	NSLog(@"ao fim");
	// Keyboard is now visible
	keyboardVisible = YES;
}

At this point, if you run the application, you can see that the textfield you clicked (tx1) moved over the keyboard. Explaining a little bit the code above, on line 4, we check if the keyboard is already visible, it may happen if for example you click in another TextField. Later, on lines 9-12 we get the size of the keyboard. On line 16 we save the original position (offset) of the content frame, so as to restore it later when the keyboard hides. On lines 18-21 we resize the UIScrollView frame, subtracting the size of the keyboard. Lines 23-25 get the position of the first UITextField and tell the UIScrollView to scroll until it’s visible, before, on line 24 we add 10 points as a margin from the UITextField to the Keyboard. At last on line 29 we configure the keyboard state variable as visible.

Implementing the Keyboard Handling Events

-(void) keyboardDidHide: (NSNotification *)notif {
	// Is the keyboard already shown
	if (!keyboardVisible) {
		NSLog(@"Keyboard is already hidden. Ignore notification.");
		return;
	}

	// Reset the frame scroll view to its original value
	scrollview.frame = CGRectMake(0, 0, SCROLLVIEW_CONTENT_WIDTH, SCROLLVIEW_CONTENT_HEIGHT);

	// Reset the scrollview to previous location
	scrollview.contentOffset = offset;

	// Keyboard is no longer visible
	keyboardVisible = NO;

}

But again, if you run the code right now, you will notice that the keyboard does not close, and that you cannot see the same effect on the second UITextField. Now we’re gonna fix that. Just add another variable to hold the state of which textfield is active, put it in your header file, which at last will look like the following:

@interface sample_scrollviewViewController : UIViewController {
    IBOutlet UIScrollView *scrollview;
    IBOutlet UITextField *tx1;
    IBOutlet UITextField *tx2;

	BOOL keyboardVisible;
	CGPoint offset;
	UITextField *activeField;
}

Making the Keyboard Close

Now we need to implement UITextField method delegates to make the keyboard close and set the active UITextField. The first code snippet, sets the current active UITextField:

-(BOOL) textFieldShouldBeginEditing:(UITextField*)textField {
	activeField = textField;
	return YES;
}

The second one, makes the keyboard close

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
	[textField resignFirstResponder];
	return YES;
}

Also we need to change the implementation method of keyboardDidShow, and change the line that recover the frame of the UITextField to the following:

	scrollview.frame = viewFrame;

	CGRect textFieldRect = [activeField frame];
	textFieldRect.origin.y += 10;

As you can see it’s very simple to achieve good results, if you want to go further, you should calculate the middle of the visible area, so as to have a broader margin from the keyboard, this will mimic even better Apple Behavior in their applications.

You can download the full XCode Project for Adjusting UITextFields hidden behind Keyboard or browse the code on github.