Introducing Trelloid

I tried to love Sprint.ly, I really did, but the biggest problem I had with it was the huge amount of space it uses displaying one item. During my trial period, I frequently created a Story, only to discover one similar to it in my Backlog, and no amount of filtering by tag, or other search criteria gave me the confidence that I was in control.

Thus, I decided to try Trello. This is a free Kanban-style web tool which I have been using for various projects ever since it launched, and it has a rather good iPhone companion application. You can create a board, fill it with lists and each list can have a number of cards:

Alisto | Trello

For reference purposes, each Card in a Trello board has a number which is unique to that board. However, to get at that number you have to view the card’s details. This makes it difficult to easily reference a card in other systems. So, inspired by the Trello Scrum Chrome Extension (which adds storypoints to Trello boards), I wrote my own extension Trelloid, which prepends the card number into the title:

Trello Board with Card Number.jpg

It works as follows:

  • If a Board title has a “#” at the end, then the board will have card numbers prepended, otherwise it will be left alone. This allows you to show card numbers only when necessary.
  • If a Card already has a number (e.g. #1052) at the beginning of its title, then it will be left alone. This allows titles that reference some external system to be unaffected.

It’s my intention to publish this on the Chrome Web Store, but in the meantime, you can get it here.

Remembering a List

Let’s assume for one moment the possibility that Alisto crashes, is terminated by iOS or indeed by the user. Wouldn’t it be nice to remember the most recently viewed List and pop that up on the screen when the application next launches?

Because a user navigates from a list of Lists, to an specific list, in this situation the application needs to push an additional view onto the stack when it launches. But, in the interests of tidyiness, I don’t want the main application delegate to do this work. This is how I’ve done it:

I want to store in NSUserDefaults a reference to the list currently on display, and whenever that changes the reference is updated. NSUserDefaults can only store simple objects, so storing an NSManagedObject is out of the question. However, each NSManagedObject does have a unique URL obtained via its URIRepresentation method.

The first step is to have the controller which displays a list to be record the list on display. This is achieved by updating the viewDidLoad method. As you’ll recall from previous posts, item is the actual list.

NSURL *url = [[item objectID] URIRepresentation];
[[NSUserDefaults standardUserDefaults] setURL:url forKey:[NSString stringWithFormat:@"%d", AlistoPrefCurrentListItemURI]];

The ListsViewController which is responsible for handling the list of lists can now show a list if it’s tapped on, or if commanded to on launch. It’s therefore appropriate to factor out the code which pushes the display of an individual list:

- (void)viewList:(ListItem *)item {
    if (item) {
        ListViewController *listViewController = [[ListViewController alloc] init];    
        [listViewController setItem:item];

        [[self navigationController] pushViewController:listViewController animated:YES];
    }
}

This is invoked either via the tableView:didSelectRowAtIndexPath method (as per usual) or (now) via the viewWillAppear method, which I updated as follows:

- (void)viewWillAppear:(BOOL)animated {    
    [super viewWillAppear:animated];
    [[self tableView] reloadData];

    if ([self pushedListItem]) {
        [self viewList:pushedListItem];
        pushedListItem = nil;
    }
}

pushListItem is a property which references a list object, it is intended to be set by the application delegate to indicate that a list is to be shown immediately. In the above code, as soon as the controller view appears, the controller examines the reference and pushes the list on display via the aforementioned viewList method.

The application delegate’s didFinishLaunchingWithOptions then has the following code which examines the user setting, converts the setting into an actual object, then tells the (just created) controller the list object it should show.

ListsViewController *listsViewController = [[ListsViewController alloc] init];
    
NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:[NSString stringWithFormat:@"%d", AlistoPrefCurrentListItemURI]];
if (url) {
    NSError *anyError = nil;
    NSManagedObjectID *objectID = [[CACoreData sharedPersistentStoreCoordinator] managedObjectIDForURIRepresentation:url];
    ListItem *item = (ListItem *)[[CACoreData sharedManagedObjectContext] existingObjectWithID:objectID error:&anyError];
    if (item) {
        [listsViewController setPushedListItem:item];
    }
}

How Did I Do That? (Part Two)

Continuing from yesterday..

Multiple XIBs

In my previous post, I mentioned that my application gives tasks a unique number so that I can easily reference them elsewhere, but that it might not be desirable to show this number at all times. In this situation, one can use a (subclass of) UITableViewCell and then when the cell is instantiated add a subview for the task number and move other fields accordingly to compensate. But I didn’t fancy programmatically adjusting such a view – the idea smelled bad. Instead I decided to create two XIBs and then choose between them when instantiating the cell. This is done as follows:

Create one XIB as normal, and a custom UITableViewCell subclass. In the XIB set the “Custom Class” field to the name of that subclass, and then create / synthesize IBOutlets and IBActions as normal. This is all standard stuff. In my app, I called the subclass TaskItemCell.

Duplicate the XIB, and add additional objects, adjusting (or removing) the existing ones as required. Create / synthesize IBOutlets and IBActions corresponding to these new objects. For my app, I named this XIB TaskItemCellWithID.xib.

At this stage, I had two XIBs which have the same custom class. Because this class has a set of properties that are common to both XIBs either one can be used. Here’s the method I use to populate the content of a cell, according to a specific task.

You’ll see that I don’t test which XIB is being used, because the class doesn’t care. In the UITableViewController‘s cellForRowAtIndexPath method, I merely check the user’s settings to determine which XIB to load:


Customising the Toolbar

View controllers that are managed by a navigation controller can set toolbar items for the view controller before it’s displayed or after it becomes visible. I wanted to have the ability to tab a “trash” icon and for completed tasks to be permanently deleted, however, I only wanted this icon to be displayed when viewing completed tasks. This helps ensure it’s a conscious action.

The method to establish the toolbar in my view controller draws the segmented control and the edit button (which is employed to allow tasks to be manually re-organised or individually deleted). Then, when the segmented control is changed, the corresponding method that responds to this change decides whether to insert the “trash” button.

UIBarItems include a tag property which allows applications to easily identify bar item objects. I use this to determine if the first item in the toolbar is the “trash” bar item or not, and add or remove it from the toolbar accordingly. Because I don’t care about any other tags in this toolbar, I’ve arbitrarily given it an NSInteger value of 10.


Action Sheets

Now that I had a settings panel and a trash button, I could implement deletion of completed tasks, and depending on a setting ask the user for confirmation.

The UIActionSheet is a view that (for iPhone applications) usually slides up from the bottom of the screen. After it is created, it can then be displayed relative to a toolbar or tab bar. For this application all I needed to do was ensure that the sheet was displayed in accordance with the user’s preference, and then delete the tasks.

Because I hooked up NSFetchedResultsController to respond to deletions, when the tasks are deleted the display updates automatically – there’s no need to reload the table.

Here are the three methods which handle deletion of completed tasks. I explicitly extracted the code that invokes the deletion just in case there are other actions to be taken in future (e.g. animating the trash icon, as per the Mail application).