Xib-Free iOS App with C# – Part 3 – Timers

This is Part 3 of our series building a xib-free iOS app with C#.

In our last episode, we created a new project in Xamarin Studio, created a custom view controller, initialized its view, and added a clock view. Today, we will explore several ways to display and update the time.

Display the Current Time

To display the current time, add a method to the class CurrentWeatherClockViewController to update the clock view:

private void UpdateClockView()
{
    timeLabel.Text = String.Format ("{0:hh:mm:ss tt}", DateTime.Now);
}

Next, override the ViewWillAppear method of the super class, UIViewController:

public override void ViewWillAppear (bool animated)
{
    base.ViewWillAppear (animated);

    this.UpdateClockView ();
}

A shortcut for entering this method is to first type override. As soon as you type a space after override, Xamarin Studio will provide a completion list. Choose ViewWillAppear from that list. The IDE will then automatically insert a skeleton of the ViewWillAppear method, including the call to base.ViewWillAppear. All you have to do is add this line:

    this.UpdateClockView ();

When you build and run the project, your app will display the current time. But the time is still not being updated like a real clock. Let’s do that now.

Update the Current Time

To update the time, we need a timer. Add a private field:

    private NSTimer timer;

Note that the IDE sets NSTimer in red. Right click and choose Resolve > using MonoTouch.Foundation.

Add a method to start the timer:

private void StartTimer()
{
    this.timer = NSTimer.CreateRepeatingScheduledTimer (TimeSpan.FromSeconds (1.0), delegate {
        this.UpdateClockView ();
    });
}

And start it by adding this line to ViewWillAppear:

    this.StartTimer ();

Your CurrentWeatherClockViewController class should look something like this now:

using System;
using System.Drawing;

using MonoTouch.UIKit;
using MonoTouch.Foundation;

namespace CurrentWeatherClock
{
    public class CurrentWeatherClockViewController : UIViewController
    {
        private UILabel timeLabel;
        private NSTimer timer;

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            this.View.BackgroundColor = UIColor.White;

            this.timeLabel = new UILabel
            {
                Frame = new RectangleF(0, 180, this.View.Bounds.Width, 100),
                Text = "00:00:00 AM",
                Font = UIFont.SystemFontOfSize(56)
            };

            this.View.AddSubview (this.timeLabel);
        }

        public override void ViewWillAppear (bool animated)
        {
            base.ViewWillAppear (animated);

            this.UpdateClockView ();
            this.StartTimer ();
        }

        private void UpdateClockView()
        {
            timeLabel.Text = String.Format ("{0:hh:mm:ss tt}", DateTime.Now);
        }

        private void StartTimer()
        {
            this.timer = NSTimer.CreateRepeatingScheduledTimer (TimeSpan.FromSeconds (1.0), delegate {
                this.UpdateClockView ();
            });
        }
    }
}

Stop the Timer

Stop the timer in the ViewWillDisappear method:

public override void ViewWillDisappear (bool animated)
{
    base.ViewWillDisappear (animated);

    this.StopTimer ();
}

private void StopTimer()
{
    this.timer.Invalidate ();
}

It’s a good idea to stop timers, tasks, and other continuously running resources that a page is using when the user navigates away.

Timer Periodicity

Let’s take an informal look at whether our timer really is firing at a one second interval. Change the clock display format:

    timeLabel.Text = String.Format ("{0:hh:mm:ss.ff}", DateTime.Now);

Build and run. In my experiments, it looks pretty good on both the simulator and an iPhone device. However, let’s not take any chances. A common technique, when sampling a continuous fuction is to sample at twice the frequency1. The clock is not a true continuous function, but appears to be from our persepctive of displaying samples once per second.

Change the timer interval rate to one half second, then build and run again. Note that the clock is updated approximately every half second.

private void StartTimer()
{
    this.timer = NSTimer.CreateRepeatingScheduledTimer (TimeSpan.FromSeconds (0.5), delegate {
        this.UpdateClockView ();
    });
}

Cross-Platform Timer

This timer works fine, but when using Xamarin and C#, we’d like to use the .Net libraries where we can. This makes the app more cross-platform in case we wish to port to Android later. Of course, Android does not have a UIViewController, so that will have to change anyway. But in a larger application, you likely would have implemented the timer in a separate class that could be cross-platform.

Replace the timer field:

    private Timer   timer;

This will require another using statement:

using System.Threading;

Replace the timer start and stop methods:

private void StartTimer()
{
    this.timer = new Timer(delegate {this.UpdateClockView();}, null, 0, 500);
}

private void StopTimer()
{
    if (this.timer != null)
    {
        this.timer.Change (Timeout.Infinite, Timeout.Infinite);
    }
}

(If you’re used to Objective-C, you might be tempted to leave out the test for null. C# doesn’t work that way. You’ll get a null exception if you try it.)

Build and run. Oops, we got an exception anyway:

MonoTouch.UIKit.UIKitThreadAccessException: UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.

This happens because we’re updating the view on a thread other than the main thread. Fix it by invoking the assignment to the UILabel on the main thread:

private void UpdateClockView()
{
    InvokeOnMainThread (delegate
    {
        timeLabel.Text = String.Format ("{0:hh:mm:ss.ff}", DateTime.Now);
    });
}

Tweak the Timer Start

This is great, but let’s do one more tweak to make the one second updates better match other clocks. Delay starting the timer to about the start of a new second:

private void StartTimer()
{
    int dueTime = 1000 - DateTime.Now.Millisecond;
    this.timer = new Timer(delegate {this.UpdateClockView();}, null, dueTime, 500);
}

Restore AM/PM Indicator

And finally, let’s restore the AM/PM indicator:

    timeLabel.Text = String.Format ("{0:hh:mm:ss tt}", DateTime.Now);

Final Version with Timer

using System;
using System.Drawing;
using System.Threading;

using MonoTouch.UIKit;
using MonoTouch.Foundation;

namespace CurrentWeatherClock
{
    public class CurrentWeatherClockViewController : UIViewController
    {
        private UILabel timeLabel;
        private Timer   timer;

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            this.View.BackgroundColor = UIColor.White;

            this.timeLabel = new UILabel
            {
                Frame = new RectangleF(0, 180, this.View.Bounds.Width, 100),
                Text = "00:00:00 AM",
                Font = UIFont.SystemFontOfSize(56)
            };

            this.View.AddSubview (this.timeLabel);
        }

        public override void ViewWillAppear (bool animated)
        {
            base.ViewWillAppear (animated);

            this.UpdateClockView ();
            this.StartTimer ();
        }

        public override void ViewWillDisappear (bool animated)
        {
            base.ViewWillDisappear (animated);

            this.StopTimer ();
        }

        private void UpdateClockView()
        {
            InvokeOnMainThread (delegate
            {
                timeLabel.Text = String.Format ("{0:hh:mm:ss tt}", DateTime.Now);
            });
        }

        private void StartTimer()
        {
            int dueTime = 1000 - DateTime.Now.Millisecond;
            this.timer = new Timer(delegate {this.UpdateClockView();}, null, dueTime, 500);
        }

        private void StopTimer()
        {
            if (this.timer != null)
            {
                this.timer.Change (Timeout.Infinite, Timeout.Infinite);
            }
        }
    }
}

  1. The Nyquist frequency, named after electronic engineer Harry Nyquist, is ½ of the sampling rate of a discrete signal processing system. http://en.wikipedia.org/wiki/Nyquist_frequency 
Advertisements
This entry was posted in iPhone Dev and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s