Implementing UITableView for iOS Data Display
UITableView, a subclass of UIScrollView, is designed for presenting tabular data in a scrollable view. It provides excellent performence for handling large lists. There are two built-in styles: Plain, which displays items in a simple list, and Grouped, which visually separates items into distinct sections. By default, UITableView uses the Plain style.
Data Source and Delegate
To display data, a view controller must adopt the UITableViewDataSource protocol and impleemnt its core methods.
// Returns the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
// Returns the number of rows in a given section.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
// Creates and configures a cell for a given row.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Additional optional methods provide further customization:
// Provides titles for the section index bar on the right.
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
// Returns a title for a section's header.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
// Returns a title for a section's footer.
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
Responding to User Interaction
The UITableViewDelegate protocol handles cell selection and row height configuration.
// Called when a row is selected.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
// Called when a row is deselected.
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
Configuring Row Height
For optimal performance, set a uniform row height using the rowHeight property in viewDidLoad.
self.tableView.rowHeight = 60.0;
For variable row heights, implement the delegate method tableView:heightForRowAtIndexPath:. However, this approach can be less efficient as it's called for every row.
Key UITableView Properties
separatorColorandseparatorStyle: Control the appearance of cell dividers.tableHeaderViewandtableFooterView: Views placed above the first cell and below the last cell, often used for banners or "Load More" buttons.
Cell Configuration and Reuse
Standard UITableViewCell objects provide properties like imageView, textLabel, and detailTextLabel for basic layouts. For custom designs, create a subclass of UITableViewCell.
Cell reuse is critical for memory efficiency and smooth scrolling. The pattern involves registering a reuse identifier and dequeuing cells from the table view's reuse queue.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *reuseID = @"MyCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseID];
}
// Configure the cell with data...
return cell;
}
Implementing Custom Cells
For complex interfaces, creating a custom cell class is necessary. This involves designing a layout in a .xib file or programmatically, and creating a corresponding subclass of UITableViewCell.
Example: A custom cell for displaying a product.
ProductCell.h
#import <UIKit/UIKit.h>
@class ProductItem;
@interface ProductCell : UITableViewCell
@property (nonatomic, strong) ProductItem *product;
+ (instancetype)cellForTableView:(UITableView *)tableView;
@end
ProductCell.m
#import "ProductCell.h"
#import "ProductItem.h"
@interface ProductCell ()
@property (weak, nonatomic) IBOutlet UIImageView *productImageView;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *priceLabel;
@end
@implementation ProductCell
+ (instancetype)cellForTableView:(UITableView *)tableView {
static NSString *reuseID = @"ProductCell";
ProductCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
if (!cell) {
cell = [[[NSBundle mainBundle] loadNibNamed:@"ProductCell" owner:nil options:nil] firstObject];
}
return cell;
}
- (void)setProduct:(ProductItem *)product {
_product = product;
self.productImageView.image = [UIImage imageNamed:product.imageName];
self.nameLabel.text = product.title;
self.priceLabel.text = [NSString stringWithFormat:@"$%@", product.cost];
}
@end
The view controller's data source method then becomes:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ProductItem *item = self.productsArray[indexPath.row];
ProductCell *cell = [ProductCell cellForTableView:tableView];
cell.product = item; // The cell handles its own configuration
return cell;
}
Handling Dynamic Cell Heights
When cell content varies, pre-calculate the height for each row. A common pattern uses a "frame model" object that stores layout data (like frames for subviews and the total row height) computed from the data model.
// In a FrameModel class (e.g., PostFrame.m)
- (void)setPost:(Post *)post {
_post = post;
// ... Calculate frames for avatar, text, image views ...
// Calculate total rowHeight based on the lowest subview.
_rowHeight = CGRectGetMaxY(self.lastSubviewFrame) + PADDING;
}
// In the view controller's delegate method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
PostFrame *frameModel = self.frameModelsArray[indexPath.row];
return frameModel.rowHeight;
}
Advanced Example: Chat Interface
A messaging interface requires dynamic bubbles and response to keyboard events.
- Message Bubbles: Use
UIButtonwith stretchable background images andcontentEdgeInsetsto create speech bubbles. - Keyboard Handling: Observe
UIKeyboardWillChangeFrameNotificationto adjust the table view's position.
// In viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardFrameChanged:)
name:UIKeyboardWillChangeFrameNotification
object:nil];
- (void)keyboardFrameChanged:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat transformY = keyboardFrame.origin.y - self.view.frame.size.height;
[UIView animateWithDuration:0.25 animations:^{
self.view.transform = CGAffineTransformMakeTranslation(0, transformY);
}];
// Scroll to the last message
NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:self.messages.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
// Remember to remove the observer in dealloc
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Implementing Expandable Sections
A contacts list with collapsible sections requires managing a BOOL property (like isExpanded) in the section's data model. A custom header view (UITableViewHeaderFooterView) toggles this state.
// In the custom header view's action method
- (void)sectionHeaderTapped {
self.sectionDataModel.isExpanded = !self.sectionDataModel.isExpanded;
// Inform delegate (the view controller) to reload this specific section
if ([self.delegate respondsToSelector:@selector(sectionHeaderDidToggle:)]) {
[self.delegate sectionHeaderDidToggle:self.tag]; // tag holds section index
}
}
// In the view controller
- (void)sectionHeaderDidToggle:(NSInteger)sectionIndex {
NSIndexSet *sectionToReload = [NSIndexSet indexSetWithIndex:sectionIndex];
[self.tableView reloadSections:sectionToReload withRowAnimation:UITableViewRowAnimationFade];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
SectionData *data = self.sectionsArray[section];
return data.isExpanded ? data.items.count : 0;
}