Monday, 26 October 2009

MonoTouch "Corp411" (part III: Revenge of System.Data)

As promised, the Corp411 sample app has been 'converted' from sqlite-net to System.Data. MonoTouch 1.2 isn't quite out yet, so you must follow the instructions on this page to download the System.Data assemblies to your installation, put them in the correct place AND add references to your MonoDevelop solution for Mono.Data.Sqlite.dll, Mono.Data.Tds.dll, System.Data.dll, System.Transactions.dll (I'm not sure if they're all required, but it seemed like a sensible set).

It's pretty easy to use after all that:
// db is the path to the SQLite database file
var conn = new SqliteConnection("Data Source=" + db);
and
listData = new List<Employee>();
// System.Data from http://monotouch.net/Documentation/System.Data
var sd = new SystemDataHelper("phonebook");
var connection = sd.GetConnection();
using (var cmd = connection.CreateCommand())
{
connection.Open ();
cmd.CommandText = "SELECT Firstname, Lastname, Work, Mobile,"
+ " Department, Email "
+ " FROM Phonebook ORDER BY Lastname";
using (var reader = cmd.ExecuteReader ())
{
while (reader.Read ())
{
var emp = new Employee();
emp.Firstname = (string)reader["Firstname"];
emp.Lastname = (string)reader["Lastname"];
emp.Work = (string)reader["Work"];
emp.Mobile = (string)reader["Mobile"];
emp.Department = (string)reader["Department"];
emp.Email = (string)reader["Email"];
Console.WriteLine("Column {0}",reader["Lastname"]);
listData.Add(emp);
}
}
}
Cool eh? Doesn't that look familiar... good old ADO.NET :-)


THE CODE
That's a small part of the System.Data namespace, so the code is pretty small :) here 'tis Corp411.3.zip (38Kb)

... and proof it runs on a device ...

Friday, 23 October 2009

MonoTouch "Corp411" (part II)

A minor update to the Corporate Phonebook incorporating the custom UITableViewCell from Simon (UITableView now shows name and department in each cell; also added a disclosure indicator). Handles RowSelected and uses OpenUrl to call and email.

The MonoDevelop solution is available to download (41Kb).



//TODO: give the new MonoTouch 1.2 System.Data implementation a try!

//HACK: System.Data example now available (26-Oct)

Wednesday, 21 October 2009

MonoTouch flip-flop with UIView animation

MonospaceThanks to Beginning iPhone Development's Objective-C examples the Monospace app now does a flip-flop between two views (the map and a new list of map locations to 'pan to'). The source code is available for download (you'll also notice the UI has been made "black"... I just like the way it looks).

Monospace22.zip (188Kb)




The view is driven by the MapFlipViewController (which is what we add to the TabViewController). It is responsible for creating/displaying the two 'flippable' views using UIView animation features.

The two views - MapViewController & MapLocationViewController - have a constructor which accepts a reference to the 'flipper' so they can call back to it's Flip() method, which looks something like this...
UIView.BeginAnimations("Flipper");
UIView.SetAnimationDuration(1.25);
UIView.SetAnimationCurve(UIViewAnimationCurve.EaseInOut);
if (mapView.View.Superview == null)
{
Console.WriteLine("to map");
UIView.SetAnimationTransition
(UIViewAnimationTransition.FlipFromRight, this.View, true);
locationView.ViewWillAppear(true);
mapView.ViewWillDisappear(true);

locationView.View.RemoveFromSuperview();
this.View.AddSubview(mapView.View);

mapView.ViewDidDisappear(true);
locationView.ViewDidAppear(true);
}
else
{
Console.WriteLine("to list");
UIView.SetAnimationTransition
(UIViewAnimationTransition.FlipFromLeft, this.View, true);
mapView.ViewWillAppear(true);
locationView.ViewWillDisappear(true);

mapView.View.RemoveFromSuperview();
this.View.AddSubview(locationView.View);

locationView.ViewDidDisappear(true);
mapView.ViewDidAppear(true);
}
UIView.CommitAnimations();
There is other code to actually set the map to a specific location - but that's actually pretty simple :)
mapView.SetLocation(toLocation);

Monday, 19 October 2009

Monospace app .1 release

MonospaceThe first version of the Monospace schedule app had a few issues (extra UITableView lines, for example); here is an updated version (with at least one less bug):

Monospace21.zip (183Kb)

A couple of additional features were also added...

UITabBarController More & Edit
Two of the cool things about UITabBarController are (a) when you add more than five items, it automatically creates the More... option; and (b) when the More list is visible you can also edit the order/position of items in the tab bar. Bonus!

There is a new 'Sponsors' ViewController to show this functionality:

I've used a UIWebView with images saved locally as a quick hack to get the sponsors page working. Setting the baseUrl makes is easy to reference images saved in the application bundle from local Html.
string basedir = Environment.GetFolderPath 
(System.Environment.SpecialFolder.Personal);
basedir = basedir.Replace("Documents", "Monospace2.app");
webView.LoadHtmlString(FormatText(),
new NSUrl(basedir+"/Sponsors/", true));
CoreLocation with MapKit
The MapViewController now also uses CoreLocation.CLLocationManager and custom delegate to track your location and calculate the distance to the conference center:

It also includes a new UISegmentedControl to change the map style.

Sunday, 18 October 2009

MonoTouch sans Interface Builder

I have nothing against Interface Builder per se, but as I've said before when developing with Visual Studio I shun the design surface (if you've used the Xaml preview feature prior to 2010 you'll understand why). I also think you get a better understanding of your environment (and a greater ability to customize your UI) if you can create it in code.

But for new MonoTouch developers it can be difficult to figure out how to create something in code. I've just been through this adding a UISegmentedControl to the Monospace app... here are my (rather obvious) suggestions:
  • Autocomplete/Intellisense™ - the easiest and most obvious thing to try is reading through the properties and methods available to you via MonoDevelop. I got the UITabBarController (mostly) working this way.
  • Interface Builder - look closely at the IB Inspector windows to get some ideas about the properties exposed.
  • XIB file - further to the Inspector windows, the XIB file (in its weird XMLish format) might also give you some hints. Obviously this means you need to 'draw' the control/s in IB, but just roughly for inspiration...
  • Objective-C references - for now most search results will be from Objective-C devs; it isn't hard to decode them (usually) so don't be dissuaded because it "looks different" :)
    Try Miguel's Rosetta stone decoder for Objective-C classes/properties → MonoTouch
  • MonoTouch docs - last but not least, the documentation will help you. Bookmark it now.
XIB and Inspector


UISegmentedControl in c#
// Don't forget your using statements 
// this example uses MonoTouch.MapKit
// as well as UIKit and Foundation
var segmentedControl = new UISegmentedControl();
segmentedControl.Frame = new RectangleF(20, 350, 282,44);
segmentedControl.InsertSegment("Map", 0, false);
segmentedControl.InsertSegment("Satellite", 1, false);
segmentedControl.InsertSegment("Hybrid", 2, false);
segmentedControl.SelectedSegment = 0;
segmentedControl.ControlStyle = UISegmentedControlStyle.Plain;

segmentedControl.ValueChanged += delegate {
if (segmentedControl.SelectedSegment == 0)
mapView.MapType = MonoTouch.MapKit.MKMapType.Standard;
else if (segmentedControl.SelectedSegment == 1)
mapView.MapType = MonoTouch.MapKit.MKMapType.Satellite;
else if (segmentedControl.SelectedSegment == 2)
mapView.MapType = MonoTouch.MapKit.MKMapType.Hybrid;
};
// And further down the code, don't forget to Add it!
this.View.AddSubview(segmentedControl);

Saturday, 17 October 2009

MonoTouch for Monospace

MonospaceMonospace is coming up fast, with a whole day's worth of MonoTouch sessions. With that in mind, it seems kinda obvious to make the Monospace schedule available as a MonoTouch app...

Here are some screenshots:



In short, it provides:
  • Conference schedule (day/session/detail views)
  • Speakers (list/detail)
  • Access to the monospaceconf blog
  • Access to @monospace_conf's stream on Twitter
  • A map of the conference location
Some of the fun MonoTouch things I learned along the way include:
  • Creating a UITabBarController in code (no IB)
  • Making icons for UITabBarItem (hint)
  • Creating an 'image watermark' with UIImage behind a UITableView (thanks Mike)
  • Using Linq for Xml to parse Atom feeds (like RSS)
  • Implementing the latest SQLite wrapper from Frank
  • Changing the default UITableViewCell layout in code (eg mutiple lines)
  • Creating a custom UITableViewCell with LoadNib (thanks Simon)
I hope to write some more detail on each of these items... but for now you can download and run the code for yourself:

Monospace2.zip (94Kb)

UPDATE 19-Oct: newer version available
Monospace21.zip (183Kb)

UPDATE 21-Oct: even newer version available
Monospace22.zip (188Kb)

and here's an annotated class diagram/placemat to help make sense of it all:


WARNING: there are plenty of shortcomings/hacks/missing features including: weird lines on the Speaker UITableView, no reloading of data from Blogger or Twitter without restarting, heaps of weird "pointer being freed was not allocated" errors accompanying the SQLite code (only visible in the Application Output window) and probably a pile of bugs I'm not aware of.
USE AT YOUR OWN RISK!

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:

Tuesday, 6 October 2009

MonoTouch "iSOFlair" 2.0

The original iSOFlair post was purely a demonstration of using Settings.bundle root.plist to drive the iPhone Settings application. It's just a bit of fun with the "Flair" feature of stackoverflow.com and related sites.

Since then I've incorporated:
  • Simon's Page Control sample (with added Page Control handling)
  • Download and save image via HTTP
  • Shake gesture support
  • Screen capture to Photos, filesystem
  • Set the badge on the application's icon
This is what it looks like:

and you can download the MonoTouch v1.1 source code to try for yourself.

Settings via root.plist
Configuring the iSOFlair application to show a particular user's statistics is done by the built-in iPhone Settings on the home screen. Creating the Settings.bundle and root.plist is covered in this post.

Page Control
Simon's sample makes it easy to set-up a Page Control with swiping however it's also possible to move between 'pages' by touching to the left or right of the dots. I added this delegate to FinishedLaunching to handle that case:
// handler for when the user navigates via the pager, rather than swiping
pageControl.ValueChanged += delegate(object sender, EventArgs e) {
var pc = (UIPageControl)sender;
double fromPage = Math.Floor((scrollView.ContentOffset.X - scrollView.Frame.Width / 2) / scrollView.Frame.Width) + 1;
var toPage = pc.CurrentPage;
var pageOffset = scrollView.ContentOffset.X + scrollView.Frame.Width;
Console.WriteLine("fromPage " + fromPage + " toPage " + toPage);
if (fromPage > toPage)
pageOffset = scrollView.ContentOffset.X - scrollView.Frame.Width;
PointF p = new PointF(pageOffset, 0);
scrollView.SetContentOffset(p,true);
list[toPage].ViewController.BecomeFirstResponder(); // so it can "accept" shakes
// so we can change the 'badge' on the application icon
maxPageVisited = maxPageVisited<toPage?toPage:maxPageVisited;
};
Download, save and display PNG in UIImage
With using System.Net added to the file, the following code will download a remote image, save to the filesystem on the iPhone and set that image to display in a UIImage control on your view:
string ImagePath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
ImagePath = Path.Combine (DocumentsDirectory, Site.PreferencesPrefix+"gravatar.png");
WebClient wc = new WebClient();
Uri uri = new Uri(gravatarUrl);
try
{
wc.DownloadFile (uri, ImagePath); // saves to filesystem
}
catch (Exception ex1) {Console.Write("oops");}
//UIImage img = UIImage.FromFile (ImagePath); // WHY does this break???
UIImage img = UIImage.FromFileUncached(ImagePath); // BUT this works???
if (img != null)
this.imageAvatar.Image = img; // displays on View
Shake gesture
Responding to the iPhone OS 3.0 'shake' gesture was covered in this post. As I said before, showing the network activity indicator UIApplication.SharedApplication.NetworkActivityIndicatorVisible=true; and then false is a good idea so the user knows that something is going on!

Screen capture
Using UIGraphics ImageContext to take a screenshot was covered in this post.


Set badge
The 'badge' is pretty contrived in this example. Every time the application starts (in FinishedLaunching) I start the badge at '4' then during paging we reduce the count for each page that has been viewed. Just before the app closes (in WillTerminate) we set the badge via
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 3 - maxPageVisited;
so that it is effectively showing the number of 'unviewed' panels in the application for the last execution.


THE CODE
You can download the MonoDevelop solution to try it for yourself. The c# files are also listed below for your perusal:

Monday, 5 October 2009

MonoTouch screen capture in c# code

In a previous post I said that if you capture a screenshot of your application's final 'state' (say in WillTerminate) and save it as Default.png then it would be used as the 'splash screen' next time the application starts (the built-in iPhone apps like Mail seem to do this by zooming in/out with a screen capture of their last state).

The code below takes a screenshot, and was supposed to overwrite the Default.png you have in your MonoTouch application root (eg. iSOFlair.app/) and become the new splash screen. In order to 'test' this theory I had to write the code below and place it in UIViewController subclasses that I wanted to 'capture'.

Anyway, here's the code... not sure what other uses it might have?
/// <summary>
/// Capture a copy of the current View and:
/// * re-display in a UIImage control
/// * save to the Photos collection
/// * save to disk in the application's Documents folder
/// </summary>
public void ScreenCapture()
{
var documentsDirectory = Environment.GetFolderPath
(Environment.SpecialFolder.Personal);

Console.WriteLine("start capture of frame: " + this.View.Frame.Size);
UIGraphics.BeginImageContext(View.Frame.Size);
var ctx = UIGraphics.GetCurrentContext();
if (ctx != null)
{
View.Layer.RenderInContext(ctx);
UIImage img = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();

// Set to display in a UIImage control _on_ the view
imageLogo.Image = img;

// Save to Photos
img.SaveToPhotosAlbum(
(sender, args)=>{Console.WriteLine("image saved to Photos");}
);

// Save to application's Documents folder
string png = Path.Combine (documentsDirectory, "Screenshot.png");
// HACK: overwrite the splash screen. iSOFlair is the application name
//string png = Path.Combine (documentsDirectory, "../iSOFlair.app/Default.png");

NSData imgData = img.AsPNG();
NSError err = null;
if (imgData.Save(png, false, out err))
{
Console.WriteLine("saved as " + png);
} else {
Console.WriteLine("NOT saved as" + png +
" because" + err.LocalizedDescription);
}
}
else
{
Console.WriteLine("ctx null - doesn't seem to happen");
}
}
Now your MonoTouch iPhone app would appear to "startup" much faster :) There is still a bit of a trick with the View.Frame.Size - doesn't take the status bar into account - which I will try to fix up in future. UPDATE: Tested in Simulator - works fine - but doesn't appear to work on the Device :-(.

Here is a useful ObjectiveC image tricks post that was helpful.

p.s. for those who don't already know - as a user you can always take a screen capture of the current display by pressing the Home and Power buttons at the same time... a new file will be created in your Photos : Camera Roll.

Saturday, 3 October 2009

MonoTouch shake shake shake

I like the way the iPhone Facebook application updates on a shake (no wasted screen real-estate with an Update button), so I went looking for some ideas how to do it. This StackOverflow thread about shake API describes both 'old school' (using accelerometer) and new OS3.0 built-in MotionEnded methods.

This c# code for MonoTouch just uses the OS3.0 feature - it's easier and the Simulator Hardware menu has a Shake Gesture item for testing :)

Firstly, in your Main.cs (or wherever FinishedLaunching is defined) add
UIApplication.SharedApplication.ApplicationSupportsShakeToEdit = true;
then put this code in your UIViewController subclass/es.
#region respond to shaking (OS3+)
public override bool CanBecomeFirstResponder {
get {
return true;
}
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
this.BecomeFirstResponder();
}
public override void ViewWillDisappear (bool animated)
{
this.ResignFirstResponder();
base.ViewWillDisappear (animated);
}
public override void MotionEnded (UIEventSubtype motion, UIEvent evt)
{
Console.WriteLine("Motion detected");
if (motion == UIEventSubtype.MotionShake)
{
Console.WriteLine("and was a shake");
// Do your application-specific shake response here...
Update();
}
}
#endregion
Within your Update method (or whatever you call it), you probably want to wrap your internet access with code to show/hide the network indicator spinner in the status bar (NetworkActivityIndicatorVisible = true) so the user knows that something is happening. Here's a basic example:
WebClient wc = new WebClient();
Uri uri = new Uri("http://sample.corp/stuff");
byte[] bytes = null;
try {
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
bytes = wc.DownloadData(uri);
}
catch (Exception ex) {
Console.WriteLine("Internet connection failed: " + ex.Message);
return;
}
finally {
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false;
}

NOTE: other iPhone applications use the shake gesture for Undo - here's a simple NSUndoManager example (ObjectiveC, but fairly easy to follow).