Titanium: Table view rows of the same class must be alike
When I first started playing with Titanium, I found the “className” property of table view rows strange. The documentation describes it as
the class name of the table. each table view cell must have a unique class name if the cell layout is different. however, use the same name for rows that have the same structural layout (even if the content is different) to provide maximum rendering performance.
Fair enough. But after playing with “real” iPhone developement for a couple of days, it makes more sense. The className is the row’s “reusableIdentifier” in Cocoa’s terms. It is used for enabling reuse of row objects, so that when rows move off the screen their instances can be reused.
Thus it makes sense, that as rows are reused, we must make sure that all rows with the same class name have the same structure. I didn’t realize that earlier, so I had written code like this:
function mapListingToTableViewRow(listing) {
var row = Ti.UI.createTableViewRow({ height: 53, className: "listing", hasChild: true });
if (listing.pictures.length > 0) {
var pic = listing.pictures[0];
var imageView = Ti.UI.createImageView({ image: pic.url, width: pic.width, height: pic.height, left: 0 });
row.add(imageView);
}
var label = Ti.UI.createLabel({ text: listing.title, left: 80 });
row.add(label);
return row;
}
This function builds up a row with the className “listing”, always adding a label, but only adding an imageView if there is an image on the listing. Sounds ok, right? What if Cocoa tries to reuse a row that only has a label, for a row that needs both image and a label? The app breaks down. It turned out that the error was in the method updateChildren in the Objective-C class TiUITableViewRowProxy. In particular, it was the first line in this for-loop that failed:
for (size_t x=0;x<[subviews count];x++)
{
TiViewProxy *proxy = [self.children objectAtIndex:x];
TiUIView *uiview = [subviews objectAtIndex:x];
[self reproxyChildren:proxy view:uiview parent:self touchDelegate:contentView];
}
The cause was simple: the subviews were two, but the proxy array only contained one object.
Going back to the JavaScript code, this was easy to fix – adding an empty image view did the trick:
function mapListingToTableViewRow(listing) {
var row = Ti.UI.createTableViewRow({ height: 53, className: "listing", hasChild: true });
if (listing.pictures.length > 0) {
var pic = listing.pictures[0];
var imageView = Ti.UI.createImageView({ image: pic.url, width: pic.width, height: pic.height, left: 0 });
row.add(imageView);
} else {
row.add(Ti.UI.createImageView({});
}
var label = Ti.UI.createLabel({ text: listing.title, left: 80 });
row.add(label);
return row;
}
This problem was driving me crazy a couple of days ago – I almost dropped using Titanium because of that. But after getting an insight into how things work underneath, and by running the Titanium app in debug mode through Xcode, I quickly was able to track down the error. So if you're running into strange breakdowns - fire up Xcode, set it to stop on Objective-C exceptions, and step through the breaking code. Hope you'll have the same luck as I did!