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

Questions and Answers

January 21, 2010, 1:21 am

I am a strong believer in the importance of constantly exercising and expanding my skillset as a programmer. I think it is pretty common that the range of tasks encounted during a normal work day will not nessasarily provide the breadth of problem domains to satisfy this desire. So I have often found myself embarking on grand, sometimes fataly flawed, personal projects.

Over the past few months, I found a new outlet for these random bursts of creativity:

Stack Overflow


Stack Overflow is a programming Q & A site that's free. Free to ask questions, free to answer questions, free to read, free to index, built with plain old HTML, no fake rot13 text on the home page, no scammy google-cloaking tactics, no salespeople, no JavaScript windows dropping down in front of the answer asking for $12.95 to go away. You can register if you want to collect karma and win valuable flair that will appear next to your name, but otherwise, it's just free. And fast. Very, very fast.

Yes! Yes, I want to win valuable flair. Badges are shiney and precious and I want them. These guys certainly know their demographic. There are a few things about Stack Overflow that I find ridiculously addictive:

  • Badges - It's slightly embarressing but apparently I have a weakness for U+25CF.
  • A couple of points of virtual self validation - I have a history of performing meaningless tasks to accrue self worth in arbitrary point systems. Stack Overflow taps right into that insanity and thankfully generates some positive behavior.
  • Inherent Value - Investigating and solving programming problems is very good practice. When this practice adds to a large pool of useful answers, the value here is undeniable.
  • I like to write - I don't think I am particularly good at it, but I think the only way to get better is to do it a lot. Stack Overflow provides a peer reviewed testing ground for clear expression. I can practice my writing without having to resort to inane, self indulgent Blog posts like this one.

So I enjoy answering programming questions. What I didn't realise was that this, primarily ego driven, exercise translates to other domains. The 'How is Babby Formed' phenomena warned me away from looking at Yahoo Answers, but there are some great competitors in this space:

Mahalo


Mahalo is a human-powered search engine dedicated to help people easily find information and resources they can trust.

Mahalo was created as the Human Powered Search Engine. Basically thousands of user contributed pages tied to traditional Google text, image and video search results. Each page is ad supported and a percentage of the revenue is paid to the page author in Mahalo's virtual currency (which can be eventually be converted to US dollars).

In the last few months Mahalo has added Q&A to this core set of functionality. Users can ask questions on any topic and Mahalo members can answer those questions to earn points and Mahalo dollars. As with any Q&A system that is open to questions on any topic, there is a certain amount of noise, but in general the questions are reasonably good. I don't find the virtual currency very compelling but, as with Stack Overflow, I am a sucker for earning points.

Aardvark


Aardvark was conceived as the first Social Search engine: a way to find people, not web pages, that have specific information.

Aardvark is based on the premise that your social network is a great place to start to find answers to your questions. Without a points system, or indeed any motivation outside altruism, I haven't felt the same compulsion to answer questions. I have found it very useful for finding answers. Because Aardvark actively scans your network and pings other users for answers to your questions, if an answer can be found, it generally finds it very quickly.

I don't really understand what motivates the helpful people who have promptly answered some of my Aardvark questions, but I am very grateful that the service exists. I wonder about its longevity, but hope that its success continues.

Stack Exchange

With the success of Stack Overflow it made perfect sense for Joel Spolsky and Jeff Atwood (the co-founders) to package up the engine and the Knowledge Exchange paradigm as a product. Stack Exchange is that product, and based on the considerable list of Stack Exchange powered sites, it looks like it has been a big success.

Permalink - Comments - Tags: Development,Misc