Mono

Using C# to Develop for iPhone, iPod Touch and iPad

Brian Long Consultancy & Training Services Ltd.
February 2012

Accompanying source files available through this download link

Page selection: Previous  Next

Proximity Sensor and Notifications

The iPhone’s Proximity Sensor is used to turn the phone’s display off when you answer a call and move the phone next to your face. It can doubtless be employed in various other useful scenarios and so it is good to see how we can be notified of proximity state changes. The mechanism is simple; proximity to something is either detected or not and there will be a state change notification when the situation changes. This code is in ViewDidAppear():

UIDevice.CurrentDevice.ProximityMonitoringEnabled = true;
if (UIDevice.CurrentDevice.ProximityMonitoringEnabled)
     NSNotificationCenter.DefaultCenter.AddObserver(UIDevice.ProximityStateDidChangeNotification,
         (n) => { proximityLabel.Text = 
             UIDevice.CurrentDevice.ProximityState ? "Proximity detected" : "Proximity not detected"; });
else
    proximityLabel.Text = "Proximity sensor not available";

Not all devices have a proximity sensor (the simulator doesn’t, for example) so the advice is to turn proximity monitoring on and then check to see if it did in fact successfully turned on. If not there is no proximity sensor.

If you have the appropriate hardware then you need to arrange to respond to state changes. This is one of the cases that do not use the familiar delegate object (or event property alternative) approach. Instead it uses notifications orchestrated from a notification centre that requires observer methods to notice them. Every application has a notification centre accessible with NSNotificationCenter.DefaultCenter and of the various overloads AddObserver() offers the simplest one takes the notification identifier and a delegate that is passed an NSNotification object (which we ignore in the code). The declaration in the MonoTouch documentation looks like this:

public NSObject AddObserver (string aName, Action<NSNotification> notify)

Action<T> is a standard .NET generic delegate type declared in the System namespace. It represents a function that returns no value but takes a parameter of type T. In Objective-C the notification identifiers are literal strings and you could very well use this as the first parameter to AddObserver():

new NSString("UIDeviceProximityStateDidChangeNotification")

However the UIDevice class has a number of these notification identifiers set up as properties for your convenience, for example: UIDevice.OrientationDidChangeNotification, UIDevice.BatteryLevelDidChangeNotification, UIDevice.BatteryStateDidChangeNotification.

Don’t forget that we enabled proximity state monitoring so in ViewDidDisappear() it is appropriate to turn it off:

if (UIDevice.CurrentDevice.ProximityMonitoringEnabled)
    UIDevice.CurrentDevice.ProximityMonitoringEnabled = false;

Battery Status and Timers

The battery status monitoring operates quite similar to the proximity state monitoring. Not all devices support battery status monitoring (for example the iPhone Simulator does not) and to see if it’s supported you again enable monitoring and then check whether monitoring is still enabled. If not, then it’s not supported.

Whilst battery status monitoring can be done with notifications, that is only appropriate if you want monitoring to be on all the time. To be a bit more battery-friendly we can instead just check once every so often, say once a minute or two. Each time we want to check we turn battery monitoring on, if possible, check the battery level and battery state and then turn monitoring off. This method does the checking:

NSTimer UpdateBatteryStatusTimer;
...
private void ReadBatteryStatus()
{
    var dev = UIDevice.CurrentDevice;
    dev.BatteryMonitoringEnabled = true;
    if (dev.BatteryMonitoringEnabled)
        try
        {
            batteryLabel.Text = string.Format("{0}% - {1}", Math.Round(dev.BatteryLevel * 100), dev.BatteryState);
        }
        finally
        {
            dev.BatteryMonitoringEnabled = false;
        }
    else
    {
        batteryLabel.Text = "Battery level monitoring not available";
        UpdateBatteryStatusTimer.Invalidate();
    }            
}

To run this code at fixed intervals we need a scheduled repeating timer. Timers only work when they are scheduled on a run loop (the iOS equivalent of a Windows message loop) and need to be repeating to fire more than once.

In ViewDidAppear() the timer is set up to trigger every 60 seconds and the battery check code executed an initial time:

UpdateBatteryStatusTimer = NSTimer.CreateRepeatingScheduledTimer(
    60, new NSAction(ReadBatteryStatus));
ReadBatteryStatus();

NSAction is rather like Action<T> described earlier, but is a Mono delegate type that represents a function that returns no value and takes no parameters. This looks a little different to the anonymous method we passed in when setting up the proximity state notification, but we could make it look more similar by writing it like this:

UpdateBatteryStatusTimer = NSTimer.CreateRepeatingScheduledTimer(
    60, () => { ReadBatteryStatus(); });
ReadBatteryStatus();

However, it can be made more intuitive as:

UpdateBatteryStatusTimer = NSTimer.CreateRepeatingScheduledTimer(
    60, ReadBatteryStatus);
ReadBatteryStatus();

Note that in the timer event handler earlier if battery monitoring is not available then the timer is cancelled using its Invalidate() method. It is also important to remember to cancel the timer in ViewDidDisappear() by calling the same method.

Go back to the top of this page

Go back to start of this article

Previous page

Next page