Many .NET developers working on the Windows platform don’t realize that lots of existing .NET code can easily be ported to all the popular mobile platforms, including iOS and Android. Almost any .NET codebase, including Windows Forms, WPF, ASP.NET, and Silverlight, has sharable code that can be ported to Xamarin.iOS, Xamarin.Android, Windows Phone & Windows Store.
In this blog post I’m going to take you through the steps involved in porting an existing Silverlight application to Xamarin.iOS. In practice, you can use these techniques to port any .NET code to mobile, but for this example I hunted down a Silverlight MVC sample that I thought had potential as a mobile app. The Diet Calculator example uses a simple algorithm to calculate the daily calories required based on inputs provided by the user.
Here’s how the Silverlight sample looks:
And here’s how the ported application will look on the iPhone (including iOS 7):
You can download the code used in this post for the Xamarin.iOS DietCalculator sample from github.
Analyze how mobile your code is…
Xamarin’s .NET Mobility Scanner can determine the amount of code sharing and re-use you can expect from an existing codebase and provides a detailed road-map of remaining work required to mobilize it. The Mobility Scanner analyzes compiled .NET assemblies and compares the APIs used with the capabilities of each platform. Since we are porting a Silverlight app we can easily extract the Silverlight_MVC.dll from the .XAP and upload that to the Scanner.
Here’s a snapshot of the report generated:
The iOS column in the report shows about 71% of the Silverlight app code can be re-used. The detailed report indicates that all of the platform-specific method calls (ie. code that we can’t re-use across platforms) are user-interface specific. This is good because we want to build a user-interface that’s native to the platform anyway.
The fact that none of the business logic code is highlighted by the Mobility Scanner suggests that we can re-use all of the Models, Controllers and Helpers from the Silverlight_MVC project in our Xamarin.iOS application!
Separate Reusable Code into a Core Library
By following the principle of separation of responsibility by layering your application architecture and then moving core functionality that is platform agnostic into a reusable core library, you can maximize code sharing across platforms. Since we discovered that Models, Controllers and Helpers can be reused, let’s move them to a core project so that the same code can be referenced in Silverlight, iOS, Android, Windows Phone & Windows Store app projects. Don’t forget to check-out our detailed documentation on Building Cross Platform Applications.
Creating the Solution in Visual Studio
Xamarin 2.0, released earlier this year, introduced support for developing Xamarin.iOS applications in Visual Studio. While you must still have a Mac on your local network in order to compile and deploy the code, you can now write and debug C# code against the iOS APIs within Visual Studio on Windows (see configuring Visual Studio for iOS development for more details). Besides the support for iOS Development in Visual Studio, Xamarin also makes it possible for developers to use Visual Studio for Android development: you can access your iOS, Android, Windows Phone & Windows Store app projects from one solution, code in one programming language, with one IDE.
Start with a New Blank Solution(DietCalculator) and add two new projects:
- File > New > New Project > Visual C#> Class Library (DietCalculator.Core)
- File > New > New Project > Visual C# > iOS > Universal >Empty Project (DietCalculator.iOS)
You may add your existing .NET projects to this solution and reference all your code from one place. In this case, Silverlight projects have been added and modified to refer the reusable code in the Core library.
DietCalculator.Core
As stated above, this library will consist of all the reusable code from a Silverlight MVC project. Create a folder called “Core”, move the Models, Controllers, and Helper files from the Silverlight_MVC project to it and rename their namespaces for clarity. These code files will be then linked to each app project (iOS, Android, Windows Phone etc) using File Linking. Other approaches to enable source code sharing would include using a Portable Class Library project or the Project Linker; however, discussing the pros and cons of each approach is beyond the scope of this article.
Here’s how our solution looks now:
Building the User Interface
A key benefit of using Xamarin is that the application user interface uses native controls on each platform and is therefore indistinguishable from an application written in Objective-C for iOS or Java for Android. We can take advantage of native UI toolkits to present a familiar interface to the user, which in our case is CocoaTouch and UIKit on iOS. You can either lay out the controls in code or create complete screens using the design tools available such as Xcode or the Xamarin iOS Designer. Whichever approach you take, it is recommended that you read Apple’s Human Interface Guidelines (HIG). The fastest and simplest way to build a data-entry UI in Xamarin.iOS is to use MonoTouch.Dialog or MT.D for short. Let’s explore how we can leverage MT.D to build our user interface.
Using MonoTouch.Dialog
MT.D uses a declarative approach to build the UI using a flexible set of APIs that enable developers to quickly create HIG-compliant user interfaces. There are two APIs available in MT.D:
-
Low-level Elements API – The Elements API is based on creating a hierarchical tree of elements that represent screens and their components. The Elements API gives developers the most flexibility and control in creating UIs. Additionally, the Elements API has advanced support for declarative definition via JSON, which allows for both incredibly fast declaration, as well as dynamic UI generation from a server.
- High-Level Reflection API – Also known as the Binding API, in which classes are annotated with UI hints and then MT.D automatically creates screens based on the objects and provides a binding between what is displayed (and optionally edited) on screen, and the underlying object backing.
MT.D builds screens using the following four classes:
- DialogViewController
- RootElement
- Section
- Element
DietCalculator.iOS
Now that you have a fair idea about MT.D, let’s go step by step building the User Interface using it.
Step 1: Create a DietDialogViewController class
Create a public class DietDialogViewController which inherits from DialogViewController (or dvc for short). In Visual Studio, Right click on DietCalculator.iOS project, click add new item, select DialogViewController file template and provide a name. DVC further inherits from UITableViewController and therefore represents a screen with table. DVCs can be pushed onto a navigation controller just like a regular UITableViewController. Refer to Xamarin’s Tables in iOS documentation if you are new to UITableViewController.
Subclassing DialogViewController gives us the flexibility of customizing the look & feel of the view by overriding appropriate methods. Take a look at the code:
public class DietDialogViewController : DialogViewController { public DietDialogViewController (RootElement rootElement) : base(rootElement) { Style = UITableViewStyle.Grouped; } // ... public override void LoadView () { base.LoadView (); TableView.BackgroundView = null; TableView.BackgroundColor = AppDelegate.Color; TableView.SeparatorColor = AppDelegate.SecondaryColor; } }
Step 2: Override FinishedLaunchingMethod in AppDelegate
In order to navigate to our newly created DietViewController, it has to be “pushed” onto the UINavigationController. As the UINavigationController will be the topmost controller for navigation in our app, it in turn needs to be set as RootViewController of the window.
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) { UINavigationBar.Appearance.TintColor = SecondaryColor; window = new UIWindow (UIScreen.MainScreen.Bounds); model = new DietCalculatorModel (); controller = new DietCalculatorController (model); CreateUI (); // explained below mainDvc = new DietDialogViewController (rootElement); nvc = new UINavigationController (mainDvc); window.RootViewController = nvc; window.MakeKeyAndVisible (); return true; }
Notice in the above code, we’re initializing the DietCalculatorModel and DietCalculatorController which are part of the Core project. CreateUI method, which is explained below, will generate the root element with appropriate Sections and Child Elements to display in the screen.
Step 3: Create UI in AppDelegate
The majority of the remaining code in AppDelegate.cs exists within CreateUI, which is a private method used to create the root element using ElementsAPI and set to the DietDialogViewController which renders the view. Because this is a relatively straightforward view of the data, I have defined entire UI for this view using MT.D. Here is a snippet of the CreateUI method that demonstrates how MT.D UIs are rendered by composing sections with different elements:
// Gender Group and Section var genderRadioGroup = new RadioGroup ("gender", 0); var femaleRadioElement = new DietRadioElement ("Female", "gender"); var maleRadioElement = new DietRadioElement ("Male", "gender"); var radioElementSection = new Section () { maleRadioElement, femaleRadioElement }; // add hips element to waist & hips section if female is selected femaleRadioElement.Tapped += delegate { if (!waistHipsSection.Elements.Contains (hipsElement = new NumericEntryElement ("Hips (in cm)", "ex. 88"))) waistHipsSection.Add (hipsElement); }; // remove hips element if male is selected. maleRadioElement.Tapped += delegate { if (waistHipsSection.Elements.Contains (hipsElement)) waistHipsSection.Remove (hipsElement); }; // ... // Create Elements on the rootElement. This will be added to the DialogViewController's root. rootElement = new RootElement ("Diet Calculator") { new Section(){ (ageElement = new NumericEntryElement("Age","ex. 25"){ KeyboardType = UIKeyboardType.NumberPad }), (new DietRootElement("Gender", genderRadioGroup){radioElementSection}) }, new Section("Height & Weight"){ (weightElement = new NumericEntryElement("Weight (in kg)","ex. 65")), (heightElement = new NumericEntryElement("Height (in cm)","ex. 170")) }, (waistHipsSection = new Section ("Waist & Hips") { (waistElement = new NumericEntryElement("Waist (in cm)", "ex. 47")) /* hips element will be added here if female is selected*/ }), // ...
The Calculate button contains code similar to the existing Silverlight project, but referencing Xamarin.iOS MT.D elements:
/* Calculate Button*/ new Section() { (calculateButtonElement = new DietStringElement("Calculate", /* On Calculate Click create the resulting UI*/ delegate { /* exising code from SL MVC project */ controller.SetAge( StringToNumberUtility.GetInt32( ageElement.Value, 0 ) ); controller.SetGender( genderRadioGroup.Selected == 0 ? true : false); controller.SetWeight( StringToNumberUtility.GetDouble( weightElement.Value, 0.00 ) ); controller.SetHeight( StringToNumberUtility.GetDouble( heightElement.Value, 0.00 ) ); controller.SetWaist( StringToNumberUtility.GetDouble( waistElement.Value, 0.00 ) ); controller.SetHips( StringToNumberUtility.GetDouble((hipsElement == null ? null : hipsElement.Value), 0.00 ) ); controller.SetIdealWeight( StringToNumberUtility.GetDouble( idealWeightElement.Value, 0.00 ) ); controller.SetIdealBMI( StringToNumberUtility.GetDouble( idealBMIElement.Value, 0.00 ) ); controller.SetCholesterol( StringToNumberUtility.GetDouble( cholestrolElement.Value, 0.00 ) ); controller.SetHDL( StringToNumberUtility.GetDouble( hdlElement.Value, 0.00 ) ); controller.SetNeck( StringToNumberUtility.GetDouble( neckElement.Value, 0.00 ) ); var selectedActivity = levelOfActivitySection.Elements[levelOfActivityRadioGroup.Selected].Caption; controller.SetActivity( ( LevelOfActivity )Enum.Parse(typeof(LevelOfActivity), selectedActivity )); resultSection = new Section ("Results") { new DietStringElement("Calories Per Day: ", model.CaloriesPerDay.ToString()), new DietStringElement("Lean Body Mass: ", model.LeanBodyMass.ToString()), new DietStringElement("Fat: ", model.PercentBodyFat.ToString() + " %"), new DietStringElement("Waist Hips Label: ", model.WaistHipsRatio.ToString() + " cm"), new DietStringElement("BMI Ratio: ", model.BMI.ToString()), new DietStringElement("Cholestrol Ratio: ", model.CholesterolRatio.ToString() + " mmol/L"), new DietStringElement("Waist Height Ratio: ", model.WaistHeightRatio.ToString() + " cm"), new DietStringElement("Ideal Weight: ", model.IdealWeight.ToString() + " kg"), }; resultDvc = new DietDialogViewController(new RootElement("Results"){ resultSection }, true); nvc.PushViewController(resultDvc, true); })) } };
Finally, the following code shows how to hook into the changed event on some inputs to update other parts of the UI:
// Since Ideal BMI has to be calculated based on the Ideal weight, set all the fields in // the controller that are used for calcaulation idealWeightElement.Changed += (sender, e) => { controller.SetWeight (StringToNumberUtility.GetDouble( weightElement.Value, 0.00)); controller.SetHeight (StringToNumberUtility.GetDouble( heightElement.Value, 0.00)); controller.SetIdealWeight (StringToNumberUtility.GetDouble( idealWeightElement.Value, 0.00)); }; idealBMIElement.Changed += (sender, e) => { controller.SetWeight (StringToNumberUtility.GetDouble( weightElement.Value, 0.00)); controller.SetHeight (StringToNumberUtility.GetDouble( heightElement.Value, 0.00)); controller.SetIdealBMI (StringToNumberUtility.GetDouble( idealBMIElement.Value, 0.00)); };
Tying it all together
The existing Silverlight application displays user inputs & resulting fields in one big screen. This is OK for desktop applications, however we cannot afford to do the same in mobile applications. Instead we split it into two views – an input view and a result view. Input view will be our root view and will have a Calculate button which, when tapped, navigates to the result view and displays all results. Since we’re using MT.D, we can leverage the ElementAPI to build both the views as RootElements instead of creating a separate UIViewController classes.
Notice the variable rootElement which creates the first view that has all the EntryElements for user input. You’ll also find RadioGroup elements among them. When a field displaying RadioGroup elements is selected by our user, MT.D automatically navigates to another view with the values as options for our user to choose from.
For e.g, gender:
CalculateButtonElement
MT.D surfaces an NSAction as a delegate for handling callbacks. To handle a touch event for a table cell created by MT.D, simply supply a callback function to it. In this case, we call the appropriate methods in the DietCalculatorController class for the calculations and navigate to the resulting view. Resulting View is nothing but the resultSection that accesses the Model class for the updated values and is displayed in the view.
Other subclasses used:
- NumericEntryElement: inherits from EntryElement, used for setting the KeyBoardType to DecimalPad by default. This will ensure users will key-in only decimal values. This class also overrides the default font.
- DietRootElement: inherits from RootElement, used for overriding the font and text color. Used in the RadioGroup where the new View is pushed to display the options.
- DietRadioElement: inherits from RadioElement, used for overriding the default font and text color of the radio element
- DietStringElement: inherits from the StringElement, used for overriding the font and text color of the StringElement
Final Output
Please note, the logic in the program is ported as is — so all calculation results will depend on the algorithm used by the original author 🙂 You can download the sample code from github.
Update: There is now a follow up article to this one for Porting existing .NET apps to Android