Building your first end-to-end Cross Platform app – Part 4 – iOS App

By | September 1, 2015

Hello and Welcome again to the fourth part of the series.  Till now we have created Weather.Common (Building your first end-to-end Cross Platform app) and Weather.Windows Building your first end-to-end Cross Platform app – Part 2 – Windows Universal App) .  and a Weather.Android (Building your first end-to-end Cross Platform app – Part 3 – Android App) .

Now we set to create our iOS app for the same, so let’s begin.

For development of iOS we need Macintosh system and install Xcode and Xamarin into it. Assuming that this has been taken care we will move ahead.

Find the complete Source Code at GitHub

Step 1: Unload the Weather.Windows project for the time being and add an iOS project “Weather.Ios” to the solution.

image

image

Step 2: Add reference to Weather.Common project and the following dll’s and mvvmcross nuget package.

image

Step 3: Delete MainPage.xaml and add a class DebugTrace for tracing warning and errors if not already added by the nuget package.

public class DebugTrace : IMvxTrace
    {
        public void Trace(MvxTraceLevel level, string tag, Func<string> message)
        {
            Debug.WriteLine(tag + ":" + level + ":" + message());
        }

        public void Trace(MvxTraceLevel level, string tag, string message)
        {
            Debug.WriteLine(tag + ":" + level + ":" + message);
        }
        public void Trace(MvxTraceLevel level, string tag, string message, params object[] args)
        {
            try
            {
                Debug.WriteLine(string.Format(tag + ":" + level + ":" + message, args));
            }
            catch (FormatException)
            {
                Trace(MvxTraceLevel.Error, tag, "Exception during trace of {0} {1}", level, message);
            }
        }
    }

Step 4: Now add a class called Setup if not already added by the nuget package.

public class Setup : MvxTouchSetup
	{
		public Setup(MvxApplicationDelegate appDelegate, IMvxTouchViewPresenter presenter)
			: base(appDelegate, presenter)
		{
		}

		protected override IMvxApplication CreateApp()
		{
			return new App();
		}

        protected override IMvxTrace CreateDebugTrace()
        {
            return new DebugTrace();
        }
	}

Step 5: Replace the FinishedLaunching method in AppDelegate.cs

public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
		{
			// create a new window instance based on the screen size
			Window = new UIWindow (UIScreen.MainScreen.Bounds);

			var presenter = new MvxTouchViewPresenter(this, Window);

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

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

			Window.MakeKeyAndVisible();

			return true;
		}

Step 6: Add a file LinkerPleaseInclude.cs to the project if not already added by the nuget package.

[Preserve(AllMembers = true)]
    public class LinkerPleaseInclude
    {
        public void Include(UIButton uiButton)
        {
            uiButton.TouchUpInside += (s, e) =>
                                      uiButton.SetTitle(uiButton.Title(UIControlState.Normal), UIControlState.Normal);
        }

        public void Include(UIBarButtonItem barButton)
        {
            barButton.Clicked += (s, e) =>
                                 barButton.Title = barButton.Title + "";
        }

        public void Include(UITextField textField)
        {
            textField.Text = textField.Text + "";
            textField.EditingChanged += (sender, args) => { textField.Text = ""; };
        }

        public void Include(UITextView textView)
        {
            textView.Text = textView.Text + "";
            textView.Changed += (sender, args) => { textView.Text = ""; };
        }

        public void Include(UILabel label)
        {
            label.Text = label.Text + "";
            label.AttributedText = new NSAttributedString(label.AttributedText.ToString() + "");
        }

        public void Include(UIImageView imageView)
        {
            imageView.Image = new UIImage(imageView.Image.CGImage);
        }

        public void Include(UIDatePicker date)
        {
            date.Date = date.Date.AddSeconds(1);
            date.ValueChanged += (sender, args) => { date.Date = NSDate.DistantFuture; };
        }

        public void Include(UISlider slider)
        {
            slider.Value = slider.Value + 1;
            slider.ValueChanged += (sender, args) => { slider.Value = 1; };
        }

        public void Include(UIProgressView progress)
        {
            progress.Progress = progress.Progress + 1;
        }

        public void Include(UISwitch sw)
        {
            sw.On = !sw.On;
            sw.ValueChanged += (sender, args) => { sw.On = false; };
        }

        public void Include(MvxViewController vc)
        {
            vc.Title = vc.Title + "";
        }

        public void Include(UIStepper s)
        {
            s.Value = s.Value + 1;
            s.ValueChanged += (sender, args) => { s.Value = 0; };
        }

        public void Include(UIPageControl s)
        {
            s.Pages = s.Pages + 1;
            s.ValueChanged += (sender, args) => { s.Pages = 0; };
        }

        public void Include(INotifyCollectionChanged changed)
        {
            changed.CollectionChanged += (s, e) => { var test = string.Format("{0}{1}{2}{3}{4}", e.Action,e.NewItems, e.NewStartingIndex, e.OldItems, e.OldStartingIndex); } ;
        }

        public void Include(ICommand command)
        {
           command.CanExecuteChanged += (s, e) => { if (command.CanExecute(null)) command.Execute(null); };
        }

		public void Include(Cirrious.CrossCore.IoC.MvxPropertyInjector injector)
		{
			injector = new Cirrious.CrossCore.IoC.MvxPropertyInjector();
		} 

		public void Include(System.ComponentModel.INotifyPropertyChanged changed)
		{
			changed.PropertyChanged += (sender, e) => { var test = e.PropertyName; };
		}
	}

Step 7: Add a folder called Views to the solution and then add an IPhone View Controller (FirstView) to this folder.

Adding IPhone View Controller

Adding IPhone View Controller

Adding IPhone View Controller

Adding IPhone View Controller

Step 8: Replace the content of the FirstView.xib with the content below.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14F27" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="FirstView">
            <connections>
                <outlet property="cityLabel" destination="iGR-Tj-Qo0" id="NBi-a5-Rk8"/>
                <outlet property="dateLabel" destination="Xu6-Us-TBl" id="Uct-Zg-B6C"/>
                <outlet property="nextButton" destination="5tf-aB-kmn" id="dgv-l8-Trv"/>
                <outlet property="prevButton" destination="bPY-bD-hMz" id="WxC-my-eMW"/>
                <outlet property="templabel" destination="4D2-5b-Vdg" id="cDq-KE-25j"/>
                <outlet property="view" destination="iN0-l3-epB" id="kCW-yf-GMq"/>
            </connections>
        </placeholder>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <view contentMode="scaleToFill" id="iN0-l3-epB">
            <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
            <subviews>
                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="City : " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XeV-he-j60">
                    <rect key="frame" x="75" y="228" width="42" height="21"/>
                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                    <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                    <nil key="highlightedColor"/>
                </label>
                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iGR-Tj-Qo0">
                    <rect key="frame" x="153" y="228" width="93" height="21"/>
                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                    <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                    <nil key="highlightedColor"/>
                </label>
                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xu6-Us-TBl">
                    <rect key="frame" x="153" y="273" width="150" height="21"/>
                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                    <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                    <nil key="highlightedColor"/>
                </label>
                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Temp : " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1yZ-og-TWZ">
                    <rect key="frame" x="75" y="325" width="63" height="21"/>
                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                    <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                    <nil key="highlightedColor"/>
                </label>
                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Date : " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c3N-eq-Muk">
                    <rect key="frame" x="75" y="273" width="51" height="21"/>
                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                    <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                    <nil key="highlightedColor"/>
                </label>
                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4D2-5b-Vdg">
                    <rect key="frame" x="153" y="325" width="88" height="21"/>
                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                    <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                    <nil key="highlightedColor"/>
                </label>
                <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bPY-bD-hMz">
                    <rect key="frame" x="24" y="381" width="46" height="30"/>
                    <state key="normal" title="Prev">
                        <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
                    </state>
                </button>
                <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5tf-aB-kmn">
                    <rect key="frame" x="215" y="381" width="46" height="30"/>
                    <state key="normal" title="Next">
                        <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
                    </state>
                </button>
            </subviews>
            <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
        </view>
    </objects>
    <simulatedMetricsContainer key="defaultSimulatedMetrics">
        <simulatedStatusBarMetrics key="statusBar"/>
        <simulatedOrientationMetrics key="orientation"/>
        <simulatedScreenMetrics key="destination" type="retina4"/>
    </simulatedMetricsContainer>
</document>

In this we have created a page which shows the city name, date and temperature on that date, along with two buttons to navigate between previous and next day’s temperature.

Step 9: Replace the content of the FirstView.designer.cs with the content below.


[Register ("FirstView")]
	partial class FirstView
	{
		[Outlet]
		UIKit.UILabel cityLabel { get; set; }

		[Outlet]
		UIKit.UILabel dateLabel { get; set; }

		[Outlet]
		UIKit.UIButton nextButton { get; set; }

		[Outlet]
		UIKit.UIButton prevButton { get; set; }

		[Outlet]
		UIKit.UILabel templabel { get; set; }

		void ReleaseDesignerOutlets ()
		{
			if (prevButton != null) {
				prevButton.Dispose ();
				prevButton = null;
			}

			if (nextButton != null) {
				nextButton.Dispose ();
				nextButton = null;
			}

			if (cityLabel != null) {
				cityLabel.Dispose ();
				cityLabel = null;
			}

			if (dateLabel != null) {
				dateLabel.Dispose ();
				dateLabel = null;
			}

			if (templabel != null) {
				templabel.Dispose ();
				templabel = null;
			}
		}
	}

Step 10: Replace the content of the FirstView.cs with the content below.

public partial class FirstView : MvxViewController
	{
		public override void ViewDidLoad ()
		{
			base.ViewDidLoad ();
			var set = this.CreateBindingSet<FirstView, FirstViewModel>();
			set.Bind(this.cityLabel).To(vm => vm.DailyTemperature.City);
			set.Bind(this.dateLabel).To(vm => vm.DailyTemperature.ShortDate);
			set.Bind(this.templabel).To(vm => vm.DailyTemperature.Temprature);
			set.Bind(this.prevButton).To(vm => vm.PreviousCommand);
			set.Bind(this.nextButton).To(vm => vm.NextCommand);
			set.Bind(this.nextButton).For( x => x.Enabled).To(vm => vm.IsNext);
			set.Bind(this.prevButton).For( x => x.Enabled).To(vm => vm.IsPrevious);
			set.Apply();
		}
	}

Congratulations you’re done with creating your first cross platform app for iOS, make sure your solution structure look like this. Now please go ahead and run the project.

image

image

Note: This series gives you a very basic idea of developing a cross platform app. Please feel free to enhance it the way you like it (for example):

· Making city and no of days as configurable instead of being constant.

· Showing error messages when there is no network connectivity.

· Showing messages when the web service is down.

· Adding exceptional handling.

· Logging exceptions.

· Adding splash images and other assets.