<p>Many of the UIKit classes in iOS SDK use the Delegation design pattern. Delegation means that you designate one object to act on behalf of another object. A common example of this is the table view in UIKit. A table view will require an object to act as a delegate to do things like return the number of rows in a section, return a table cell and so on.</p>
<p>You are probably already familiar with using Delegation as its presented to you in UIKit. Today I want to show you how to implement delegation for yourself, but first let's talk about why you would want to do this.</p>
<h3>Use Case</h3>
<p>Have you ever had views in a navigation controller that have a parent-child type relationship where you can edit data in the child view that is supposed to be updated in the parent view? For example, in my app users can select a note from a table view and get directed to a child screen that allows them to edit the note.</p>
<p>The problem is that the original note screen does not get updated with the new information when the user touches the back button (at least not without reloading the entire table view which could get expensive).</p>
<h3>How Delegation Can Help</h3>
<p>Delegation is a nice way to handle this situation. What I do is have the parent table view controller act as a delegate for the child editing view. When a user is done writing a note the editing view controller will send a message back to the parent table view controller. This gives me a chance to update the UI with the new note information.</p>
<h3>What You Need To Use Delegation</h3>
<p>Let's assume that you have a navigation based app with a table view (let's call this ParentTable) that holds a list of strings. When you touch a cell on the table view you are taken to a view (let's call this ChildEditor) where you can edit the string.</p>
<p>You are going to need to do these things to use Delegation:</p>
<p>1.) Define a protocol for ChildEditor</p>
<p>2.) Add a delegate property to ChildEditor that requires the protocol from step 1</p>
<p>3.) Add a NSIndexPath property to ChildEditor to remember what table view cell the string is displayed in</p>
<p>4.) When ChildEditor updates a string it must send a message to the delegate property from step 2</p>
<p>5.) ParentTable must adopt the protocol from the step 1</p>
<p>6.) ParentTable must implement the delegate method that corresponds to the message that will be sent when ChildEditor updates a string (this is when the UI is updated)</p>
<p>7.) Before a new ChildEditor is pushed onto the navigation controller set the delegate property to the ParentTable (you can use the self keyword here)</p>
<p>8.) Before a new ChildEditor is pushed onto the navigation controller set the indexPath property to the property that you get in the didSelectRowAtIndexPath method</p>
<h3>Example Code</h3>
<p>To test this out for yourself you can create a navigation based app with XCode. You will need to add your own ChildEditor that has a text field hooked up with Interface Builder already. To stay consistent, I named the root table view ParentTable (you can use XCode's refactoring tool to do this).</p>
<p>Let's go through all the steps above now but adding in the code. To make sure that you can see the code in context I include all the code in the file and then bold the code that was added in each step - sometimes you may need to scroll down to see the new code.</p>
<p>Quick note here: textToEdit is the string that we will editing here and updating the ParentTable UI with.</p>
<h3>1.) Define a protocol for ChildEditor</h3>
<h4>ChildEditor Header File (ChildEditor.h)</h4>
<pre>
#import <UIKit/UIKit.h>
<strong>
@protocol ChildEditorDelegate <NSObject>
-(void) thisNoteWasJustUpdated: (NSMutableString *)note atThisIndexPath: (NSIndexPath *)noteIndexPath;
@end</strong>
@interface ChildEditor : UIViewController<UITextFieldDelegate> {
NSMutableString *textToEdit;
UITextField *editingTextField;
}
@property(nonatomic, strong) NSMutableString *textToEdit;
@property(nonatomic, strong) IBOutlet UITextField *editingTextField;
@end
</pre>
<h3>2.) Add a delegate property to ChildEditor that requires the protocol from step 1</h3>
<h3>AND</h3>
<h3>3.) Add a NSIndexPath property to ChildEditor to remember what table view cell the string is displayed in</h3>
<h4>ChildEditor Header File (ChildEditor.h)</h4>
<pre>
#import <UIKit/UIKit.h>
@protocol ChildEditorDelegate <NSObject>
-(void) thisNoteWasJustUpdated: (NSMutableString *)note atThisIndexPath: (NSIndexPath *)noteIndexPath;
@end
@interface ChildEditor : UIViewController<UITextFieldDelegate> {
NSMutableString *textToEdit;
UITextField *editingTextField;
<strong>NSIndexPath *indexPath;
id<ChildEditorDelegate> delegate;</strong>
}
@property(nonatomic, strong) NSMutableString *textToEdit;
@property(nonatomic, strong) IBOutlet UITextField *editingTextField;
<strong>
@property(nonatomic, assign) NSIndexPath *indexPath;
@property(nonatomic, assign) id<ChildEditorDelegate> delegate;</strong>
@end
</pre>
<h4>ChildEditor Implementation File(ChildEditor.m)</h4>
<pre>
#import "ChildEditor.h"
@implementation ChildEditor
...
[CODE OMITTED]
...
-(void)dealloc{
<strong>
delegate = nil;
}
@end
</pre>
<h3>4.) When ChildEditor updates a string it must send a message to the delegate property from step 2</h3>
<h4>ChildEditor Implementation File(ChildEditor.m)</h4>
<pre>
#import "ChildEditor.h"
@implementation ChildEditor
- (BOOL)textFieldShouldReturn: (UITextField *)textField{
[textToEdit appendString:textField.text];
<strong>[delegate thisNoteWasJustUpdated:textToEdit atThisIndexPath:indexPath];</strong>
[textField resignFirstResponder];
[self.navigationController popViewControllerAnimated:YES];
return YES;
}
...
[CODE OMITTED]
...
@end
</pre>
<h3>5.) ParentTable must adopt the protocol from the step 1</h3>
<h4>ParentTable Header File(ParentTable.h)</h4>
<pre>
#import <UIKit/UIKit.h>
#import "ChildEditor.h"
@interface ParentTable : UITableViewController<strong><ChildEditorDelegate></strong>{
NSMutableArray *listOfStrings;
}
@property(nonatomic, strong) NSMutableArray *listOfStrings;
@end
</pre>
<h3>6.) ParentTable must implement the delegate method that corresponds to the message that will be sent when ChildEditor updates a string (this is when the UI is updated)</h3>
<h4>ParentTable Implementation File(ParentTable.m)</h4>
<pre>
#import "ParentTable.h"
@implementation ParentTable
@synthesize listOfStrings;
<strong>-(void) thisNoteWasJustUpdated: (NSMutableString *)note atThisIndexPath: (NSIndexPath *)noteIndexPath{
UITableViewCell *tvc = [self.tableView cellForRowAtIndexPath:noteIndexPath];
tvc.textLabel.text = note;
}</strong>
...
[CODE OMITTED]
...
@end
</pre>
<p>NOTE: here listOfStrings is an array that is serving as our makeshift data model. The other code is typical table view controller delegate methods that you need to implement to make table views work.</p>
<h3>7.) Before a new ChildEditor is pushed onto the navigation controller set the delegate property to the ParentTable (you can use the self keyword here)</h3>
<h3>AND</h3>
<h3>8.) Before a new ChildEditor is pushed onto the navigation controller set the indexPath property to the property that you get in the didSelectRowAtIndexPath method</h3>
<h4>ParentTable Implementation File(ParentTable.m)</h4>
<pre>
#import "ParentTable.h"
@implementation ParentTable
@synthesize listOfStrings;
-(void) thisNoteWasJustUpdated: (NSMutableString *)note atThisIndexPath: (NSIndexPath *)noteIndexPath{
UITableViewCell *tvc = [self.tableView cellForRowAtIndexPath:noteIndexPath];
tvc.textLabel.text = note;
}
...
[CODE OMITTED]
...
- (void)tableView: (UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath{
ChildEditor *detailViewController = [[ChildEditor alloc] initWithNibName:
@EditorView bundle:nil];
detailViewController.textToEdit = [listOfStrings objectAtIndex:indexPath.row];
<strong> detailViewController.indexPath = indexPath;
detailViewController.delegate = self;</strong>
[self.navigationController pushViewController:detailViewController animated:YES];
}
@end
</pre>
<h3>Final Thoughts</h3>
<p>You should be able to run your app, edit your note and then see the changes when you return back to the starting screen.</p>
<p>This example has very simple on purpose, but you can probably imagine lots of situations where you may want to implement Delegation in your own app. I have found that this method works pretty well in data model objects when information may be altered from the app or other sources like web services.</p>
Replies
<br />
New app - See screenshots and details at <a href="http://www.globaclock.com" target="_blank">www.globaclock.com</a>.<br />
<br />
If you want to
Domele - thanks for pointing that out. I wrote this before I really understood the retain cycle problem.
<br />
<i>Author of Objective-C Recipes, blogger and iOS trainer</i>
Maybe you should post the code where you're having the problem? My feeling is that you should be able to use an image here.
<br />
<i>Author of Objective-C Recipes, blogger and iOS trainer</i>
- (IBAction)save:(id)sender {
//converts the drawing
UIImage *image = [drawingView imageRepresentation];
[(UIImageView*)mainViewController.image1 setImage:image];
//saves it to library
UIImageWriteToSavedPhotosAlbum(image, self, nil, nil);
[self dismissModalViewControllerAnimated:YES]
The first view has an image view and a button. This view is managed by a class named ViewController. When you press the button a model view pops up with another image view (that you would use to draw) and a button.
When you press the button the model view is dismissed. This model view is managed by a class called drawViewController. When the model view is dismissed the image view on the first screen will display what was created in the model view's image view.
Now, let's implement using delegation. The first thing we need to do is define a protocol for the drawViewController class in drawViewController.h:
<pre>#import <UIKit/UIKit.h>
<strong>@protocol drawViewControllerDelegate <NSObject>
@required
-(void)justPickedThisImage:(UIImage *)image;
@end</strong>
@interface drawViewControllerViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIImageView *drawImageView;
- (IBAction)pickImage:(id)sender;
@end</pre>
The protocol has one required method <strong>justPickedThisImage:</strong>.
Next, we'll need a delegate property for drawViewController:
<pre>#import <UIKit/UIKit.h>
@protocol drawViewControllerDelegate <NSObject>
@required
-(void)justPickedThisImage:(UIImage *)image;
@end
@interface drawViewControllerViewController : UIViewController
<strong>@property (weak) id<drawViewControllerDelegate> delegate;</strong>
@property (weak, nonatomic) IBOutlet UIImageView *drawImageView;
- (IBAction)pickImage:(id)sender;
@end</pre>
Now we'll need to locate the spot in this model view where the image is picked. Then we'll send a message to the delegate with the image as a parameter before dismissing the model view.
This code is located in the file <strong>drawViewController.m</strong>.
<pre>#import "drawViewControllerViewController.h"
@implementation drawViewControllerViewController
@synthesize delegate, drawImageView;
- (IBAction)pickImage:(id)sender {
<strong>[self.delegate justPickedThisImage:self.drawImageView.image];</strong>
[self dismissModalViewControllerAnimated:YES];
}
- (void)viewDidUnload {
[self setDelegate:nil];
[self setDrawImageView:nil];
[super viewDidUnload];
}
@end</pre>
Note that we also set the delegate to nil in the viewDidUnload method.
<strong>Moving On to View Controller Class</strong>
ViewController is the view controller class responsible for presenting the model view and then displaying the content that was created. We'll need to adopt the drawViewController protocol first.
This code is in <strong>ViewController.h</strong>.
<pre>#import <UIKit/UIKit.h>
<strong>#import "drawViewControllerViewController.h"</strong>
@interface ViewController : UIViewController<strong><drawViewControllerDelegate></strong>
@property (weak, nonatomic) IBOutlet UIImageView *myImageView;
@end</pre>
Next, we gotta implement that delegate method in <strong>ViewController.m</strong>.
<pre>#import "ViewController.h"
@implementation ViewController
@synthesize myImageView;
<strong>-(void)justPickedThisImage:(UIImage *)image{
self.myImageView.image = image;
}</strong>
- (void)viewDidUnload{
[self setMyImageView:nil];
[super viewDidUnload];
}
@end</pre>
This is how we can update the UI. Finally, we need to set the delegate property. Since I'm using Storyboards I'll need to use the prepareForSegue:sender: method (but you could also have this attached to an IBAction that presents the model view).
<pre>#import "ViewController.h"
@implementation ViewController
@synthesize myImageView;
<strong>-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
drawViewControllerViewController *dvc = [segue destinationViewController];
dvc.delegate = self;
}</strong>
-(void)justPickedThisImage:(UIImage *)image{
self.myImageView.image = image;
}
- (void)viewDidUnload{
[self setMyImageView:nil];
[super viewDidUnload];
}
@end</pre>
That should be it. Let us know how it goes.
<br />
<i>Author of Objective-C Recipes, blogger and iOS trainer</i>
Thank you again for you help! I started my project as a 'SingleView" so Im not sure if I can add the Segue code/ StoryBoard in it . should I just redo the project as a storyboard if thats the only way it can be done?
You can use this pattern with or without Storyboards. The key thing is how the delegate method was implemented. If you really want to copy this exactly how I did it, then yes you'll need to use a storyboard based app or just add a storyboard to your application.
<br />
<i>Author of Objective-C Recipes, blogger and iOS trainer</i>
gotcha, Im going to figure it out without Storyboards first.
Now, I'm facing this problem where I don't know how to apply the last step.
I am creating an example application where there's view1 class and a popupview class.
There is a button and a label on the first class' view. The button calls the [self.view addsSubView:popupviewObj.view];
On the popupview which has then appeared, there's a textfield and a 'send' button. Now I want that when the user click the sendbutton the [self.view removeFromSuperview]; method gets called and the application returns to view1 class but with the text in the label changed to what I entered in the popup class.
Can you guide me what different do I need to do in this application from what you did? I am using XIBs.
Thanks in advance.
The real answer that I am looking for is where the use of custom delegate is absolutely necessary. An example will be really helpful.
Last, please don't mind if my questions come across as very novice-grade. I am new not only to the iPhone Programming, but to the programming scene altogether!
Thanks!
<br />
<i>Author of Objective-C Recipes, blogger and iOS trainer</i>
@interface ChildEditor : UIViewController -UITextFieldDelegate-
What would be the proper delegate? Any thoughts? I searched but didn't find much to help.,
<br />
<i>Author of Objective-C Recipes, blogger and iOS trainer</i>
@interface ChildEditor:UIViewController<UITextFieldDelegate>
to something like
@interface ChildEditor:UIViewController<UIPickerViewDelegate>
?<br />
<i>Author of Objective-C Recipes, blogger and iOS trainer</i>