Key-Value-Observing on MonoTouch and MonoMac

This morning Andres came by IRC asking questions about Key Value Observing, and I could not point him to a blog post that would discuss the details on how to use this on C#.

Apple's Key-Value Observing document contains the basics on how to observe changes in properties done to objects.

To implement Key-Value-Observing using MonoTouch or MonoMac all you have to do is pick the object that you want to observe properties on, and invoke the "AddObserver" method.

This method takes a couple of parameters: an object that will be notified of the changes, the key-path to the property that you want to observe, the observing options and a context object (optional).

For example, to observe changes to the "bounds" property on a UIView, you can use this code:

view.AddObserver (
	observer: this, 
	keyPath:  new NSString ("bounds"), 
	options:  NSKeyValueObservingOptions.New, 
	context:  IntPtr.Zero);

In this example, I am using the C# syntax that uses the Objective-C style to highlight what we are doing, but you could just have written this as:

view.AddObserver (
	this, new NSString ("bounds"),
	NSKeyValueObservingOptions.New, IntPtr.Zero);

What the above code does is to add an observer on the "view" object, and instructs it to notify this object when the "bounds" property changes.

To receive notifications, you need to override the ObserveValue method in your class:

public override
void ObserveValue (NSString keyPath, NSObject ofObject,
			NSDictionary change, IntPtr context)
{
    var str = String.Format (
	"The {0} property on {1}, the change is: {2}",
        keyPath, ofObject, change.Description);

    label.Text = str;
    label.Frame = ComputeLabelRect ();
}

This is what the app shows if you rotate your phone:

The complete sample has been uploaded to GitHub.

Posted on 19 Apr 2012 by Miguel de Icaza

Call for Comments: Strongly Typed Notifications

I am adding support for strongly typed notifications to MonoTouch and MonoMac. The idea behind this is to take guesswork, trips to the documentation and trial and error from using notifications on iOS and MacOS.

The process is usually: (a) find the right notification; (b) look up apple docs to see when the notification is posted; (c) look up each of the keys used to retrieve the data from the dictionary.

Currently, listening to a notification for a keyboard-will-be-shown notification looks like this in MonoTouch:

void DoSomething (
	UIViewAnimationCurve curve,
	double               duration,
	RectangleF           frame)
{
	// do something with the above
}

var center = NSNotificationCenter.DefaultCenter;
center.AddObserver (UIKeyboard.WillShowNotification, PlaceKeyboard);

[...]

void PlaceKeyboard (NSNotification notification)
{
    // Get the dictionary with the interesting values:
    var dict = notification.UserInfo;

    // Extract the individual values
    var animationCurve = (UIViewAnimationCurve)
	(dict [UIKeyboard.AnimationCurveUserInfoKey] as NSNumber).Int32Value;
    double duration =
	(dict [UIKeyboard.AnimationDurationUserInfoKey] as NSNumber).DoubleValue;
    RectangleF endFrame =
	(dict [UIKeyboard.FrameEndUserInfoKey] as NSValue).RectangleFValue;

    DoSomething (animationCurve, duration, endFrame)
}

Currently we map the Objective-C constant "FooClassNameNotification" into the C# class "Foo" as the member "NameNotification" of type NSString.

What we want to do is to expose the notifications as strongly typed C# events. This will provide auto-complete support in the IDE to produce the lambdas or helper methods, auto-complete for all the possible properties of the notification, strong types for the data provided and live documentation for the values in the notification.

This means that the above code would instead be written like this:

var center = NSNotificationCenter.DefaultCenter;
center.Keyboard.WillShowNotification += PlaceKeyboard;

void PlaceKeyboard (object sender, KeyboardShownEventArgs args)
{
    DoSomething (args.AnimationCurve, args.Duration, args.EndFrame);
}

The question is where should these notifications be exposed in the API? In the example above we do this by the event "WillShowNotification" on a class "Keyboard" inside the "NSNotificationCenter". We have a few options for this.

We could host the notification in the class that defines the notification, but we would have to come up with a naming scheme to avoid the name clash with the existing NSString constant:

class UIKeyboard {
    public NSString WillShowNotification { get; }

    // replace "Notification" with the class name:
    public event EventHandler WillShowKeyboard;

    // prefix the event:
    public event EventHandler KeyboardWillShow;

    // plain, does not work on all types though:
    public event EventHandler WillShow;
}

// Consumer code would be one of:

UIKeyboard.WillShowKeyboard += handler;
UIKeyboard.KeyboardWillShow += handler;
UIKeyboard.WillShow += handler;

Another option is to add everything into NSNotificationCenter:

class NSNotificationCenter {
	// Existing implementation

    public event EventHandler UIKeyboardWillShow;

    // Another 141 events are inserted here.
}

// Consumer code would be:

NSNotificationCenter.DefaultCenter.UIKeyboardWillShow += handler;

Another option is to partition the notifications based on their natural host, this is my personal favorite, but could be harder to find with the IDE using code completion:

class NSNotificationCenter {
    public static class Keyboard {
        public static event EventHandler WillShow;
    }
}

// Consumer code would be:
NSNotificationCenter.Keyboard.WillShow += handler;

All of these proposals have one potential problem: they would all assume that all of these interesting notifications are always posted into the NSNotificationCenter.DefaultCenter.

Apple's documentation does not seem to suggest that any of the iOS notifications are posted anywhere but the DefaultCenter. I could not find on GitHub any code that would use anything but the DefaultCenter.

On MacOS the InstantMessage framework posts notifications to its own notification center. We could just bind those events to this specific NSNotificationCenter.

Thoughts?

Posted on 12 Apr 2012 by Miguel de Icaza
This is a personal web page. Things said here do not represent the position of my employer.