Thursday 8 October 2009

MonoTouch "Roget's 1911 Thesaurus"

This sample was inspired by Roget’s Hierarchical Thesaurus in a Silverlight App. I highly recommend you check out the Silverlight Thesaurus 'visualization' as it will give you some idea about why it seemed like a good match for the iPhone UINavigationController (ie. a hierarchy). The fact that it's not a uniform depth just makes the problem more interesting.

Charles very generously provided me with the source data he worked on to produce the Silverlight app (and the associated C# classes). It's great that MonoTouch allows existing .NET source to be re-used so easily.

Here are some screenshots of the iPhone app:


Xml de-serialization is easy...
Loading 2.6Mb of Xml data into an object graph is so easy with the System.Xml.Serialization support in MonoTouch!
using (TextReader reader = new StreamReader("roget15aCategories.xml"))
{
XmlSerializer serializer = new XmlSerializer(typeof(RogetCategories));
Categories = (RogetCategories)serializer.Deserialize(reader);
}
...but don't forget the linker
When you create the classes you will be de-serializing "into", don't forget to mark them with the MonoTouch.Foundation attribute [Preserve(AllMembers=true)]. This prevents the compiler/linker from 'optimising away' parts of your class that don't "appear" to be referenced in code (eg. the constructor) because they are only getting called at runtime as part of the deserialization process. Instead of marking the class declaration, you could alternatively mark specific members with [Preserve] to give you greater control over the final output.

Use Linq
Another great piece of .NET support is Linq. It isn't referenced in MonoDevelop MonoTouch solutions by default, so don't forget to right-click your References and tick System.Xml.Linq then add the using clause and Linq away...
public List GetRange (string start, string end)
{
Console.WriteLine("Get {0} to {1}", start, end);
var l = from c in Categories
where c.Index >= start.ToNumber() && c.Index <= end.ToNumber()
select c;
return l.ToList();
}
Use Extension Methods
Obviously if Linq works, so do extension methods. ToNumber() is a very simple (contrived, even) example of an extension method in MonoTouch:
public static class RogetExtensions
{
public static int ToNumber(this string num)
{
string s = num.Replace("a","");
int index;
if (int.TryParse(s, out index))
return index;
else
return -1;
}
}
"Look ma, no Interface Builder"
As I .NET developer I usually shun the design surface (in Xaml you don't really have a choice) so I was keen to try building an iPhone app without Interface Builder. It's a little difficult knowing where to start, but this UITableView in code example was immensely helpful.

There are four ViewControllers, and the basic format is the same for each: inherit from UIViewController (or a subclass), build up some controls in ViewDidLoad() and implement any additional delegates required.

The scrolling table views look like this:
// no XIB !
tableView = new UITableView()
{
Delegate = new TableViewDelegate(Classes, this),
DataSource = new TableViewDataSource(Classes, this),
AutoresizingMask = UIViewAutoresizing.FlexibleHeight|
UIViewAutoresizing.FlexibleWidth,
BackgroundColor = UIColor.White,
};
tableView.SizeToFit();
tableView.Frame = new RectangleF (
0, 0, this.View.Frame.Width, this.View.Frame.Height);
this.View.AddSubview(tableView);
and the 'details view' like this:
// no XIB !
webView = new UIWebView()
{
ScalesPageToFit = false
};
webView.LoadHtmlString(FormatText(), new NSUrl());
webView.SizeToFit();
webView.Frame = new RectangleF (
0, 0, this.View.Frame.Width, this.View.Frame.Height);
this.View.AddSubview(webView);
Look in MainViewController.cs and AppController.cs to see how they are all wired together. The other ViewControllers pass around a reference to MainViewController so they can call mvc.NavigationController.PushViewController (????, true); which makes the navigation work (automatic 'back' buttons, animation between views, etc). I've no idea if this is the best way to do it, but hey it works :)

THE CODE
This sample code would not have been possible without the hard work of others. It also uses content which can have copyright/ownership implications.
  • Thank you to Charles Petzold for (a) researching/parsing the text (b) writing and supplying the .NET classes used to access it and (c) allowing his work to be included in this derivative
  • Acknowledgement to Project Gutenburg for providing the original text. The copyright status is listed as Not copyrighted in the United States. If you live elsewhere check the laws of your country before downloading this ebook. - I presume the same applies to users of this derivative work
  • Sabon Rai MonoTouch posts including UITableView in code taught me a lot.
Anyway, you can download the MonoDevelop solution and try it for yourself.

These two class diagrams show the structure of the code. The first shows the classes that are used to deserialize the Xml data:


while this shows the application and viewcontroller implementations:

3 comments:

  1. Wow, another great post, Craig.

    I tried XmlSerializer a couple weeks ago and it didn't work for me. It wasn't much XML, so I just parsed it with XDocument. With your key tips, I'll try again.

    P.S. Thanks for recognizing my blog article about UITableView. Would you mind changing the reference to "Sabon Rai" rather than just Sabon? Sabon Rai is not a person's name; it means "New Life" in an African language I learned growing up in Nigeria: http://www.sabonrai.com/about.html.

    ReplyDelete
  2. Thanks for posting this sample; As usual, it was amazing!

    ReplyDelete
  3. Thanks guys - having heaps of fun with these little samples, although I feel like I should get cracking on a 'real' app for the AppStore... so many ideas, so little time (outside the "real job")...

    ReplyDelete

Note: only a member of this blog may post a comment.