How do I display a placeholder image when my UITableView has no data yet?
I have an application that, when loaded, displays a UITableView with user data in it.
However, when the user first loads the app (before they created any data), I would like to display a background image instead of an empty table (with an arrow pointing to "add record", a navigation button). After the user adds their first record, a table is displayed instead. I've seen a lot of applications do this - the only example I can currently think of is Cha-Chhing before you have any budgets / etc. I can't get my life to work on how to do it.
I originally added a navigation controller to the main xib window, whose rootViewController was a custom view / xib controller. This rootViewController contained a background image with a hidden table view above it and a custom tableview control that controlled the table view. This seemed to work pretty well and if there is data, it will be loaded and displayed in the table. However, if I were to loop through the data behind the scenes, the application would fail with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'*** -[UITextEffectsWindow tableView:cellForRowAtIndexPath:]: unrecognized selector sent to instance 0xd2d130'
I don't know what the UITextEffectsWindow is, or why it was trying to manipulate my tableview. I am guessing that something might be wrongly connected in my view hierarchy ...
If there is a much simpler or easier way to do this, I would be very grateful if anyone could explain it. How do you do it?
Thanks in advance.
So, to solve this, I did it as described in the comments above:
I created a regular UIViewController subclass containing a UIImageView and a UITableView. The viewController conforms to the UITableViewDelegate and UITableViewDatasource protocols and monitors the table view. The viewController class simply shows or hides the image depending on whether data is available.
I was wrong before trying to control both views with a UITableViewController. A UITableViewController must have a tableView as its view, whereas with this solution, a viewController can contain both an image and a tableView and implement the necessary protocols for manipulating the tableView.
Thanks for the help!
Here's one solution I'm happy with.
I first created a transparent view that was the same size as my TableView. I am adding this view as a child of the TableView when the TableView datasource has no data. I completely remove the view when data is available as transparency can affect the smoothness of scrolling animations with TableViews.
I just added a transparent label to this view that says something about the impact of "No data". Adding a little shading to this label really helped solidify the concept that the label was "floating" on top of an empty TableView.
I like your method of using the image. Done correctly, it might save you some work if you don't need to localize the string as I am currently doing.
a source to share
To achieve this by subclassing UITableViewController as the only view (inside a UINavigationController according to Apple's template), I used the following approach:
-
Create a UIView for the size of my tableView in XIB which contains your UITableViewController and tableView.
-
Add an ImageView set with my sample placeholder to the UIView.
-
Connect the UIView as an IBOutlet (in the code example below, I named it emptyTableView)
-
When it's time to show the placeholder from the UITableViewController subclass:
[self.tableView addSubView: emptyTableView]; [self.tableView setScrollEnabled: FALSE];
Disabling scrolling is necessary otherwise the user can move the placeholder image up and down. Just remember to enable it as soon as the user adds the item.
-
To remove an image view
[emptyTableView removeFromSuperview];
a source to share
For this, I am using the following controller instead UITableViewController
. It will automatically place the view on top of the table when it is empty and delete it when it is full.
Just call [self reloadData]
instead [self.tableView reloadData]
so it can check if the table has become empty.
In your subclass, implement a function makeEmptyOverlayView
that will create a view displayed above an empty table.
@interface MyTableViewController : UITableViewController
{
BOOL hasAppeared;
BOOL scrollWasEnabled;
UIView *emptyOverlay;
}
- (void) reloadData;
- (void) checkEmpty;
@end
@implementation MyTableViewController
- (void)viewWillAppear:(BOOL)animated
{
[self reloadData];
[super viewWillAppear: animated];
}
- (void)viewDidAppear:(BOOL)animated
{
hasAppeared = YES;
[super viewDidAppear: animated];
[self checkEmpty];
}
- (void)viewDidUnload
{
if (emptyOverlay)
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
- (void) reloadData
{
[self.tableView reloadData];
if (hasAppeared &&
[self respondsToSelector: @selector(makeEmptyOverlayView)])
[self checkEmpty];
}
- (void) checkEmpty
{
BOOL isEmpty(YES);
id<UITableViewDataSource> src(self.tableView.dataSource);
NSInteger sections(1);
if ([src respondsToSelector: @selector(numberOfSectionsInTableView:)])
sections = [src numberOfSectionsInTableView: self.tableView];
for (int i(0); i<sections; ++i)
{
NSInteger rows([src tableView: self.tableView numberOfRowsInSection: i]);
if (rows)
isEmpty = NO;
}
if (!isEmpty != !emptyOverlay)
{
if (isEmpty)
{
scrollWasEnabled = self.tableView.scrollEnabled;
self.tableView.scrollEnabled = NO;
emptyOverlay = [self makeEmptyOverlayView];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
else
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
else if (isEmpty)
{
// Make sure it is still above all siblings.
[emptyOverlay retain];
[emptyOverlay removeFromSuperview];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
}
@end
a source to share