Following on from the previous post on creating a table view with a “Load More…” option I wanted to further enhance my feed reader app with a couple of extra features. I will add a new property to the model to allow a post to be marked as read and modify the view controller to change the user presentation for read and unread posts. Also I will look at a common table operation - swiping to delete a row from the table.
Marking Posts as Read
One of the nice things about the MVC architecture is that it makes easy to see how to add new features to an application. To allow my pretend feed reader to mark posts as read when the user has viewed them I know that I need to add a read flag to my model and then update the view controller to make use of that flag.
Modifying the Post
model class is trivial. Our new class looks like this:
@interface Post : NSObject {
BOOL isRead;
NSString *postTitle;
NSString *postDescr;
NSDate *pubDate;
}
The boolean flag will default to false when we create a new post object indicating an unread post. In our user interface we will show the post title in bold when it is unread and in a normal font when it is read. The place to make that change is in the tableView:cellForRowAtIndexPath:
method in FeedViewController.m
. I will not show the whole method again (check yesterdays post). The relevant lines that set the post title are as follows:
Post *currentPost = [posts objectAtIndex:row];
cell.textLabel.text = [currentPost postTitle];
cell.textLabel.font = [UIFont systemFontOfSize:14];
Given the post object it is easy to check the isRead
flag and set the font weight accordingly:
if (currentPost.isRead) {
cell.textLabel.font = [UIFont systemFontOfSize:14];
} else {
cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
}
Now when we push the PostViewController
onto the navigation stack in the tableView:didSelectRowAtIndexPath:
to view a post we can also update the model object for the post to mark it as read:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSUInteger count = [posts count];
if (row == count) {
...
} else {
Post *currentPost = [posts objectAtIndex:row];
currentPost.isRead = YES;
PostViewController *postController = [[PostViewController alloc]
initWithNibName:@"PostView"
bundle:nil];
postController.post = currentPost;
[[self navigationController] pushViewController:postController
animated:YES];
[postController release];
}
}
To ensure that when we return from the detailed post view we update the table to show the new post status we also need to implement the viewWillAppear
method to allow us to reload the table:
- (void)viewWillAppear:(BOOL)animated {
[self.tableView reloadData];
}
One other nice feature that we can quickly add is to update the view title to show the total number of unread posts. First a method to count the number of unread posts:
- (NSUInteger)countUnread {
NSUInteger count = 0;
for (Post *post in self.posts) {
if (post.isRead == NO) {
count++;
}
}
return count;
}
Since we will be performing the title update in several places I will also add a method to set the view title:
- (void)updateViewTitle {
NSUInteger count = [self countUnreadPosts];
if (count) {
self.title = [NSString stringWithFormat:@"Posts (%u unread)", count];
} else {
self.title = @"Posts";
}
}
Then we need to update the viewDidLoad
method to set the initial view title and viewWillAppear
to refresh the title each time we read a post:
- (void)viewDidLoad {
...
[self updateViewTitle];
}
- (void)viewWillAppear:(BOOL)animated {
[self updateViewTitle];
[self.tableView reloadData];
}
We also need to update the view when the user touches the Load More… row. We only need to update the title when new rows are actually added:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSUInteger count = [posts count];
if (row == count) {
NSArray *newPosts = [feed newPosts];
NSUInteger newCount = [newPosts count];
if (newCount) {
...
...
[self updateViewTitle];
}
} else {
...
}
}
Deleting Rows from the Table
One other common feature of iPhone table views is to allow the user to delete a row with a swipe gesture. In this case we will use the swipe gesture to remove the post from the view. A better approach might be to actually just mark the post as read and provide the user with the ability to choose between viewing all posts or only unread posts. For now we will stick with just removing the post from the feed to illustrate the interaction with the table view.
To support the delete action for a UITableView
we need to implement three methods. The first method determines for which rows the delete gesture is enabled by setting the editing style. We want to allow all rows to be deleted except the last one (which has our Load More… message):
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSUInteger count = [posts count];
if (row < count) {
return UITableViewCellEditingStyleDelete;
} else {
return UITableViewCellEditingStyleNone;
}
}
So for alls rows except the last row we return UITableViewCellEditingStyleDelete
to allow the swipe to delete gesture. When a user swipes a row and taps the delete button we get a call to a method to allow the data source to update which we can use to remove the post from the array holding all of the posts:
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSUInteger count = [posts count];
if (row < count) {
[posts removeObjectAtIndex:row];
}
}
Finally we can use a call to another tableView delegate method that indicates the editing of the table has ended to force a reload and update the view. As well as updating the table we can also ensure that the view title is updated:
- (void)tableView:(UITableView *)tableView
didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
[self updateViewTitle];
[tableView reloadData];
}
Wrapping Up
I guess that will do for today. I am still experimenting with developing an example app post by post so I am not sure where this will lead but I hope it proves useful to somebody. The next area that I really want to get into is actually retrieving an RSS feed over the network and parsing it to produce some real post data.