How To Make A Swipeable Table View Cell With Actions – Without Going Nuts With Scroll Views
So you want to make a swipeable table view cell like in Mail.app? This tutorial shows you how without getting bogged down in nested scroll views. By Ellen Shapiro.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make A Swipeable Table View Cell With Actions – Without Going Nuts With Scroll Views
60 mins
- Getting Started
- Digging into the View Hierarchy
- A List Of Ingredients for a Swipeable Table View Cell
- Creating the Custom Cell
- Adding a delegate
- Adding actions to the buttons
- Adding the Top Views And The Swipe Action
- Adding the data
- Gesture recognisers - go!
- Moving those constraints
- Snap!
- Playing Nicer With The Table View
- Where To Go From Here
A List Of Ingredients for a Swipeable Table View Cell
So what does this mean for you? Well, at this point you have a list of obvious ingredients for cooking up a UITableViewCell
subclass with your own custom buttons.
Going in reverse z-order with the items at the “bottom” of the view stack first, you have the following:
- The
contentView
as your base view, since it's required that you add subviews to this view. - Any
UIButtons
you want to display after the user swipes. - A container view above the buttons to hold all of your content.
- Either a
UIScrollView
to hold your container view, like Apple use, or you could use aUIPanGestureRecognizer
. This can also handle the swipes to reveal/hide the buttons. You'll take the latter approach in your project. - Finally, the views with your actual content.
There’s one ingredient that may not be as obvious: you have to ensure the existing UIPanGestureRecognizer
— which lets you swipe to show the delete button — is disabled. Otherwise that gesture recognizer will collide with the custom one you’re adding to your project.
The good news is that disabling the default swipe is pretty simple.
Open MasterViewController.m. Modify tableView:canEditRowAtIndexPath:
to always return NO
as follows:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
Build and run your application; swipe one of the items and you’ll find that you can no longer swipe to delete.
To keep it simple, you'll walk through this example with two buttons, but these same techniques will work with one button, or more than two buttons — though be warned you may need to add a few tweaks not covered in this article if you add so many buttons that you’d have to slide the entire cell out of view to see them all.
Creating the Custom Cell
You can see from the basic list of views and gesture recognizers that there’s an awful lot going on in the table view cell. You’ll want to create your own custom UITableViewCell
subclass to keep all the logic in one place.
Go to File\New\ File… and select iOS\Cocoa Touch\Objective-C class. Name the new class SwipeableCell and make it a subclass of UITableViewCell, like so:
Set up the following class extension and IBOutlets
in SwipeableCell.m, just below the #import
statement and above the @implementation
statement:
@interface SwipeableCell()
@property (nonatomic, weak) IBOutlet UIButton *button1;
@property (nonatomic, weak) IBOutlet UIButton *button2;
@property (nonatomic, weak) IBOutlet UIView *myContentView;
@property (nonatomic, weak) IBOutlet UILabel *myTextLabel;
@end
Next, go into your storyboard and select the UITableViewCell prototype, as shown below:
Open the Identity Inspector, then change the Custom Class to SwipeableCell, like so:
The name of the UITableViewCell prototype now appears as “Swipeable Cell” in the Document Outline on the left. Right-click on the item that says Swipeable Cell - Cell, you'll see the list of IBOutlets you set up above:
First, you’ll need to change a couple things in the Attributes Inspector to customize the view. Set the Style to Custom, the Selection to None, and the Accessory to None, as shown in the screenshot below:
Next, drag two Buttons into the cell's content view. Set each button's background color in the View section of the Attributes Inspector to some distinctive color and set each button's text color to something legible so you can see the buttons clearly.
Pin the first button to the right side, top, and bottom of the contentView. Pin the second button to the left edge of the first button, and to the top and bottom of the contentView. When you’re done, the cell should look something like this, although your colors may differ:
Next, hook up each of your buttons to the appropriate outlets. Right-click the swipeable cell to open up its outlets, then drag from the button1 outlet to the right button, and button2 to the left button, as such:
You need to create a method to handle taps on each of these buttons.
Open SwipeableCell.m and add the following method:
- (IBAction)buttonClicked:(id)sender {
if (sender == self.button1) {
NSLog(@"Clicked button 1!");
} else if (sender == self.button2) {
NSLog(@"Clicked button 2!");
} else {
NSLog(@"Clicked unknown button!");
}
}
This handles button taps from either of the buttons and logs it to the console so you can confirm which button was tapped.
Open the Storyboard again, and hook up the action for both buttons to this new method. Right-click the Swipeable Cell - Cell to bring up its list of outlets and actions. Drag from the buttonClicked: action to your button, like so:
Select Touch Up Inside from the list of events, as shown below:
Repeat the above steps for the second button. Now tapping on either button calls buttonClicked:
.
Since you're customizing the cell's content view, you can't rely on the built-in text label. Instead, you’ll need to add your own property and method to set the cell's text.
Open SwipeableCell.h and add the following property:
@property (nonatomic, strong) NSString *itemText;
You’ll be doing more with the itemText
property later, but for now, this is all you need.
Open MasterViewController.m and add the following line to the top:
#import "SwipeableCell.h"
This ensures the class knows about your custom cell subclass.
Replace the contents of tableView:cellForRowAtIndexPath:
with the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
SwipeableCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
NSString *item = _objects[indexPath.row];
cell.itemText = item;
return cell;
}
It's now your new cell class being used instead of the standard UITableViewCell
.
Build and run your application; you’ll see something like the following:
Adding a delegate
Hooray — your buttons are there! If you tap on each button, you’ll see the appropriate log messages in your console. However, you don't want to have the cell itself take any direct action.
For instance, a cell can’t present another view controller or push directly onto the navigation stack. You’ll have to set up a delegate to pass the button tap event back to the view controller to handle that event.
Open SwipeableCell.h and add the following delegate protocol declaration above the @interface
statement:
@protocol SwipeableCellDelegate <NSObject>
- (void)buttonOneActionForItemText:(NSString *)itemText;
- (void)buttonTwoActionForItemText:(NSString *)itemText;
@end
Add the following delegate property to SwipeableCell.h, just below your property for itemText
:
@property (nonatomic, weak) id <SwipeableCellDelegate> delegate;
Update buttonClicked:
in SwipeableCell.m as shown below:
- (IBAction)buttonClicked:(id)sender {
if (sender == self.button1) {
[self.delegate buttonOneActionForItemText:self.itemText];
} else if (sender == self.button2) {
[self.delegate buttonTwoActionForItemText:self.itemText];
} else {
NSLog(@"Clicked unknown button!");
}
}
This updates the method to call the appropriate delegate methods instead of simply creating an entry in the log.
Now, open MasterViewController.m and add the following delegate methods to the implementation:
#pragma mark - SwipeableCellDelegate
- (void)buttonOneActionForItemText:(NSString *)itemText {
NSLog(@"In the delegate, Clicked button one for %@", itemText);
}
- (void)buttonTwoActionForItemText:(NSString *)itemText {
NSLog(@"In the delegate, Clicked button two for %@", itemText);
}
These methods will simply log to the console to ensure everything is passing through properly.
Next, add the following protocol conformance declaration to the class extension at the top of MasterViewController.m:
@interface MasterViewController () <SwipeableCellDelegate> {
NSMutableArray *_objects;
}
@end
This simply indicates that this class conforms to the SwipeableCellDelegate
protocol.
Finally, you need to set this view controller as the cell's delegate.
Add the following line to tableView:cellForRowAtIndexPath:
just before the final return statement:
cell.delegate = self;
Build and run your application; you’ll see the appropriate “in the delegate” messages firing off when you tap on the buttons.