Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / iPhone

MvvmCross TipCalc - Step 3: Creating an iOS UI

5.00/5 (4 votes)
21 Apr 2013Ms-PL8 min read 44.4K  
Step 3 in the TipCalc tutorial for MvvmCross v3 - Hot Tuna

Introduction

This article is step 3 in the TipCalc tutorial for MvvmCross v3 - Hot Tuna!

The Story So Far...

We started with the goal of creating an app to help calculate what tip to leave in a restaurant.

We had a plan to produce a UI based on this concept:

Sketch

To satisfy this, we built a 'Core' Portable Class Library project which contained:

  • our 'business logic' - ICalculation
  • our ViewModel - TipViewModel
  • our App which contains the application wiring, including the start instructions.

We then added our first User Interface - for Xamarin.Android:

Android

For our next project, let's shift to Xamarin.iOS.

To create an iPhone MvvmCross UI, you can use the Visual Studio project template wizards, but here we'll instead build up a new project 'from empty', just as we did for the Core and Android projects.

Also, to work with iPhone, for now we will switch to working on the Mac with Xamarin Studio.

Create a New iOS UI Project

Add a new project to your solution - a 'Xamarin.iOS' iPhone application with name TipCalc.UI.Touch.

Within this, you'll find the normal iOS application constructs:

  • the Resources folder
  • the info.plist 'configuration' information
  • the AppDelegate.cs class
  • the Main.cs class
  • the MyViewController.cs class

Delete MyViewController.cs

No-one really needs an MyViewController. Smile | <img src= " align="top" src="http://www.codeproject.com/script/Forums/Images/smiley_smile.gif" />  

Also, delete MyViewController.xib (if there is one).

Add References

Add references to CoreCross, Binding and MvvmCross - PCL versions

Add references to the new project for the portable libraries:

  • Cirrious.CrossCore.dll
    • core interfaces and concepts including Trace, IoC and Plugin management
  • Cirrious.MvvmCross.Binding.dll
    • DataBinding classes - which you'll mainly use from C# code
  • Cirrious.MvvmCross.dll
    • Mvvm classes - including base classes for your views and viewmodels

Normally these will be found in a folder path like {SolutionRoot}/Libs/Mvx/Portable/.

Add references to CoreCross, Binding and MvvmCross - iOS specific versions

Add references to the new project for the Xamarin.iOS specific libraries:

  • Cirrious.CrossCore.Touch.dll
  • Cirrious.MvvmCross.Binding.Touch.dll
  • Cirrious.MvvmCross.Touch.dll

Each of these extends the functionality of its PCL counterpart with iOS specific additions.

Normally, these will be found in a folder path like {SolutionRoot}/Libs/Mvx/Touch/.

Also, within that same folder, you need to add:

Add a reference to TipCalc.Core.csproj

Add a reference to your TipCalc.Core project - the project we created in the last step which included:

  • your Calculation service
  • your TipViewModel
  • your App wiring

Add a Setup Class

Just as we said during the Android construction, Every MvvmCross UI project requires a Setup class.

This class sits in the root namespace (folder) of our UI project and performs the initialisation of the MvvmCross framework and your application, including: 

  • the Inversion of Control (IoC) system
  • the MvvmCross data-binding
  • your App and its collection of ViewModels
  • your UI project and its collection of Views

Most of this functionality is provided for you automatically. Within your iOS UI project, all you have to supply are:

  • your App - your link to the business logic and ViewModel content

For TipCalc, here's all that is needed in Setup.cs:

C#
using System;
using Cirrious.MvvmCross.Touch.Platform;
using TipCalc.Core;
using Cirrious.MvvmCross.Touch.Views.Presenters;

namespace TipCalc.UI.Touch
{
    public class Setup : MvxTouchSetup
    {
        public Setup (MvxApplicationDelegate appDelegate, IMvxTouchViewPresenter presenter)
            : base(appDelegate, presenter)
        {
        }

        protected override Cirrious.MvvmCross.ViewModels.IMvxApplication CreateApp ()
        {
            return new App();
        }
    }
}

Modify the AppDelegate to use Setup

Your AppDelegate provides a set of callback that iOS uses to inform you about events in your application's lifecycle.

To use this AppDelegate within MvvmCross, we need to:

  • modify it so that it inherits from MvxApplicationDelegate instead of UIApplicationDelegate:

    C#
    public partial class AppDelegate : MvxApplicationDelegate
  • modify it so that the method that is called on startup (FinishedLaunching) does some UI application setup:

    • create a new presenter - this is the class that will determine how Views are shown - for this sample, we choose a 'standard' one:

      C#
      var presenter = new MvxTouchViewPresenter(this, window);
    • create and call Initialize on a Setup:

      C#
      var setup = new Setup(this, presenter);
      setup.Initialize();
    • with Setup completed, use the Mvx Inversion of Control container in order to find and Start the IMvxAppStart object:

      C#
      var startup = Mvx.Resolve<IMvxAppStart>();
      startup.Start();

Together, this looks like:

C#
using System;
using System.Collections.Generic;
using System.Linq;

using MonoTouch.Foundation;
using MonoTouch.UIKit;
using Cirrious.MvvmCross.Touch.Platform;
using Cirrious.MvvmCross.Touch.Views.Presenters;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.CrossCore.IoC;

namespace TipCalc.UI.Touch
{
    [Register("AppDelegate")]
    public partial class AppDelegate : MvxApplicationDelegate
    {
        UIWindow window;

        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            window = new UIWindow(UIScreen.MainScreen.Bounds);

            var presenter = new MvxTouchViewPresenter(this, window);

            var setup = new Setup(this, presenter);
            setup.Initialize();

            var startup = Mvx.Resolve<IMvxAppStart>();
            startup.Start();

            window.MakeKeyAndVisible();

            return true;
        }
    }
}

Add your View

Create an initial UIViewController

Create a Views folder:

Within this, add a new 'iPhone UIViewController' and call it TipView.

This will generate:

  • TipView.cs
  • TipView.designer.cs
  • TipView.xib

Edit the XIB layout

Double click on the XIB file.

This will open the XIB editor within xCode.

Just as we did with Android, I won't go into depth here about how to use the XIB iOS editor - instead I'll just cover the bare basics, and I'll also try to provide some comparisons for those familiar with XAML.

Drag/drop from the 'Object Library' to add:

  • some UILabels for showing static text - these are like TextBlocks
  • a UITextField for editing the SubTotal - this is like a TextBox
  • a UISlider for editing the Generosity - this is like a ProgressBar
  • a UILabel for showing the Tip result - this is like a TextBlock

Using drag and drop, you should be able to quite quickly generate a design similar to:

design

Create 'outlets' within the XIB editor

Once you have your UI drawn, you can then link those UI displayed fields to ObjectiveC variables called outlets.

After you have done this, then the Xamarin.iOS tools within Xamarin Studio will then automatically detect these changes and map those ObjectiveC fields to C# properties back in your iOS app.

To start doing this, you need to open the 'Assistant Editor' from menu option 'View' -> 'Assistant Editor' -> 'Show Assistant Editor' within xCode. This will display a small pane with some ObjectiveC code in it - this is the 'Assistant Editor'.

Once you have done this, then you can ctrl-click (right click) on each of the 3 SubTotal, Generosity and Tip fields in turn.

outlet

For each of them:

  • ctrl-click the UI field in the designer
  • this will 'pop up' a window listing the 'outlets and actions' available for this field
  • find the one marked 'New Referencing Outlet'
  • click on the circle to the right of 'New Referencing Outlet' and drag that to the Assistant Editor
  • drop the field on the Assistant Editor
  • it will then ask you to provide a name for your outlet option

Following this process, you should be able to create three ObjectiveC outlet variables for the three fields:

  • SubTotalTextField
  • GenerositySlider
  • TipLabel

With this done, save your xCode changes (using the File Menu) and then exit xCode.

Edit TipView.cs

Back in Xamarin Studio, you should now see that the Xamarin products have updated the TipView.designer.cs file - it will now contain three [Outlet] properties with those same three names:

  • SubTotalTextField
  • GenerositySlider
  • TipLabel

Close the TipView.designer.cs file - this file is an auto-generated partial class, and Xamarin Studio can regenerate it at any time - so there is no point in editing it yourself.

Instead open TipView.cs - this contains an editable part of the same partial class.

Because we want our TipView to be not only a UIViewController but also an Mvvm View, then change the inheritance of TipView so that it inherits from MvxViewController.

C#
public class TipView : MvxViewController

Now, to link TipView to TipViewModel, create a public new TipViewModel ViewModel property - exactly as you did in Xamarin.Android:

C#
public new TipViewModel ViewModel
{
    get { return (TipViewModel) base.ViewModel; }
    set { base.ViewModel = value; }
}

To add the data-binding code, go to the ViewDidLoad method in your TipView class. This is a method that will be called after the View is loaded within iOS but before it is displayed on the screen.

This makes ViewDidLoad a perfect place for us to call some data-binding extension methods which will specify how we want the UI data-bound to the ViewModel:

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

    this.Bind (this.TipLabel, (TipViewModel vm) => vm.Tip ); 
    this.Bind (this.SubTotalTextField, (TipViewModel vm) => vm.SubTotal );
    this.Bind (this.GenerositySlider, (TipViewModel vm) => vm.Generosity );
}

What this code does is to generate 'in code' exactly the same type of data-binding information as we generated 'in XML' in Android.

Note that before the calls to this.Bind are made, then we first call base.ViewDidLoad(). This is important because base.ViewDidLoad() is where MvvmCross locates the TipViewModel that this TipView will bind to.

Altogether, this looks like:

C#
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using Cirrious.MvvmCross.Touch.Views;
using Cirrious.MvvmCross.Binding.BindingContext;
using TipCalc.Core;

namespace TipCalc.UI.Touch
{
    public partial class TipView : MvxViewController
    {
        public new TipViewModel ViewModel
        {
            get { return (TipViewModel)base.ViewModel; }
            set { base.ViewModel = value; }
        }

        public TipView () : base ("TipView", null)
        {
        }

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

            this.CreateBinding (this.TipLabel).To( (TipViewModel vm) => vm.Tip ).Apply(); 
            this.CreateBinding (this.SubTotalTextField).To( (TipViewModel vm) => vm.SubTotal ).Apply();
            this.CreateBinding (this.GenerositySlider.To( (TipViewModel vm) => vm.Generosity ).Apply();
        }
    }
}

Binding in Xamarin.iOS

You will no doubt have noticed that data-binding in iOS looks very different to the way it looked in Android - and to what you may have expected from XAML.

This is because the XIB format used in iOS is a lot less human manipulable and extensible than the XML formats used in Android AXML and Windows XAML - so it makes more sense to use C# rather than the XIB to register our bindings.

Within this section of the tutorial, all of our iOS bindings look like:

C#
this.CreateBinding (this.TipLabel).To ((TipViewModel vm) => vm.Tip ).Apply(); 

What this line means is:

  • bind the TipLabel's default binding property - which happens to be a property called Text
  • to the ViewModel's Tip property

As with Android, this will be a TwoWay binding by default - which is different to what XAML developers may expect to see.

If you had wanted to specify the TipLabel property to use Text explicitly instead of relying on the default, then you could have done this with: 

C#
this.CreateBinding (this.TipLabel).For(label => label.Text).To( (TipViewModel vm) => vm.Tip ).Apply(); 

In later topics, we'll cover more on binding in iOS, including more on binding to non-default fields; other code-based binding code mechanisms; custom bindings; using ValueConverters; and creating bound sub-views.

The iOS UI is Complete!

At this point, you should be able to run your application.

When it starts... you should see:

v1

This seems to work perfectly, although you may notice that if you tap on the SubTotal property and start entering text, then you cannot afterwards close the keyboard.

This is a View concern - it is a UI problem. So we can fix it just in the iOS UI code - in this View. For example, to fix this here, you can add a gesture recognizer to the end of the ViewDidLoad method like:

C#
View.AddGestureRecognizer(new UITapGestureRecognizer(() => {
    this.SubTotalTextField.ResignFirstResponder();
})); 

Moving On...

There's more we could do to make this User Interface nicer and to make the app richer... but for this first application, we will leave it here for now. 

Let's move on to Windows!

The Articles

History 

  • 22nd March, 2013 - First submission    

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)