Xamarin and C# for iOS

xamarin-dev-screenshot

With Visual Studio 2015 (VS2015) getting closer and closer to being released and the announcement that it will contain Xamarin for multi-platform development (iOS, Android and Windows apps), I figured I would give Xamarin a try for some simple iOS development.

For iOS development, unfortunately, there aren’t any emulators for Windows… So if you’re going to be doing development on a Windows computer, you will also need a Mac. The iOS emulator, build services and all the bells ans whistles for iOS have to be run through the Mac. There are some networking components that help streamline this process, basically setting up the Mac as a build host. You can get more information on all of this from Xamarin, here.

Since I am more interested in just testing out the C# language features for Xamarin, and not so much the VS2015 implementation of it just yet, I downloaded and installed Xamarin on my Mac directly.

xamarin-pricing

Once I was able to get Xamarin installed, I was hit with all of the subscription information and business class services from Xamarin. You don’t specifically need the paid services, but the features offered will make things much easier for more complicated apps and shared features provided by Xamarin. Basically it’s a $999/year for business licencing, there is also an “indie” program for $25/month. For the specifics on pricing, take a look, here. For just playing around, I’m sticking to the “free” version. (Note: You still have to sign up for an account)

When using the “free” version of Xamarin, and starting a new project, I had to remove some code that is used to deploy the code to the Xamarin cloud as well as remove some packages that are only licensed for paid subscriptions.

Here is the code that you need to remove from the AppDelegate.cs file in the XXXXX.iOS project (in the FinishedLaunching function).

// Code to start the Xamarin Test Cloud Agent
#if ENABLE_TEST_CLOUD
Xamarin.Calabash.Start();
#endif

You can also remove the Xamarin.TestCloud.Agent package from the iOS project.

xamarin-package-removal

All of this stuff facilitates running of the the application in the Xamarin Test Cloud… Something you have to pay for (if you’re paying for it, leave it in so you can use it).

Once that is all removed, you can build and run iOS applications natively on your Mac.

xamarin-test-app-1

For testing I decided to write a simple application that makes a call to the RSS feed of this blog (http://santsys.com/s2blog/feed/) and displays the information in a simple UITableView. When you tap on an article, it opens up in a simple UIWebView.

I was blown away how simple most of the coding is, it’s pretty much just C# using the Objective-C / Swift iOS class names… So to access UITableView, you simply use UITableView. There is no special thing you have to remember, etc.

There are some complexities with things like UITableViewSource objects and them needing to be their own class… I’ve found I’ve had to add a lot of constructors that take “parent view” information. But for core logic and other things like that, it’s all C#. And, so I hear, if you are using the Xamarin forms code that is cross platform, it’s even easier to target multiple device type.

An example of the table data source class from this application is below. This shows how I open the UIWebView when an article is tapped on. Basically using the navigation controller parent that is passed on the source creation to push the view controller.

public class TableSource : UITableViewSource {
	private UINavigationController _parent;

	public TableSource(IEnumerable items, UINavigationController parent) {
		this._parent = parent;
	}

	public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
	{
		UIWebView web = new UIWebView (this._parent.View.Frame);
		web.AutoresizingMask = UIViewAutoresizing.All;
		web.LoadRequest (new NSUrlRequest (new NSUrl ("URL FROM VIEW DATA")));
		UIViewController view = new UIViewController ();
		view.Add (web);
		this._parent.PushViewController (view, true);

		tableView.DeselectRow (indexPath, true);
	}
}

The simple passing of the parent navigation controller lets me access the navigation, push or pop view controllers, etc.

Another cool aspect of C# for iOS is that you can do really easy things like background threads, web requests, etc. You can see more of that below and in the more complete source code.

System.Threading.Tasks.Task.Factory.StartNew (() => {

});

Here is the main view application code, i’ve combined the table source and RSS information classes into the one file for simplicity here. Feel free to create your own project and give it a try. I was shocked how easy it all was and how well it all runs.

Bottom line, if you’re a C# developer, you need to check this out if you want to make apps for the various devices. In the case of iOS, the storyboard editor in Xamarin seems to work better for me and because the underlying code is C#, I just like it all that much better. You need to check it out if you haven’t yet.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Xml;
using System.Collections;
using System.Drawing;
using Foundation;
using CoreGraphics;
using UIKit;

namespace TestApp.iOS
{
	public class RssData {
		public string Title { get; set; }
		public string Url { get; set; }
		public string Detail { get; set; }
		public DateTime? PublishDate { get; set; }
	}

	public class TableSource : UITableViewSource {

		private ArrayList itemList = new ArrayList ();
		private string CellIdentifier = "rssCell";
		private UINavigationController _parent;

		public TableSource(IEnumerable items, UINavigationController parent) {
			foreach (RssData r in items) {
				itemList.Add (r);
			}
			this._parent = parent;
		}

		public override nint RowsInSection (UITableView tableview, nint section)
		{
			return itemList.Count;
		}

		public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
		{
			UITableViewCell cell = tableView.DequeueReusableCell (CellIdentifier);
			RssData item = (RssData)itemList[indexPath.Row];

			if (cell == null)
			{ 
				cell = new UITableViewCell (UITableViewCellStyle.Subtitle, CellIdentifier);
				cell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
			}

			cell.TextLabel.Text = item.Title;

			if (!string.IsNullOrWhiteSpace (item.Detail)) {
				cell.DetailTextLabel.Text = item.Detail;
			} else {
				if (item.PublishDate.HasValue) {
					cell.DetailTextLabel.Text = item.PublishDate.Value.ToString ("ddd, dd MMM yyyy HH:mm:ss");
				}
			}

			return cell;
		}

		public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
		{
			RssData d = (RssData)itemList [indexPath.Row];
			if (d != null && !string.IsNullOrWhiteSpace (d.Url)) {
				UIWebView web = new UIWebView (this._parent.View.Frame);
				web.AutoresizingMask = UIViewAutoresizing.All;
				web.LoadRequest (new NSUrlRequest (new NSUrl (d.Url)));
				UIViewController view = new UIViewController ();
				view.Add (web);
				this._parent.PushViewController (view, true);
			}
			tableView.DeselectRow (indexPath, true);
		}
	}

	public partial class ViewController : UIViewController
	{
		UITableView table;
		LoadingOverlay loadingOverlay;

		public ViewController (IntPtr handle) : base (handle)
		{
			
		}

		public override void ViewDidLoad ()
		{
			base.ViewDidLoad ();

			// Add the tableview to the view controller
			table = new UITableView(this.View.Bounds);
			table.AutoresizingMask = UIViewAutoresizing.All;
			this.View.Add (table);

			 System.Threading.Tasks.Task.Factory.StartNew (() => {
				LoadRssData ();
			});
		}

		public override void DidReceiveMemoryWarning ()
		{		
			base.DidReceiveMemoryWarning ();
			// Release any cached data, images, etc that aren't in use.	
		}

		public void LoadRssData() {
			string url = "http://santsys.com/s2blog/feed/";
			string content = "";

			WebRequest request = new HttpWebRequest (new Uri(url));
			using (WebResponse resp = request.GetResponse ()) {
				System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream ());
				content = sr.ReadToEnd ();
				sr.Close ();
			}

			if (!string.IsNullOrEmpty (content)) {
				XmlDocument xDoc = new XmlDocument ();
				try {
					xDoc.LoadXml (content);

					ArrayList list = new ArrayList();

					XmlNodeList itemList = xDoc.GetElementsByTagName("item");

					foreach(XmlNode n in itemList) {
						RssData d = new RssData ();
						if(n["title"] != null) {
							d.Title = n["title"].InnerText;
						}

						if(n["link"] != null) {
							d.Url = n["link"].InnerText;
						}

						if(n["pubDate"] != null) {
							string pubDate = n["pubDate"].InnerText;
							pubDate = pubDate.Substring(0, pubDate.Length - 6);
							DateTime parsedDate;
							if (DateTime.TryParseExact(pubDate, "ddd, dd MMM yyyy HH:mm:ss", null, System.Globalization.DateTimeStyles.AssumeLocal, out parsedDate)) {
								d.PublishDate = parsedDate;
							}
							else {
								d.Detail = pubDate;
							}
						}

						if(!string.IsNullOrEmpty(d.Title)) {
							list.Add(d);
						}
					}

					InvokeOnMainThread (() => {
						table.Source = new TableSource(list, this.NavigationController);
						table.ReloadData();
					});
				} 
				catch(Exception) {
				}
				finally {
				}
			}
		}
	}
}

Any questions or comments? Let me know in the comments below!

Leave a Reply

Your email address will not be published. Required fields are marked *