Subscripts and Superscripts in a UITableView

February 28, 2010, 11:47 pm

I have written a handful of iPhone apps now and used UITableView extensively in one of them. Until recently, I had never needed to put any rich text into a UITableViewCell.

The data I am using for this project is basically an index list of mini HTML documents, with an index term for each document. I use the UITableView to render the index. Most of the index terms are in plain text, but there are handful of them that include HTML tags to indicate sub-scripted or super-scripted characters:

Glucose (C6H12O6)

My initial reaction to cases like this was to create a UITableViewCell that had a UIWebView as a subview. I could just stick my HTML in the sub view and voila! my index would be rendered perfectly:

// get the term so we can check what is in it NSMutableString * term = [[[NSMutableString alloc] initWithFormat:@"%@", [dataController objectInListAtIndex:[termIndex intValue]].term] autorelease]; // need to work out what sort of cell this needs to be NSRange textRange =[term rangeOfString:@"<"]; bool foundHtml = NO; if(textRange.location != NSNotFound) foundHtml = YES; NSString * cellIdentifier = [[[NSString alloc] initWithFormat:@"%@", (foundHtml ? @"HtmlCell" : @"Cell") ] autorelease]; // Dequeue or create cell of the appropriate type. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) { // didn't find a reusable cell, create one based on the type if (foundHtml) { // Grab the UITableViewCell from the HtmlCell XIB NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"HtmlCell" owner:self options:nil]; cell = [topLevelObjects objectAtIndex:0]; } else { // This cell doesn't have any markup, just use a regular cell cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease]; cell.textLabel.textColor = [UIColor blackColor]; cell.textLabel.highlightedTextColor = [UIColor whiteColor]; } cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } // set the text if (foundHtml) { // grab the htmlView from the cell and set it's content HtmlCell * htmlCell = (HtmlCell*) cell; [htmlCell.htmlView loadHTMLString:[NSString stringWithFormat:@"%@", term] baseURL:nil]; } else { // set the cell text normally cell.textLabel.text = term; } return cell;

I had a nagging feeling about approaching the problem this way and after the writing the code and seeing the woeful performance of UIWebView I remembered what it was. Months ago I listened to this mobile orchard interview with the Joe Hewitt, creator of the Facebook iPhone app and the open source Three20 Project. Seeing the slugglish rendering of my beautifully marked up index terms, I recalled that the unsuitability of UITableView for web content, was one of the motivations for the Three20 project (or it's original implementation in the Facebook app).

I decided against using Three20 partially because of an unreasonable case of not invented here syndrome, but also because I didn't have the time to invest in learning a new framework. I also felt like my problem was a pretty simple subset of the rich text rendering problem (just the sub-scripts and super-scripts), so there must be a simple solution.

It occurred to me that UITableView does a great job of rendering unicode characters (both Ballet Index and Karate Index render unicode characters in the table and they look great). So maybe there are sup-scripted and super-scripted unicode characters for the small set of characters that I needed (+,- and the numbers)?

Yep, there is! All I needed to do was recurse my way through each of the HTML elements in my index and replace them with the appropriate unicode character. Problem solved:

- (unichar) mapCharacter:(char)sourceChar forElement:(NSString*)element { if ([element localizedCaseInsensitiveCompare:@"sup"] == NSOrderedSame) { if (sourceChar == '2') return 0xB2; if (sourceChar == '3') return 0xB3; if (sourceChar >= '0' && sourceChar <= '9') return (0x2070 + (sourceChar - '0')); if (sourceChar == '+') return 0x207A; if (sourceChar == '-') return 0x207B; if (sourceChar == -82) return 0x00AE; } if ([element localizedCaseInsensitiveCompare:@"sub"] == NSOrderedSame) { if (sourceChar >= '0' && sourceChar <= '9') return (0x2080 + (sourceChar - '0')); if (sourceChar == '+') return 0x208A; if (sourceChar == '-') return 0x208B; if (sourceChar == -82) return 0x00AE; } return sourceChar; } // N.B. This code works for a very specific set of data. It doesn't handle // nested html tags for instance. - (NSMutableString*) processMarkupCell:(NSMutableString*) cellText { NSRange openRange =[cellText rangeOfString:@"<"]; if (openRange.location == NSNotFound) return cellText; // found an angle bracket ... need to remove the elements and map the sub/sup characters NSRange closeRange =[cellText rangeOfString:@">"]; NSRange elementRange = openRange; elementRange.location++; elementRange.length = closeRange.location - elementRange.location; // we get the element so we can use the appropriate mapping for each character (sup or sub) NSString * element = [[[NSString alloc] initWithFormat:@"%@", [cellText substringWithRange:elementRange]] autorelease]; // set up the new cell text NSRange prefixRange; prefixRange.location = 0; prefixRange.length = openRange.location; // stick everything before the element in the newCellText NSMutableString * newCellText = [[[NSMutableString alloc] initWithFormat:@"%@", [cellText substringWithRange:prefixRange]] autorelease]; // find out where we need to stop NSRange stopRange; stopRange.location = closeRange.location + 1; stopRange.length = [cellText length] - (closeRange.location + 1) ; NSRange nextOpenRange = [cellText rangeOfString:@"<" options:(NSStringCompareOptions)0 range:stopRange]; NSRange nextCloseRange = [cellText rangeOfString:@">" options:(NSStringCompareOptions)0 range:stopRange]; NSRange postfixRange; postfixRange.location = nextCloseRange.location + 1; postfixRange.length = [cellText length] - (nextCloseRange.location + 1); // start mapping the first character after the closed element for (int currentPosition = closeRange.location + 1; currentPosition < nextOpenRange.location; currentPosition++) { // append each mapped character in the newCellText [newCellText appendFormat:@"%C", [self mapCharacter:[cellText characterAtIndex:(NSUInteger)currentPosition] forElement:element]]; } // stick everything after the closing element in newCellText (other tags will be resolved recursively) [newCellText appendFormat:[cellText substringWithRange:postfixRange]]; // recurse through the string .. getting all the elements return [self processMarkupCell:newCellText]; }

Permalink - Comments - Tags: Development,iPhone

No cmp argument to list.sort () in Python 3.0

February 23, 2010, 10:07 pm

One of the changes introduced in Python 3.0 was the removal of the cmp argument from list.sort () and builtin.sorted (). This is all part of Python 3.0's intentionally backwards incompatible feature set, something I whole heartedly agree with (for reasons that will become clear).

I had been blissfully unaware of the performance implications of using the cmp argument (mostly because performance is rarely a consideration for the tools I write in Python) and had code like this sprinkled generously around the place:

# sort an array by the length of it's constituents l.sort(cmp=lambda x, y: len(x) - len(y))

I hadn't read the whole Python 2.4 article on sorting, which explains how cool the key argument is and how calling your key parameter once for each member in the list is much more efficient. So when I went to use the cmp argument in Python 3.0 it cheerfully failed and sent me off to the What�s New In Python 3.0 page at python.org for an explanation.

This is exactly the problem that Python 3.0 is trying to fix, unfortunately the explanation on the What's New page was sadly lacking:

builtin.sorted() and list.sort() no longer accept the cmp argument providing a comparison function. Use the key argument instead.

What I needed in addition, to this terse repetition of the error message I got from the interpreter, was something like:

The value of the key parameter should be a function that takes a single argument and returns a key to use for sorting purposes.

This all seems fairly trivial in retrospect but I spent real time hunting around for a description of this fangled key thing and built up enough angst to write this admittedly petulant post.

I doubt very much that a like minded, slightly befuddled individual will find his way here, so mostly for my own reference, sorting by string length:

def cmpLen (other): return -1 * len(other) # sort by string length (descending) l.sort (key=cmpLen)

Permalink - Comments - Tags: Development

Small things amuse ...

February 9, 2010, 4:25 pm

Recursion is fun. Just sayin...

mappings = { 'o':['1','2'], 'k':['d','l','w'] } def printVariants (substring): print (substring) for i in range(0, len(substring)): if mappings.has_key (substring[i]): for mapped in mappings[substring[i]]: newSubstring = substring[:i] + mapped + substring[i + 1:] printVariants (newSubstring) printVariants ("fook")

Permalink - Comments - Tags: Development

Google Maps Sandbox

February 5, 2010, 10:48 pm

I have been a Google Maps enthusiast for some time and of late have been venting some of that enthusiasm in the google-maps tag on Stack Overflow. Despite having answered a bucket load of questions and done quite a bit of development with the Google Maps API, I sometimes encounter a question whose solution requires some empirical testing.

To facilitate these tests I set up a sandbox page on my site to host my cobbled together examples. The process has been quite rewarding and I have found myself delving into areas of the API which I would have ignored for my own projects. Here are a few of my favorites:

  • Draw a square at an arbitrary point - This mostly involved taking someone else's awesome server side code, to determine a location an arbitrary distance/direction from a known point, and porting it to Javascript. Although I didn't get any questioner love on this one, the solution was still quite satisfying.
  • Directions - Before this question I hadn't had a chance to play with the GDirections functionality in the API. This simple example just grabs the route between two points. The corresponding questioner was having some trouble getting this to work and it turned out the problem was unrelated.
  • Travel time data - You can use the GDirections object without providing a map and directions div and just get the data back as a JSON response. In this demo I grabbed the driving time for the directions calculated between two points.
  • Overlays - Had a lot of fun answering this question with a simple demo for using GTileLayerOverlay. The ability to leverage to the Google Maps API with your own tilesets is very powerful.
  • Open street map tiles in Google maps - I implemented this demo for a question I didn't actually answer myself. I was browsing questions and found one that described how you can use the Google Maps API with an entirely separate map tile set, in this case from OpenStreetMap.org. This seemed like such a cool concept, I had to try it out.

Permalink - Comments - Tags: Development,Google

Jack Aubrey a sceptic?

February 4, 2010, 12:28 pm

I have had, for as long as I remember, a deeply rooted confidence in science and a corresponding lack of faith in religion. Recently I have found that there exists a like minded group of individuals whose world view very neatly fits my own.

The Skepticism movement is a somewhat eclectic group of people with a common belief in the importance of science and reason. I hadn't realized that there were people actively promoting this kind of thinking (and the importance of doing so) until I began listening to the popular Skeptics Guide to the Universe podcast:

My experience of The Skeptics' Guide has been a liberating one. It has led me to re-examine some of the irrational beliefs, which I had held quite strongly, and taken away much of the uncertainty that had plagued my perception of the world around us.

I feel like a Skeptical world view, rather having the negative connotations of cynicism, is a compassionate and hopeful one that is not driven by our very human ability to frighten ourselves with the unknown.

I was delighted to discover, while reading The Surgeon's Mate for my Mapping Project, Jack Aubrey having a moment of sceptical clarity in response to Mr Pellworm's lamentation of departing on Friday the thirteenth (and with a woman on board for all love):

'No, no: your omens keep threatening disaster - they did so before Grimsholm, and you see what happened: all cry with no wolf at the end of it. I have done with omens,' he said, grasping a belaying-pin. 'But your falling glass is another kettle of fish: your glass is scientific.'

Another kettle of fish indeed.

Permalink - Comments - Tags: Patrick O'Brian