wannabegeek

Icon

Things I find interesting – generally programming

UITableView Place-holder image

The question of how to add a place holder view to an empty UITableView seems to be very common. There also seem to be many different solutions.
These place-holder views can be seem in a range of applications including the iOS AppStore when it doesn’t have a data connection.

The Problem

Currently, as of iOS 5.0, there is no build in way of presenting a custom view when a table view is empty.
The way I envisaged my solution working was as a drop in replacement for UITableView (a subclass of). This could then call the appropriate data source method to retrieve the UIView to be overlayed.

The Solution

My first step was to extend the UITableViewDataSource protocol to add our own method for obtaining a UIView to be displayed as the placeholder.

@protocol PlaceholderTableViewDelegate <UITableViewDelegate>
@optional
- (UIView *)tableView:(UITableView *)tableView placeHolderViewForFrame:(CGRect)frame;
@end

The next step was to determine when the table view has been updated in order to re-evaluate whether the placeholder view should be displayed. So, I created a subclass of UITableView overriding some of the methods involved in updating the displayed state of the table view. Within these over-ridden methods we call the super class’ method but also call a method which checks with the data source for the contents of the table view, and displays the placeholder if appropriate.

Below is are the overridden methods (_batchUpdateInProgress is a member of the subclass)

- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
	if (_batchUpdateInProgress == NO) {
		[self displayPlaceholderIfNeeded];
	}
	[super insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];
}
 
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
	[super deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];
	if (_batchUpdateInProgress == NO) {
		[self displayPlaceholderIfNeeded];
	}
}
 
- (void)beginUpdates {
	_batchUpdateInProgress = YES;
	[super beginUpdates];
	[self displayPlaceholderIfNeeded];
}
 
- (void)endUpdates {
	[super endUpdates];
	_batchUpdateInProgress = NO;
	[self displayPlaceholderIfNeeded];
}
 
- (void)setEditing:(BOOL)editing animated:(BOOL)animate {
	[super setEditing:editing animated:animate];
	[self displayPlaceholderIfNeeded];
}
 
- (void)setEditing:(BOOL)editing {
	[super setEditing:editing];
	[self displayPlaceholderIfNeeded];
}
 
- (void)didMoveToSuperview {
	[self displayPlaceholderIfNeeded];
}

To determine if the placeholder needs to be displayed, we first check to see if the table view is being edited (if it is, we won’t display the placeholder), and then query the table views data source methods to determine if there is any data being displayed.
If we should be showings the place holder, we call the extending protocol method on the data source, to get the UIView to be displayed, and animate it’s appearance. If the placeholder shouldn’t be displayed, and it currently is, we will animate it’s disappearance.

- (void)displayPlaceholderIfNeeded {
	BOOL needPlaceHolder = YES;
	if (!self.editing) {
		NSInteger sections = [super.dataSource numberOfSectionsInTableView:self];
		for (NSInteger i = 0; i < sections && needPlaceHolder == YES; i++) {
			if ([super.dataSource tableView:self numberOfRowsInSection:i] != 0) {
				needPlaceHolder = NO;
			}
		}
	} else {
		needPlaceHolder = NO;
	}
 
	if ([self.delegate respondsToSelector:@selector(tableView:placeHolderViewForFrame:)]) {
		if (needPlaceHolder) {
			if (_placeHolderVisible == NO) {
				_placeHolderView = [(NSObject<PlaceholderTableViewDelegate> *)self.delegate tableView:self placeHolderViewForFrame:self.frame];
 
				if (_initialised) {
					_placeHolderView.alpha = 0.0f;
					[self.superview addSubview:_placeHolderView];
					[self.superview bringSubviewToFront:_placeHolderView];
					self.scrollEnabled = NO;
					[UIView animateWithDuration:0.5f delay:0.5f options:UIViewAnimationOptionCurveEaseIn animations:^{
						_placeHolderView.alpha = 1.0f;
					} completion:^(BOOL completed){
					}];
				} else {
					_placeHolderView.alpha = 1.0f;
					[self.superview addSubview:_placeHolderView];
					[self.superview bringSubviewToFront:_placeHolderView];
					self.scrollEnabled = NO;
				}
				_placeHolderVisible = YES;
			}
		} else {
			if (_placeHolderVisible == YES && _placeHolderView != nil) {
				[self bringSubviewToFront:_placeHolderView];
				if (_currentlyAnimating == NO) {
					_currentlyAnimating = YES;
					_placeHolderVisible = NO;
					[UIView animateWithDuration:0.5f delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{
						_placeHolderView.alpha = 0.0f;
					} completion:^(BOOL completed){
						if (completed) {
							[_placeHolderView removeFromSuperview];
						}
						self.scrollEnabled = YES;
						_currentlyAnimating = NO;
					}];
				}
			}
		}
	}
	_initialised = YES;
}

Conclusion

For fully working example project see this repository on GitHub

Category: Cocoa, iOS, iOS5, iPad, iPhone, Programming, UIKit

Tagged:

Leave a Reply