Displaying an Attraction

Work and travel have been cutting into my software development schedule, but I’ve continued to make progress on Fort Collins for Kids. As I mentioned in my last post, I abandoned storyboards, and went back to .XIB files. The goal of my iteration 4 is to adjust the Editor application to store new fields for the attraction object. So I set out to do just that, dragging in objects from the object library to lay out my view.

Attraction Name… UITextField… check.
General Information… UITextView…check.
Address… oops.

I want to display all of the fields in a single scrollable view. I also want the General Information field to allow a large amount of text. In fact, I want it to fill most of the screen. The user would need to scroll the view to see additional fields. And… there’s no room on the view for additional fields. How do I create a view that can scroll?

I went back through the books that I’ve read, and found no good examples. UIScrollView was a possibility, but every sample I found scrolled around a large two-dimensional graphical canvas rather than a simple list of attributes. I finally asked a friend for some ideas, and he suggested using a UITableView. What? A UITableView? Wasn’t that what I was using back in Iteration 2? Indeed.

The basic concept is that each field is stored as a separate row. Not the simple row used in Iteration 2, but a custom cell. A UITableViewCell can itself contain whatever views you choose. So here, for example, is AKGeneralInformationCell, which holds a UITextView:

The heart of AKAttractionViewController is tableView:cellForRowAtIndexPath, and it logically looks like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = nil;

  //Attraction name
    if (AKAttractionFieldIndexName == indexPath.row) {
        //create and return a cell for attraction name

    //General Information
    } else if (AKAttractionFieldIndexGeneralInformation == indexPath.row){
        //create and return a cell for general information

    //Oops
    } else {
        NSAssert(YES, @"Bad row number for AKAttractionViewController");
    }

    return cell;
}

(And, yes, I should be using a switch statement here). We start with an enum that contains the row numbers for the displayed fields:

typedef enum {
    AKAttractionFieldIndexName = 0,
    AKAttractionFieldIndexGeneralInformation,
    AKAttractionFieldIndexAddress,
    AKAttractionFieldIndexAddressCity,
    AKAttractionFieldIndexAddressState,
    AKAttractionFieldIndexAddressZip,
    AKAttractionFieldIndexAttractionType,
    AKAttractionFieldIndexAvailableDays,
    AKAttractionFieldIndexAvailableSeasons,
    AKAttractionFieldIndexCost,
    AKAttractionFieldIndexInsideOutside,
    AKAttractionFieldIndexOpenHours,
    AKAttractionFieldIndexPricingInformation,
    AKAttractionFieldIndexWebsite,
} AKAttractionFieldIndex;

And of course there are corresponding string constants for the cell identifiers:

NSString *const DefaultCellIdentifier = @"DefaultAttractionCellIdentifier";
NSString *const GeneralInformationCellIdentifier = @"GeneralInformationCellIdentifier";

Then that massive if statement, creating and returning a cell of the correct type for each row. So for General Information, we get the following:

    AKGeneralInformationCell *generalInformationCell =
        [tableView
        dequeueReusableCellWithIdentifier:GeneralInformationCellIdentifier];
    generalInformationCell.generalInformation.text =
        self.attraction.generalInformation;
    generalInformationCell.generalInformation.editable = NO;
    return generalInformationCell;

That pattern should look familiar to anyone who’s ever coded a tableview before.

It feels like there might be a way to make the thing entirely table-driven rather than using an if/switch statement. I decided to hold off on trying this until I’ve created a few more cell types to see how much the code varies for each cell type.

That’s my next task- generate custom cells for each of the fields listed in that enum. Two down, twelve to go.