BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles An Intro to the Model-View-Controller in MonoTouch

An Intro to the Model-View-Controller in MonoTouch

In our first article we created an application using MonoTouch on the iPhone. We used outlets and actions, got to know the basic application structure, and made a simple user interface. In this article we’re going to make another simple application, but this time we’re going to explore using Views and View Controllers to make an application with more than one screen. Specifically, we’ll use the UINavigationController to navigate to two different pages/screen in our application.

Before we begin building our application, let’s briefly touch on an important pattern that iPhone applications use.

Model-View-Controller (MVC) Pattern

Cocoa Touch uses a modified version of the Model-View-Controller (MVC) pattern to handle the display of their GUI. The MVC pattern has been around for a long time (since 1979) and it is intended to separate the burden of tasks necessary to display a user interface, and handle user interaction.

As the name implies, the MVC has three main parts, the Model, the View, and the Controller:

  • Model – The model is a domain specific representation of data. For instance, let’s say we were making a task-list application. You might have a collection of Task objects, say List<Task>. You might store these in a DB, an XML file, or even pull them from a web service, but the MVC isn’t specifically interested in where/how they’re persisted (or even if they are). Rather, it deals specifically with how they’re displayed and users interact with them.
  • View – The view represents how the data is actually displayed. In our hypothetical task application, we might display these tasks on a web page (in HTML), or a WPF page (in XAML), or in a UITableView on an iPhone application. If a user clicks on a specific task, say to delete it, then typically, the view raises an event, or makes a callback to our Controller.
  • Controller – The controller is the glue between the Model and the View. It’s the Controller’s job to take the data in the model and tell the View to display it. It is also the Controller’s job to listen to the View when our user clicks on the task to delete it, and then either delete that task from the Model, or tell the Model to delete the task itself.

By separating the responsibilities of displaying data, persisting data, and handling user interaction, the MVC pattern tends to create code that is easily understood. Furthermore, it encourages the decoupling of the View and the Model, so that the Model can be re-used. For example, if in your app, you had both a web based interface, and a WPF interface, you could still use the same code for your Model for both.

So that’s how it’s supposed to work, and in many MVC frameworks, it works exactly like that. In Cocoa (and Cocoa Touch), however, it works a little differently, and largely, Apple uses the MVC in name, employing Views, View Controllers, and Models, however, they’re inconsistent between different controls on how they actually implement it. We’ll explore this in more detail when building our sample application.

Views and View Controllers in MonoTouch

I mentioned briefly before, that in an iPhone application, you only ever have one window. However, you can have lots of screens. To accomplish this, you add a View and a View Controller for each screen you want to have.

The view actually has all the visual elements, such as your labels, buttons, etc., and the View Controller handles the actual user interaction (via events) on the View and allows you to run code when those events are raised. In a rough analogy, this is a slightly similar model to ASP.NET or WPF, where you define your user interface in HTML or XAML, and have a code-behind page that handles events.

When you want to go to another page, you push your View Controller (and associated view) onto the View Controller stack. In the application we’re going to build today, we’ll use a Navigation View Controller (UINavigationController) to handle our different screens, because it gives us a way to navigate between screens very easily by giving you a hierarchal-based navigation bar that allows your user to navigate backwards and forwards through View Controllers.

The UINavigationController is seen in many of the stock iPhone applications. For example, when you’re viewing a list of your text messages, if you click on one, the top bar gets a left arrow tab at the top that takes you back a view to the message list.

Hello World with Multiple Screens

Now that we understand how all that works in concept, let’s actually create an application that does it.

First, create a new MonoTouch iPhone solution in MonoDevelop and name it Example_HelloWorld_2 (refer to the first article if you’ve forgotten how to do this).

Next, let’s add two View Controllers (and their associated views) that will serve as the screens of our application that we’ll navigate to. To do this, right click on your project and choose Add : New File. Then, in the new file dialog, choose iPhone : View Interface Definition with Controller. Name our first new file “HelloWorldScreen”, and the second one “HelloUniverseScreen”:

Open the .xib files up in Interface builder and add a label to the HelloWorldScreen that says “Hello World,” and add a label to the HelloUniverseScreen that says “Hello Universe” as shown in the following screenshot:

Now, let’s add a Navigation Controller to the Main Window. To do this, open the MainWindow.xib file in Interface Builder, and drag a Navigation Controller from the Library Window onto the Document Window:

The Navigation Controller has several parts to it:

  • Navigation Controller – This is the main Controller that handles the navigation events and wires it all up together.
  • Navigation Bar – This is the bar along the top that allows the user to see where they’re at in the navigation hierarchy, and navigate backwards.
  • View Controller – This is the View Controller for the view that it will hold.
  • Navigation Item – The navigation item is in the Navigation Bar and is what actually has the button for navigating, as well as the title.

Next, let’s add a Table View to the Navigation Controller, so we can create a list of links of our screens. To do this, drag a UITableView from the library onto the View Controller in the Navigation Controller:

Let’s change the title of the Navigation Bar. Double click on the top-bar in the Navigation Controller and type in “Hello World Home.”:

Do I have to use a Table View to hold my Navigation Items?

No, you can put just about anything into your View Controller. As we’ll see later, when we want to navigate to a new screen, we call NavigationController.PushViewController and pass it our View Controller for the screen we want to go to. We could just as easily do this when a user clicks on a button.

Now that we have our Navigation Controller we need to make both it, and it’s associated Table View available to our code-behind. We need to make the Navigation Controller available so that we can push our View Controllers onto it, and we need the Table View available so that we can populate it with the names of the screens we can navigate to.

To do this, lets create Outlets for them as we did in Part 1. Let’s call them mainNavigationController for the Navigation Controller, and mainNavTableView for the Table View. Make sure to create them on your AppDelegate. When you’re done, your Connection Inspector should look like the following:

Next, we need to set our Navigation Controller to show up when we start the app. Remember the commented out Window.AddSubview in Main.cs from before? Well, this is where we’ll use it. Let’s change that line to the following:

// If you have defined a view, add it here: 

window.AddSubview (this.mainNavigationController.View); 

AddSubView is a lot like AddControl in WPF, ASP.NET, etc. By passing it the View property of our mainNavigationController, we’re telling the window to display the Navigation Controller interface.

Let’s run the application now, we should see the following:

Our Navigation Controller shows up, but there are no links yet to our other screens. In order to get links, we have to populate the Table View with Data. In order to do this we have to create a UITableViewDataSource and bind it to the DataSource property of our Table View. In traditional .NET programming, you would bind anything that implements IEnumerable to the DataSource property, specify some of the data binding parameters (such as what fields to use), and it would magically data bind. In Cocoa it works a little differently, as we’ll see, the DataSource itself is called whenever the object that it’s bound to needs to build out a new item, and the DataSource is responsible for actually creating the item.

Before we implement our DataSource though, let’s create the actual item that our DataSource we’ll use. Create a new class called NavItem. To do this right-click on your project and choose Add : New File, select General : Empty Class, and name it “NavItem”:

Now, put the following code in there:

using System;

using MonoTouch.UIKit;


namespace Example_HelloWorld_2

{

	//========================================================================

	/// <summary>

	/// 

	/// </summary>

	public class NavItem

	{

		//=============================================================

		#region -= declarations =-



		/// <summary>

		/// The name of the nav item, shows up as the label

		/// </summary>

		public string Name

		{

			get { return this._name; }

			set { this._name = value; }

		}

		protected string _name;


		/// <summary>

		/// The UIViewController that the nav item opens. Use this property if you 

		/// wanted to early instantiate the controller when the nav table is built out,

		/// otherwise just set the Type property and it will lazy-instantiate when the 

		/// nav item is clicked on.

		/// </summary>

		public UIViewController Controller

		{

			get { return this._controller; }

			set { this._controller = value; }

		}

		protected UIViewController _controller;



		/// <summary>

		/// The Type of the UIViewController. Set this to the type and leave the Controller
 
		/// property empty to lazy-instantiate the ViewController when the nav item is 

		/// clicked.

		/// </summary>

		public Type ControllerType

		{

			get { return this._controllerType; }

			set { this._controllerType = value; }

		}

		protected Type _controllerType;



		/// <summary>

		/// a list of the constructor args (if neccesary) for the controller. use this in

		/// conjunction with ControllerType if lazy-creating controllers.

		/// </summary>

		public object[] ControllerConstructorArgs

		{

			get { return this._controllerConstructorArgs; }

			set

			{

				this._controllerConstructorArgs = value;


				
				this._controllerConstructorTypes = new Type[this._controllerConstructorArgs.Length];

				for (int i = 0; i < this._controllerConstructorArgs.Length; i++)

				{

					this._controllerConstructorTypes[i] = this._controllerConstructorArgs[i].GetType ();

				}

			}

		}

		protected object[] _controllerConstructorArgs = new object[] {


			
		};



		/// <summary>

		/// The types of constructor args.

		/// </summary>

		public Type[] ControllerConstructorTypes

		{

			get { return this._controllerConstructorTypes; }

		}

		protected Type[] _controllerConstructorTypes = Type.EmptyTypes;



		#endregion


		//========================================================================



		//========================================================================

		#region -= constructors =-



		public NavItem ()

		{

		}



		public NavItem (string name) : this()

		{

			this._name = name;

		}



		public NavItem (string name, UIViewController controller) : this(name)

		{

			this._controller = controller;

		}



		public NavItem (string name, Type controllerType) : this(name)

		{

			this._controllerType = controllerType;

		}



		public NavItem (string name, Type controllerType, object[] controllerConstructorArgs) : this(name, controllerType)

		{

			this.ControllerConstructorArgs = controllerConstructorArgs;

		}



		
		#endregion

		//===============================================================

	}

}

This class is fairly simple. Let’s first explore the properties:

  • Name – The Name of the screen as we want to see it in our Navigation Table.
  • Controller – The actual UIViewController of our screen.
  • ControllerType – This is the type of the UIVeiwController of our screen, we can use this to late-instantiate the UIViewController by simply storing the type of the controller and creating it only when necessary.
  •  ControllerConstructorArgs – If your UIViewController has any constructor arguments that you want to pass, this is where you put them. In our example, we won’t need this, so we can ignore it for now, but I left it in here because it’s a handy class to build on later.
  • ControllerConstructorTypes – This is a read-only property that pulls the types from the ControllerConstructorArgs, it’s used for instantiating the control.

The rest of the class is just some basic constructors.

Now that we have NavItem, let’s create a DataSource for our Navigation Table View that actually uses it. Create a new class called NavTableViewDataSource. Do this just as you created NavItem.

Now, put the following code in there:

using System;

using System.Collections.Generic;

using MonoTouch.UIKit;

using MonoTouch.Foundation;



namespace Example_HelloWorld_2

{

	//========================================================================

	//

	// The data source for our Navigation TableView

	//

	public class NavTableViewDataSource : UITableViewDataSource

	{



		/// <summary>

		/// The collection of Navigation Items that we bind to our Navigation Table

		/// </summary>

		public List<NavItem> NavItems

		{

			get { return this._navItems; }

			set { this._navItems = value; }

		}

		protected List<NavItem> _navItems;



		/// <summary>

		/// Constructor

		/// </summary>

		public NavTableViewDataSource (List<NavItem> navItems)

		{

			this._navItems = navItems;

		}



		/// <summary>

		/// Called by the TableView to determine how man cells to create for that particular section.

		/// </summary>

		public override int RowsInSection (UITableView tableView, int section)

		{

			return this._navItems.Count;

		}



		/// <summary>

		/// Called by the TableView to actually build each cell. 

		/// </summary>

		public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)

		{

			//---- declare vars

			string cellIdentifier = "SimpleCellTemplate";


			
			//---- try to grab a cell object from the internal queue

			var cell = tableView.DequeueReusableCell (cellIdentifier);

			//---- if there wasn't any available, just create a new one

			if (cell == null)

			{

				cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier);

			}


			
			//---- set the cell properties

			cell.TextLabel.Text = this._navItems[indexPath.Row].Name;

			cell.Accessory = UITableViewCellAccessory.DisclosureIndicator;


			
			//---- return the cell

			return cell;

		}

	}

	//====================================================================

}

Let’s take at look at this. The first part is our List<NavItem> collection. This is just a collection of our NavItem objects. We then have a basic constructor that forces you to initialize the NavTableViewDataSource with your NavItems.

We then override RowsInSection. Table Views can have multiple sections with items in each section. RowsInSection should return the number of items in whatever section it passes in via the section parameter. In our case, we only have one section, so we return the Count property of our NavItem collection.

The last method GetCell is actually where the interesting bits of the data binding happen. This method is called by the UITableView for each row it needs to build. You can use this method to build out each row of your Table to look like whatever you want.

The first thing we do here is try to get a UITableViewCell object from the TableView via the DequeueReusableCell method. The TableView keeps an internal pool of UITableViewCell objects, based on CellIdentifiers. This allows you to create a custom template for a UITableViewCell one time, and then reuse that object, instead of each time GetCell is called and can increase performance. The first time that we call DequeueReusableCell, this should return nothing, so we create a new UITableViewCell. Every time after, the UITableViewCell should already exist, so we can just reuse it.

We’re using the Default cell style, which gives us just a few options for customization, so the next thing we do is set the TextLabel.Text property to be the Name of our NavItem. Next, we set the Accessory property to use the DisclosureIndicator, which is just a simple arrow that shows up on the right side of our Navigation Item.

Now that we have our UITableViewDataSource created, it’s time to put it to use. Open up Main.cs in MonoDevelop and add the following line to our AppDelegate class:

protected List<NavItem> _navItems = new List<NavItem> (); 

This will hold our NavItem objects.

Next, add the following code to the FinishedLaunching method, right after Window.MakeKeyAndVisible():

//---- create our list of items in the nav 

this._navItems.Add (new NavItem ("Hello World", typeof(HelloWorldScreen))); 

this._navItems.Add (new NavItem ("Hello Universe", typeof(HelloUniverseScreen))); 

//---- configure our datasource 

this.mainNavTableView.DataSource = new NavTableViewDataSource (this._navItems); 

All we’re doing here is creating two NavItem objects, and adding them to our _navItems collection. Then, we create a NavTableViewDataSource and bind it to our Navigation Table View.

Our AppDelegate class should now look like this, after we’ve put the preceding code in:

// The name AppDelegate is referenced in the MainWindow.xib file.

public partial class AppDelegate : UIApplicationDelegate

{

	protected List<NavItem> _navItems = new List<NavItem> ();



	// This method is invoked when the application has loaded its UI and its ready to run

	public override bool FinishedLaunching (UIApplication app, NSDictionary options)

	{

	// If you have defined a view, add it here:

		window.AddSubview (this.mainNavigationController.View);


		
		window.MakeKeyAndVisible ();


			
		//---- create our list of items in the nav

		this._navItems.Add (new NavItem ("Hello World", typeof(HelloWorldScreen)));

		this._navItems.Add (new NavItem ("Hello Universe", typeof(HelloUniverseScreen)));


			
		//---- configure our datasource

		this.mainNavTableView.DataSource = new NavTableViewDataSource (this._navItems);


			
		return true;

	}


	// This method is required in iPhoneOS 3.0

	public override void OnActivated (UIApplication application)

	{

	}

}

If you run your application now, you should see something like the following:

We now have our Navigation Items being built out, but nothing happens when we click on them. When you click on an item, the UITableView raises an event, but it needs us to give it a special class, called a UITableViewDelegate to actually listen to those events. To do this, create a new class in your project called “NavTableDelegate” and put in the following code:

using MonoTouch.Foundation;

using MonoTouch.UIKit;

using System;

using System.Collections.Generic;

using System.Reflection;



namespace Example_HelloWorld_2

{

//========================================================================

//

// This class receives notifications that happen on the UITableView

//

public class NavTableDelegate : UITableViewDelegate

{

	//---- declare vars

	UINavigationController _navigationController;

	List<NavItem> _navItems;



	//========================================================================

	/// <summary>

	/// Constructor

	/// </summary>

	public NavTableDelegate (UINavigationController navigationController, List<NavItem> navItems)

	{

		this._navigationController = navigationController;

		this._navItems = navItems;

	}

	//========================================================================



	//========================================================================

	/// <summary>

	/// Is called when a row is selected

	/// </summary>

	public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
	{
		//---- get a reference to the nav item

		NavItem navItem = this._navItems[indexPath.Row];



		//---- if the nav item has a proper controller, push it on to the NavigationController

		// NOTE: we could also raise an event here, to loosely couple this, but isn't neccessary,

		// because we'll only ever use this this way

		if (navItem.Controller != null)

		{

			this._navigationController.PushViewController (navItem.Controller, true);

			//---- show the nav bar (we don't show it on the home page)

			this._navigationController.NavigationBarHidden = false;

		} else

		{

			if (navItem.ControllerType != null)

			{

				//----

				ConstructorInfo ctor = null;



				//---- if the nav item has constructor aguments

				if (navItem.ControllerConstructorArgs.Length > 0)

				{

					//---- look for the constructor

					ctor = navItem.ControllerType.GetConstructor (navItem.ControllerConstructorTypes);

				} else

				{

					//---- search for the default constructor

					ctor = navItem.ControllerType.GetConstructor (System.Type.EmptyTypes);

				}



				//---- if we found the constructor

				if (ctor != null)

				{

					//----

					UIViewController instance = null;



					if (navItem.ControllerConstructorArgs.Length > 0)

					{

						//---- instance the view controller

						instance = ctor.Invoke (navItem.ControllerConstructorArgs) as UIViewController;

					} else

					{

						//---- instance the view controller

						instance = ctor.Invoke (null) as UIViewController;

					}



					if (instance != null)

					{

						//---- save the object

						navItem.Controller = instance;



						//---- push the view controller onto the stack

						this._navigationController.PushViewController (navItem.Controller, true);

					} else

					{

						Console.WriteLine ("instance of view controller not created");

					}

				} else

				{

					Console.WriteLine ("constructor not found");

				}

			}

		}

	}
	//==================================================================

}

//========================================================================

}

The first part of this class is a couple of declarations for a UINavigationController and a Collection of NavItem objects, and then a constructor that requires them. We’ll see why we need these in the next method, RowSelected.

RowSelected is called by our UITableView when a user clicks on a row, and it gives us a reference to the UITableView that it’s from, as well as the NSIndexPath (index of section and row) that the user clicked on. The first thing we do is look up the NavItem based on that NSIndexPath. Next, we push the UIViewController of that NavItem onto our NavigationController. If the Controller is null, then we try to instantiate it based on the type.

These last two operations are why we need a reference to both the NavItem collection and the NavigationController.

Now that we have our UITableViewDelegate, let’s wire it up. Go back over to Main.cs, and add the following line right after we set our DataSource property in our AppDelegate class:

this.mainNavTableView.Delegate = new NavTableDelegate (this.mainNavigationController, this._navItems); 

This creates a new NavTableDelegate class, with a reference to our Navigation Controller and our collection of NavItems, and tells our mainNavTable to use it to handle events.

Our AppDelegate class in Main.cs should now look something like this:

// The name AppDelegate is referenced in the MainWindow.xib file.

public partial class AppDelegate : UIApplicationDelegate

{

	protected List<NavItem> _navItems = new List<NavItem> ();



	// This method is invoked when the application has loaded its UI and its ready to run

	public override bool FinishedLaunching (UIApplication app, NSDictionary options)

	{

		// If you have defined a view, add it here:

		window.AddSubview (this.mainNavigationController.View);


		
		window.MakeKeyAndVisible ();


		
		//---- create our list of items in the nav

		this._navItems.Add (new NavItem ("Hello World", typeof(HelloWorldScreen)));

		this._navItems.Add (new NavItem ("Hello Universe", typeof(HelloUniverseScreen)));


		
		//---- configure our datasource

		this.mainNavTableView.DataSource = new NavTableViewDataSource (this._navItems);

		this.mainNavTableView.Delegate = new NavTableDelegate (this.mainNavigationController, this._navItems);


		
		return true;

	}



	// This method is required in iPhoneOS 3.0

	public override void OnActivated (UIApplication application)

	{

	}

}

Let’s run our application now and see what happens, click on “Hello World” and you should see:

Notice that we automatically get a “Hello World Home” button at the top that allows us to go back to our home screen. Clicking on “Hello Universe” should get you:

Congratulations! You should now understand the basic concept of how to work with multiple screens in a MonoTouch iPhone application, as well as have a basic understanding of how the UINavigationController works!

Sample Code

Rate this Article

Adoption
Style

BT