You are on page 1of 30

All Topics Find tutorials, courses, and more...

Code Categories Learning Guides

IOS SDK

Blocks and Table View


Cells on iOS
by Bart Jacobs 17 Feb 2014
9 Comments

24 29

A table view cell doesn't know about the table view it belongs to and that's fine. In
fact, that's how it should be. However, people who are new to this concept are often
confused by it. For example, if the user taps a button in a table view cell, how do
you obtain the index path of the cell so you can fetch the corresponding model? In
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
this tutorial, I'll show you how not to do this, how it's usually done, and how to do
this with style and elegance.

1. Introduction
When the user taps a table view cell, the table view invokes
tableView:didSelectRowAtIndexPath:of the UITableViewDelegate protocol on
the table view delegate. This method accepts two arguments, the table view and
the index path of the cell that was selected.

The problem that we're going to tackle in this tutorial, however, is a bit more
complex. Assume we have a table view with cells, with each cell containing a
button. When the button is tapped, an action is triggered. In the action, we need to
fetch the model that corresponds with the cell's position in the table view. In other
words, we need to know the index path of the cell. How do we infer the cell's index
path if we only get a reference to the button that was tapped? That's the problem
we'll solve in this tutorial.

2. Project Setup

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Step 1: Create Project
Create a new project in Xcode by selecting theSingle View Application template
from the list of iOS Application templates. Name the project Blocks and Cells, set
Devices to iPhone, and click Next. Tell Xcode where you'd like to store the project
and hit Create.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Step 2: Update Deployment Target
Open the Project Navigator on the left, select the project in theProject section,
and set the Deployment Target to iOS 6. We do this to make sure that we can run
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
the application on both iOS 6 and iOS 7. The reason for this will become clear later
in this tutorial.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Step 3: Create UITableViewCell Subclass
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Step 3: Create UITableViewCell Subclass
Select New > File... from the File menu and choose Objective-C class from the list
of Cocoa Touch templates. Name the class TPSButtonCell and make sure it
inherits from UITableViewCell.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Open the class's header file and declare two outlets, a UILabel instance named
titleLabel and a UIButton instance named actionButton.

1 #import <UIKit/UIKit.h>
2
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
2
3 @interface TPSButtonCell : UITableViewCell
4
5 @property (weak, nonatomic) IBOutlet UILabel *titleLabel;
6 @property (weak, nonatomic) IBOutlet UIButton *actionButton;
7
8 @end

Step 4: Update View Controller


Open the header file of the TPSViewController class and create an outlet named
tableView of type UITableView . The TPSViewController also needs to adopt the
UITableViewDataSource and UITableViewDelegate protocols.

1 #import <UIKit/UIKit.h>
2
3 @interface TPSViewController : UIViewController <UITableViewDataSource
4
5 @property (weak, nonatomic) IBOutlet UITableView *tableView;
6
7 @end

We also need to take a brief look at the view controller's implementation file. Open
TPSViewController.m and declare a static variable of type NSString that we'll use
as the reuse identifier for the cells in the table view.

1 #import "TPSViewController.h"
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
#import "TPSViewController.h"
2
3 @implementation TPSViewController
4
5 static NSString *CellIdentifier = @"CellIdentifier";
6
7 // ... //
8
9 @end

Step 5: User Interface


Open the project's main storyboard, Main.Storyboard, and drag a table view to the
view controller's view. Select the table view and connect its dataSource and
delegate outlets with the view controller instance. With the table view still
selected, open the Attributes Inspector and set the number of Prototype Cells to
1 . The Content attribute should be set to Dynamic Prototypes. You should now
see one prototype cell in the table view.

Select the prototype cell and set its Class to TPSButtonCell in the Identity
Inspector. With the cell still selected, open theAttributes Inspector and set the
Style attribute to Custom and the Identifier to CellIdentifier.

Drag a UILabel instance from the Object Library to the cell's content view and
repeat this step for a UIButton instance. Select the cell, open theConnections
Inspector, and connect the titleLabel and actionButton outlets with their
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
counterparts in the prototype cell.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Before we dive back into the code, we need to make one more connection. Select
the view controller, open the Connections Inspector one more time, and connect
the view controller's tableView outlet with the table view in the storyboard. That's it
for the user interface.

3. Populating the Table View

Step 1: Create a Data Source


Let's populate the table view with some notable movies that were released in 2013.
In the TPSViewController class, declare a property of type NSArray and name it
dataSource . The corresponding instance variable will hold the movies that we'll
show in the table view. Populate dataSource with a dozen or so movies in the view
controller's viewDidLoad method.

1 #import "TPSViewController.h"
2
3 @interface TPSViewController ()
4
5 @property (strong, nonatomic) NSArray *dataSource;
6
7 @end

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
01 - (void)viewDidLoad {
02 [super viewDidLoad];
03
04 // Setup Data Source
05 self.dataSource = @[
06 @{ @"title" : @"Gravity", @"year" : @(2013) },
07 @{ @"title" : @"12 Years a Slave", @"year" : @(
08 @{ @"title" : @"Before Midnight", @"year" : @(
09 @{ @"title" : @"American Hustle", @"year" : @(
10 @{ @"title" : @"Blackfish", @"year" : @(2013
11 @{ @"title" : @"Captain Phillips", @"year" : @(
12 @{ @"title" : @"Nebraska", @"year" : @(2013) },
13 @{ @"title" : @"Rush", @"year" : @(2013) },
14 @{ @"title" : @"Frozen", @"year" : @(2013) },
15 @{ @"title" : @"Star Trek Into Darkness", @"year"
16 @{ @"title" : @"The Conjuring", @"year" : @(
17 @{ @"title" : @"Side Effects", @"year" : @(2
18 @{ @"title" : @"The Attack", @"year" : @(201
19 @{ @"title" : @"The Hobbit", @"year" : @(201
20 @{ @"title" : @"We Are What We Are", @"year"
21 @{ @"title" : @"Something in the Air", @"year"
22 ];
23 }

Step 2: Implement the UITableViewDataSource Protocol

The implementation of the UITableViewDataSource protocol is very easy. We only


need to implement numberOfSectionsInTableView:,
, and tableView:cellForRowAtIndexPath:.
tableView:numberOfRowsInSection:
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
01 - (NSInteger)numberOfSectionsInTableView:(
UITableView *)tableView {
02 return self.dataSource ? 1 : 0;
03 }
04
05 - (NSInteger)tableView:(
UITableView *)tableView numberOfRowsInSection
06 return self.dataSource ? self.dataSource.count : 0;
07 }
08
09 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath
10 TPSButtonCell *cell = (TPSButtonCell *)[tableView dequeueReusableCellWithIdentif
11
12 // Fetch Item
13 NSDictionary *item = [self.dataSource objectAtIndex:indexPath.row
14
15 // Configure Table View Cell
16 [cell.titleLabel setText:[NSString stringWithFormat:@"%@ (%@)", item[
17 [cell.actionButton addTarget:self action:@selector(didTapButton:)
18
19 return cell;
20 }

In tableView:cellForRowAtIndexPath:, we use the same identifier that we set in


the main storyboard, CellIdentifier, which we declared earlier in the tutorial. We
cast the cell to an instance of TPSButtonCell, fetch the corresponding item from
the data source, and update the cell's title label. We also add a target and action
for the UIControlEventTouchUpInsideevent of the button.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Don't forget to add an import statement for the TPSButtonCell class at the top of
TPSViewController.m.

1 #import "TPSButtonCell.h"

To prevent the application from crashing when a button is tapped, implement


didTapButton: as shown below.

1 - (void)didTapButton:(id)sender {
2 NSLog(@"%s", __PRETTY_FUNCTION__);
3 }

Build the project and run it in the iOS Simulator to see what we've got so far. You
should see a list of movies and tapping the button on the right logs a message to
the Xcode console. Great. It's time for the meat of the tutorial.

4. How Not To Do It
When the user taps the button on the right, it will send a message of
didTapButton: to the view controller. You almost always need to know the index
path of the table view cell that the button is in. But how do you get that index path?
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
As I mentioned, there are three approaches you can take. Let's first look at how not
to do it.

Take a look at the implementation of didTapButton: and try to find out what's
wrong with it. Do you spot the danger? Let me help you. Run the application first on
iOS 7 and then on iOS 6. Take a look at what Xcode outputs to the console.

01 - (void)didTapButton:(id)sender {
02 // Find Table View Cell
03 UITableViewCell *cell = (UITableViewCell *)[[[sender superview] superview
04
05 // Infer Index Path
06 NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
07
08 // Fetch Item
09 NSDictionary *item = [self.dataSource objectAtIndex:indexPath.row
10
11 // Log to Console
12 NSLog(@"%@", item[@"title"]);
13 }

The problem with this approach is that it's error prone. On iOS 7, this approach
works just fine. On iOS 6, however, it doesn't work. To make it work on iOS 6, you'd
have to implement the method as shown below. The view hierarchy of a number of
commons UIView subclasses, such as UITableView , has changed in iOS 7 and
the result is that the above approach doesn't produce a consistent result.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
01 - (void)didTapButton:(id)sender {
02 // Find Table View Cell
03 UITableViewCell *cell = (UITableViewCell *)[[sender superview] superview
04
05 // Infer Index Path
06 NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
07
08 // Fetch Item
09 NSDictionary *item = [self.dataSource objectAtIndex:indexPath.row
10
11 // Log to Console
12 NSLog(@"%@", item[@"title"]);
13 }

Can't we just check if the device is running iOS 7? That's a very good idea.
However, what will you do when iOS 8 changes the internal view hierarchy of
UITableView yet again? Are you going to patch your application every time a major
release of iOS is introduced? And what about all those users that don't upgrade to
the latest (patched) version of your application? I hope it's clear that we need a
better solution.

5. A Better Solution
A better approach is to infer the cell's index path in the table view based on the
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
position of the sender , the UIButton instance, in the table view. We use
convertPoint:toView: to accomplish this. This method converts the button's
center from the button's coordinate system to the table view's coordinate system. It
then becomes very easy. We call indexPathForRowAtPoint:on the table view and
pass pointInSuperview to it. This gives us an index path that we can use to fetch
the correct item from the data source.

01 - (void)didTapButton:(id)sender {
02 // Cast Sender to UIButton
03 UIButton *button = (UIButton *)sender;
04
05 // Find Point in Superview
06 CGPoint pointInSuperview = [button
.superview convertPoint:button.center
07
08 // Infer Index Path
09 NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint
:pointInSuperview]
10
11 // Fetch Item
12 NSDictionary *item = [self.dataSource objectAtIndex:indexPath.row
13
14 // Log to Console
15 NSLog(@"%@", item[@"title"]);
16 }

This approach may seem cumbersome, but it actually isn't. It's an approach that
isn't affected by changes in the view hierarchy of UITableView and it can be used
in many scenarios, including in collection views.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Advertisement

6. The Elegant Solution


There is one more solution to solve the problem and it requires a bit more work.
The result, however, is a display of modern Objective-C. Start by revisiting the
header file of the TPSButtonCell and declare a public method named
setDidTapButtonBlock: that accepts a block.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
01 #import <UIKit/UIKit.h>
02
03 @interface TPSButtonCell : UITableViewCell
04
05 @property (weak, nonatomic) IBOutlet UILabel *titleLabel;
06 @property (weak, nonatomic) IBOutlet UIButton *actionButton;
07
08 - (void)setDidTapButtonBlock:(
void (^)(id sender))didTapButtonBlock;
09
10 @end

In the implementation file of TPSButtonCell create a private property named


didTapButtonBlock as shown below. Note that the property attributed is set to
copy , because blocks need to be copied to keep track of their captured state
outside of the original scope.

1 #import "TPSButtonCell.h"
2
3 @interface TPSButtonCell ()
4
5 @property (copy, nonatomic) void (^didTapButtonBlock)(
id sender);
6
7 @end

Instead of adding a target and action for the UIControlEventTouchUpInsideevent


in the view controller's tableView:cellForRowAtIndexPath:, we add a target and
action in awakeFromNib in the TPSButtonCell class itself.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
1 - (void)awakeFromNib {
2 [super awakeFromNib];
3
4 [self.actionButton addTarget:self action:@selector(didTapButton:)
5 }

The implementation of didTapButton: is trivial.

1 - (void)didTapButton:(id)sender {
2 if (self.didTapButtonBlock) {
3 self.didTapButtonBlock(sender);
4 }
5 }

This may seem like a lot of work for a simple button, but hold you horses until we've
refactored tableView:cellForRowAtIndexPath:in the TPSViewController class.
Instead of adding a target and action to the cell's button, we set the cell's
didTapButtonBlock. Getting a reference to the corresponding item of the data
source becomes very, very easy. This solution is by far the most elegant solution to
this problem.

01 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath


02 TPSButtonCell *cell = (TPSButtonCell *)[tableView dequeueReusableCellWithIdentif
03
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
03
04 // Fetch Item
05 NSDictionary *item = [self.dataSource objectAtIndex:indexPath.row
06
07 // Configure Table View Cell
08 [cell.titleLabel setText:[NSString stringWithFormat:@"%@ (%@)", item[
09
10 [cell setDidTapButtonBlock:^(id sender) {
11 NSLog(@"%@", item[@"title"]);
12 }];
13
14 return cell;
15 }

Conclusion
Even though the concept of blocks has been around for decades, Cocoa
developers have had to wait until 2011. Blocks can make complex problems easier
to solve and they make complex code simpler. Since the introduction of blocks,
Apple has started making extensive use of them in their own APIs so I encourage
you to follow Apple's lead by taking advantage of blocks in your own projects.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Advertisement

Difficulty: Suggested Tuts+ Course


Beginner
Length:
Short
Categories:

iOS SDK Mobile Development

Translations Available:

Tuts+ tutorials are translated by our community


members. If you'd like to translate this post into
another language, let us know! The Swift Programming Language $15
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Download Attachment
Related Tutorials

Core Data from Scratch: More


NSFetchedResultsController
About Bart Jacobs
Code
Bart Jacobs runs Code Foundry, a mobile
and web development company based in
Belgium and writes about iOS Core Data from Scratch:
development on his blog. Bart is also the mobile NSFetchedResultsController
editor of Tuts+. Code

Getting Started with UIKit Dynamics


Code

Jobs

PHP Coder with Magento Knowledge


at Yoginet Web Solutions in New Delhi,
Delhi, India

Advertisement

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Advertisement
WordPress Site Migration
in Fort Lauderdale, FL, USA

Envato Market Item

9 Comments Mobiletuts+

Sort by Best

Join the discussion

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Slm 8 months ago
Step 2: Implement the UITableViewDataSource Protocol

where do i implement this step?


1 Reply Share

StanislavK 10 months ago


Good read. Just in case you have an issue when adding UILabel and UIButton into the cell's content view
creating this in Xcode 5 see :
http://stackoverflow.com/quest....
1 Reply Share

Bart Jacobs Mod > StanislavK 10 months ago


It does indeed appear to be a bug in Xcode 5. Thanks for linking to a possible solution.
Reply Share

jet a year ago


Great tutorial
1 Reply Share

Bart Jacobs Mod > jet 10 months ago


I'm glad you liked it, Jet.
Reply Share

Brady 2 months ago


I try this but the cell not call dealloc method, I thint it causes retain cycle

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Reply Share

mincom 3 months ago


Thanks very much, this great tutorial.
Reply Share

richarddas 10 months ago


Instead of creating a new block and adding the target to the button, why not set the cell's delegate and imp
button inside the cell to call that? That would better encapsulate the logic.
Reply Share

Bart Jacobs Mod > richarddas 10 months ago


A table view cell doesn't have a delegate by default so you'd have to declare and implement it first.
situations, blocks are a great replacement for the delegate pattern. It allows for better code organiz
Reply Share

Subscribe d Add Disqus to your site Privacy

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Advertisement

18,868 Tutorials 458 Video Courses


Teaching skills to millions worldwide.

Follow Us Email Newsletters

Get Tuts+ updates, news, surveys &


offers.

Email Address
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Help and Support Email Address

FAQ
Subscribe
Terms of Use
Contact Support Privacy Policy
About Tuts+
Advertise
Teach at Tuts+

Custom digital services like logo design, WordPress installation, video


production and more.
Check out Envato Studio

Build anything from social networks to file upload systems. Build faster
with pre-coded PHP scripts.
Browse PHP on CodeCanyon

2014 Envato Pty Ltd. Trademarks and brands are the property of their respective
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
owners.

open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com