Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing UITableView for iOS Data Display

Tech May 12 3

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

  • separatorColor and separatorStyle: Control the appearance of cell dividers.
  • tableHeaderView and tableFooterView: 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.

  1. Message Bubbles: Use UIButton with stretchable background images and contentEdgeInsets to create speech bubbles.
  2. Keyboard Handling: Observe UIKeyboardWillChangeFrameNotification to 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;
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.