Resized-
Written By Jon Peppers

Developing for iPhone – Part 4: Controlling the Hubble Space Telescope with MVC

February 28, 2011 | development / technology / app development

So for this part of the series, I wanted to explore MVC by writing a sample application that reuses code between an iPhone app and a WPF windows application.  The goal here is to lessen the amount of code required to be written on the iPhone, because what developer wants to use a Mac, right?  But this should also cut down development time, too, and should make it easier to port the same app to Windows Phones or other platforms.

So what should our app do?  Why not measure some planets?  Let’s make a little app that contacts the Hubble space telescope to get an updated measurement of each planet.  The operation will take a little time and will run in the background with a progress indicator, and when completed, will populate a list for the user to see.

Well… we will not really contact the Hubble telescope, but let’s write a simple Model class to simulate it:

    /// <summary>
    /// Class exposing operations to the Hubble space telescope
    /// </summary>
    public class Telescope
    {
        /// <summary>
        /// Event when MeasurePlanets method has data available
        /// </summary>
        public event EventHandler<PlanetEventArgs> PlanetsMeasured = delegate { };

        /// <summary>
        /// Asynchronously sends message to the hubble space telescope for the current diameter of the planets.
        /// OK, not really, just sleeps for 3 sec and fires the PlanetsMeasured event
        /// </summary>
        public void MeasurePlanets()
        {
            ThreadPool.QueueUserWorkItem(_ =>
            {
                Thread.Sleep(3000);

                PlanetsMeasured(this, new PlanetEventArgs(new Planet[]
                    {
                        new Planet { Name = "Mercury", Diameter = 4880 },
                        new Planet { Name = "Venus", Diameter = 12104 },
                        new Planet { Name = "Earth", Diameter = 12756 },
                        new Planet { Name = "Mars", Diameter = 6794 },
                        new Planet { Name = "Jupiter", Diameter = 142984 },
                        new Planet { Name = "Saturn", Diameter = 120536 },
                        new Planet { Name = "Uranus", Diameter = 51118 },
                        new Planet { Name = "Neptune", Diameter = 49532 },

                        //Wait! this isn't a planet
                        //new Planet { Name = "Pluto", Diameter = 2274 },
                    }));
            });
        }
    }

Simple enough right?  In real life, our Model class would be a little more complicated: running SQL queries, contacting web servers, etc.  This should be plenty for us to demonstrate MVC.

So to utilize this in a MVC application, we need 2 more pieces: the Controller and the View.

Our WPF app will be setup like so:

  • We’ll write some Xaml code with a ListView, refresh Button, and some kind of progress indicator—this is the View.
  • We’ll write a TelescopeController class that exposes everything the View needs to function: a collection of planets, a “Refresh” command, and an “IsBusy” property—this is the Controller.

NOTE: in reality this is the MVVM pattern, let’s ignore that for the sake of this article.

So a snippet of our View:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
       
        <!--Refresh button-->
        <Button HorizontalAlignment="Left" Width="150" Height="30" Command="{Binding Refresh}">Refresh</Button>
       
        <!--List of our planets-->
        <ListView Grid.Row="1" ItemsSource="{Binding Planets}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Planet" DisplayMemberBinding="{Binding Name}" />
                    <GridViewColumn Header="Diameter" DisplayMemberBinding="{Binding Path=Diameter, StringFormat={}{0} KM}" />
                </GridView>
            </ListView.View>      
        </ListView>
       
        <!--Our busy indicator-->
        <TextBlock
           Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center"
           Text="Please Wait..." Visibility="{Binding IsBusy, Converter={StaticResource Converter}}" />
       
    </Grid>

And our Controller:

    /// <summary>
    /// Controller that coordinates work to the Telescope Model
    /// </summary>
    public sealed class TelescopeController : PropertyChangedBase
    {
        //Create our Model class for use
        private Telescope _telescope = new Telescope();

        #region Properties exposed to View

        private string _title = "Hubble Space Telescope";

        /// <summary>
        /// The title of the window
        /// </summary>
        public string Title
        {
            get { return _title; }
            set { _title = value; OnPropertyChanged("Title"); }
        }

        private Planet[] _planets = null;

        /// <summary>
        /// List of planets
        /// </summary>
        public Planet[] Planets
        {
            get { return _planets; }
            set { _planets = value; OnPropertyChanged("Planets"); }
        }

        /// <summary>
        /// Command that refreshes the Planets property
        /// </summary>
        public DelegateCommand Refresh
        {
            get;
            private set;
        }

        private bool _isBusy = false;

        /// <summary>
        /// Indicates a search is in progress
        /// </summary>
        public bool IsBusy
        {
            get { return _isBusy; }
            set { _isBusy = value; OnPropertyChanged("IsBusy"); }
        }

        #endregion

        public TelescopeController()
        {
            //Setup our Refresh command
            Refresh = new DelegateCommand(() =>
            {
                Planets = null;
                IsBusy = true;
                _telescope.MeasurePlanets();
                Refresh.InvalidateCanExecute();
            },
            () => !IsBusy);

            //Hook up the event from our Model
            _telescope.PlanetsMeasured += (sender, e) =>
            {
                //We need to execute on the main thread, since the Telescope class works on a background thread
                Execute.OnUIThread(() =>
                {
                    IsBusy = false;
                    Planets = e.Planets;
                    Refresh.InvalidateCanExecute();
                });
            };
        }
    }

Simple enough for WPF, right? (I used a few helper classes in here, check example code at the bottom)

Here’s what we have to do for the iPhone:

  • Create a View in Interface Builder (IB): in the UIWindow, we’ll add a UITableView, UIBarButtonItem to refresh  and a UIActivityIndicatorView for progress
  • Still in IB, we have to hook up a UITableViewController with a “Refresh” action and an “Outlet” for the indicator
  • In MonoDevelop, we setup a TelescopeController that inherits UITableViewController
  • We also have to implement UITableViewSource to populate our UITableView
  • Next we have to explicitly interact with all of our View objects within the controller

 

Here’s the full code for the TelescopeController for iPhone:

  /// <summary>
  /// Controller that coordinates with the Telescope Model class
  /// </summary>
  public partial class TelescopeController : UITableViewController
  {
    //A few member variables
    private Telescope _telescope = null;
    private DataSource _dataSource = null;
   
    #region Data Source
   
    /// <summary>
    /// Data source for the UITableViewController
    /// </summary>
    private class DataSource : UITableViewSource
    {
      //ID for re-using UITableViewCells
      private const string _id = "TelescopeCell";
     
      public Planet[] Planets
      {
        get;
        set;
      }
     
      #region implemented abstract members of MonoTouch.UIKit.UITableViewSource
     
      //Returns number of items in list
      public override int RowsInSection (UITableView tableview, int section)
      {
        return Planets == null ? 0 : Planets.Length;
      }
     
      //Returns the actual UITableViewCell
      public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
      {
        var cell = tableView.DequeueReusableCell(_id);
        var planet = Planets[indexPath.Row];
        if (cell == null)
        {
          cell = new UITableViewCell(UITableViewCellStyle.Value1, _id);
        }
        cell.TextLabel.Text = planet.Name;
        cell.DetailTextLabel.Text = string.Format("{0} KM", planet.Diameter);  
        return cell;
      }
     
      #endregion     
    }
   
    #endregion
   
    #region Constructors
 
    public TelescopeController (IntPtr handle) : base (handle)
    {
      Initialize();
    }
 
    [Export("initWithCoder:")]
    public TelescopeController (NSCoder coder) : base(coder)
    {
      Initialize();
    }
 
    protected virtual void Initialize()
    {
      _telescope = new Telescope();
      _dataSource = new DataSource();
    }
 
    #endregion
   
    public override void ViewDidLoad()
    {
      base.ViewDidLoad();
     
      //Set our data source
      TableView.Source = _dataSource;
     
      //Hook up our telescope event
      _telescope.PlanetsMeasured += (sender, e) =>
      {
        _dataSource.Planets = e.Planets;
       
        BeginInvokeOnMainThread(() =>
        {
          View.UserInteractionEnabled =
            _indicator.Hidden = true;
          TableView.ReloadData();
        });
      };
    }
   
    /// <summary>
    /// Action to refresh the list of Planets
    /// </summary>
    /// <param name="item">
    /// A <see cref="UIBarButtonItem"/>
    /// </param>
    partial void Refresh(UIBarButtonItem item)
    {
      //Set our progress indicator and disable the view
      View.UserInteractionEnabled =
        _indicator.Hidden = false;
     
      //Clear out our list, just for aesthetics
      _dataSource.Planets = null;
      TableView.ReloadData();
     
      //Measure the planets
      _telescope.MeasurePlanets();
    }
  }

Seems a little more complicated, right?  And there’s definitely some stuff in here that seems fishy to me.

What is wrong with Apple’s version of MVC:

  • Everything is very tightly coupled:
  • --> Controllers are created in IB, just like the View
  • --> View objects are completely accessible from within the controller, this is also the only way that controllers can interact with the view—directly
  • --> So you can’t really re-use controllers across a WPF app and an iPhone app (nor Android for that matter)
  • Unit testing your controllers on the iPhone is just not possible
  • --> It is possible to use NUnit in MonoDevelop, but to run NUnit on the iPhone or in the simulator, someone would need to port NUnit to iOS (not something anyone has tackled yet).
  • --> You can’t use the iOS SDK while running Mono on the Mac, and all your controllers directly use iOS View objects, so testing almost goes out the window here.  You really can only test your Model classes in MonoDevelop, and why not just set that up in Visual Studio on Windows?
  • Apple’s iOS SDK seems overcomplicated for no reason
  • --> You have to inherit UITableViewController and UITableViewSource just to get a simple list to be displayed.  Yeah it is very flexible as far as what you can do with it, but has way too much boilerplate code to get setup.
  • --> Various Attributes must be set to hook up your controllers to what you setup in IB, and messing these up cause poorly worded error messages (crashes) at runtime.
  • Controllers are setup in IB, pushing MVC into the UI design.
  • --> To me, this seems like it would hinder a graphic designer from designing views for an iPhone app, as it requires them to understand the MVC design pattern to know what is going on.
  • --> In WPF, a designer could easily create Views in Expression Blend and only have to setup bindings to the different properties or commands—something they could even leave out for the developer to come in and setup later.  You just can’t do this when developing for iPhone.
  • --> Interface Builder is also painful to use for a Windows developer in general: you have to drag things around, and attach weird little lines, etc.  (You have to try it to see what I mean here, but I think I threw up a little when I used IB the first time around).
  • You have to use a Mac to develop with… Need I say more!

Well, did Apple get anything right?

  • At least they’re using MVC
  • Hooking up the command (Action) for the Refresh button is pretty easy and intuitive (passing in the UIBarButtonItem is actually optional).
  •  The default look of most UI controls are great, in reality you don’t need a graphic designer to do too much to develop an aesthetically pleasing app.

At least Novell ported enough of the .Net base class libraries to where we could completely reuse our Model classes with no additional work whatsoever.

For full code examples, check out: Hitcents Examples on Codeplex

Depressed on learning to develop for the iPhone? Well, there are some tricks you can do to clean up MVC on the iPhone, which has definitely called for some deep thinking on my part.

Next time I’ll simplify the iPhone app to be as elegant as possible, hook up IoC containers in both applications, promote the WPF app to using the Caliburn MVVM framework, and even setup unit testing.  I bet you guys just can’t wait!

Share this Article

Related Articles