Animating the stroke color of a CAShapeLayer with Xamarin

I wanted to indicate the most recent move in an AI-on-AI game of TicTacToe, so I wanted to have the most recent move be highlighted. The Xs and Os are CAShapeLayer objects.

Here’s the code to do it, featuring a very ugly hack to cast an IntPtr to an NSObject Including the use of SetTo and SetFrom to use a type that is not an NSObject in CABasicAnimation (thanks Sebastien!):

var layer = mark == 'X' ? ShapeLayer.XLayer (endFrame) : ShapeLayer.OLayer (endFrame);
layer.Position = origin;
this.Layer.AddSublayer (layer);

var animation = CABasicAnimation.FromKeyPath ("strokeColor");
animation.SetFrom(UIColor.Green.CGColor);
animation.SetTo(layer.StrokeColor);
animation.Duration = 0.5;

layer.AddAnimation (animation, "animateStrokeColor");

GameplayKit path-finding in iOS 9 with Xamarin.iOS

Easy-peasy, lemon-squeazy:

var a = GKGraphNode2D.FromPoint (new Vector2 (0, 5));
var b = GKGraphNode2D.FromPoint (new Vector2 (3, 0));
var c = GKGraphNode2D.FromPoint (new Vector2 (2, 6));
var d = GKGraphNode2D.FromPoint (new Vector2 (4, 6));
var e = GKGraphNode2D.FromPoint (new Vector2 (6, 5));
var f = GKGraphNode2D.FromPoint (new Vector2 (6, 0));

a.AddConnections (new [] { b, c }, false);
b.AddConnections (new [] { e, f }, false);
c.AddConnections (new [] { d }, false);
d.AddConnections (new [] { e, f }, false);

var graph = GKGraph.FromNodes(new [] { a, b, c, d, e, f });

var a2e = graph.FindPath (a, e); // [ a, c, d, e ]
var a2f = graph.FindPath (a, f); // [ a, b, f ]

GKPathFindPath

FizzBuzz with iOS 9 GameplayKit Expert System in C# with Xam.iOS

OK, so this is silly, but:

			var clearRule = GKRule.FromPredicate ((rules) => reset, rules => {
				output = "";
				reset = false;
			});
			clearRule.Salience = 1;

			var fizzRule = GKRule.FromPredicate (mod (3), rules => {
				output += "fizz";
			});
			fizzRule.Salience = 2;
			var buzzRule = GKRule.FromPredicate (mod (5), rules => { 
				output += "buzz";
			});
			 buzzRule.Salience = 2;

			var outputRule = GKRule.FromPredicate (rules => true, rules => {
				System.Console.WriteLine(output == "" ? input.ToString() : output);
				reset = true;
			});
			outputRule.Salience = 3;

			var rs = new GKRuleSystem ();
			rs.AddRules (new [] {
				clearRule,
				fizzRule,
				buzzRule,
				outputRule
			});

			for (input = 1; input < 16; input++) {
				rs.Evaluate ();
				rs.Reset ();
			}

Output:

2015-08-04 13:08:47.164 GameplayKit0[46277:18357203] 1
2015-08-04 13:08:47.164 GameplayKit0[46277:18357203] 2
2015-08-04 13:08:50.338 GameplayKit0[46277:18357203] fizz
2015-08-04 13:08:50.338 GameplayKit0[46277:18357203] 4
2015-08-04 13:08:51.089 GameplayKit0[46277:18357203] buzz
2015-08-04 13:08:51.934 GameplayKit0[46277:18357203] fizz
2015-08-04 13:08:51.934 GameplayKit0[46277:18357203] 7
2015-08-04 13:08:51.935 GameplayKit0[46277:18357203] 8
2015-08-04 13:08:52.589 GameplayKit0[46277:18357203] fizz
2015-08-04 13:08:53.256 GameplayKit0[46277:18357203] buzz
2015-08-04 13:08:53.256 GameplayKit0[46277:18357203] 11
2015-08-04 13:08:53.872 GameplayKit0[46277:18357203] fizz
2015-08-04 13:08:53.873 GameplayKit0[46277:18357203] 13
2015-08-04 13:08:53.873 GameplayKit0[46277:18357203] 14
2015-08-04 13:08:55.005 GameplayKit0[46277:18357203] buzzfizz

The important thing I learned is that you have to call GKRuleSystem.Reset() if you want evaluated GKRules to be re-evaluated.

How to: Handoff to a Xamarin iPhone app from Apple Watch

# How to: Handoff to a Xamarin iPhone app from Apple Watch

There are two ways to activate the parent (aka container) app from an Apple Watch app. You can either directly activate the container app using WKInterfaceController.OpenParentApplication or you can use Handoff.

Using Handoff is a little more complex, so I thought I’d write a quick little how-to. There are a few different Handoff scenarios, but perhaps the most common for the  Watch is: “On my watch I want to begin a task that I complete later on my iPhone.” So, for instance, some task that requires either more data-entry than is appropriate for the watch or some capabilities not available on the watch.

I want to keep the focus on the APIs, so instead of a real-world sample, I’m going to create a minimal example: a button on the Watch activates handoff and a status label on the phone app is updated when the user activity is continued on the phone.

As always, we have a single Solution with 3 projects: the parent App, the Watch extension, and the Watch App.

Napkin 10 05-06-15, 4.21.12 PM

Every handoff activity has a unique identifier. By convention, this is a domain-reversed string such as: com.xamarin.HandOffDemo.verb.

To trigger the Handoff behavior, the watch extension calls the WKInterfaceController.UpdateUserActivity method, with its first argument set equal to this identifier:

partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
    var userInfo = NSDictionary.FromObjectAndKey(new NSString(“value”), new NSString(“key”));
    this.UpdateUserActivity(“com.xamarin.HandOffDemo.verb”, userInfo, null);
}

The third argument is a NSUrl object that can be used for Handoff tasks that should be handled by Safari. But in our case, we’re handing the userInfo dictionary containing the very complex data associated with our handoff.

Your parent app registers its interest in this type of handoff within its info.plist. In the parent app info.plist, add a new array called NSUserActivityTypes and add to it a string with value com.xamarin.HandOffDemo.verb

Napkin 11 05-06-15, 4.29.20 PM

It’s possible that an app could be interested in certain handoffs, but not always be in a position to actually handle them. That logic can be placed in an override of the UIApplicationDelegate.WillContinueUserActivity method:

public override bool WillContinueUserActivity (UIApplication application, string userActivityType)
{
    //Yeah, we can handle it
    return true;
}

Assuming that we return true from that method, the next step is to override the UIApplicationDelegate.ContinueUserActivity method:

An architectural issue that needs to be addressed is that this Handoff re-entry point is in the UIApplicationDelegate object, which of course does not have an associated user interface. There are several ways to handle this, but as a fan of reactive programming, I think the proper design is to create either an IObservable sequence or a more traditional C# event, which is what I do here:

public event EventHandler HandoffOccurred = delegate {};

public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    HandoffOccurred?.Invoke (this, userActivity.UserInfo);

    return true;
}

The third parameter, completionHandler used if you have references to custom UIResponder objects that should handle the user activity. In the case, you put those references in a NSArray and pass them to completionHandler, which will cause each of their ContinueUserActivity methods to be called. (Update: Apple would probably prefer this technique to my event, but I am not sure if it’s necessary, and it requires the UIApplicationDelegate to maintain a reference to the subscribing UIResponder, so you either have an ugly dependency or you have to implement some kind of Observer / Subscriber pattern. So I still would suggest an event or IObservable as the better solution.)

In the parent app’s main UIViewController class, I have:

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    //Configure user experience for Handoff
    var myAppDel = (AppDelegate) UIApplication.SharedApplication.Delegate;
    myAppDel.HandoffOccurred += HandoffOccurred;
}

public void HandoffOccurred(object sender, NSDictionary userInfo)
{
    InvokeOnMainThread( () =&gt; statusLabel.Text = userInfo[“key”].ToString() );
}

And that’s all there is to it. Obviously, a real use-case would involve building a more complex NSDictionary holding the context of the watch interaction and a similarly complex handler in the parent app.

Now, when the Handoff is activated from the Apple Watch, my iPhone lock screen shows the icon of the parent app in the lower-left corner. If I drag that up, the parent app opens, the re-entry process begins, with UIApplicationDelegate.WillContinueUserActivity and UIApplicationDelegate.ContinueUserActivity.

The Protocol Pattern

In C# (and F#), one can define extension methods on interfaces. These extension methods can have implementations, which can be used as default implementations for implementors of the extension. I haven’t heard a name for this technique.

Example:

interface IFoo
{

}

static class IFoo_Extensions 
{
	public static void Foo(this IFoo self) { Console.WriteLine("Foo"); }
}

class ImplementingClass : IFoo
{

}


class MainClass
{
	public static void Main (string[] args)
	{
		var aFoo = new ImplementingClass ();
		aFoo.Foo (); //Prints "Foo" from extension default implementation

	}
}

Xamarin uses this pattern extensively when binding Objective-C Protocols, which are essentially interfaces with optional methods. For instance, if you have an interface where some methods must be implemented by the library user but some aren’t, you can do this:

interface IFoo
{
	//Methods defined here, as always, must be implemented
	void Necessary ();
}

static class IFoo_Extensions
{
	//"Optional" methods defined here with default implementations
	public static void Optional (this IFoo self)
	{
	}
}

class ImplementingClass : IFoo
{
	public void Necessary ()
	{
		Console.WriteLine ("Necessary");
	}

//    public void Optional()
//    {
//        Console.WriteLine("Overridden");
//    }

}

Obviously, it’s not exactly the same to have a default implementation defined in an extension method as it is to have an optional method that simply does not exist. But conceptually it’s close enough that I’ve started referring to this technique as the “Protocol Pattern.”

Thoughts?

Using Xamarin.Forms.Maps: You have to Init() first!

The first time I wrote a Xamarin.Forms.Maps program, I couldn’t figure out why my map wasn’t appearing. And then I put a call to Xamarin.FormsMaps.Init() into the AppDelegate (iOS) and MainActivity (Android):

Shared:

    public class App
	{
		public static Page GetMainPage ()
		{	
			return new ContentPage { 
				Content = new StackLayout {
					Children = {
						new BoxView { BackgroundColor = Color.Green },
						new Map(MapSpan.FromCenterAndRadius(new Position(37,-122), Distance.FromMiles(10))){
							VerticalOptions = LayoutOptions.FillAndExpand,
							HeightRequest = 100,
							WidthRequest = 960,
							BackgroundColor = Color.Blue
						},
						new BoxView { BackgroundColor = Color.Red }
					}
				}
			};
		}
	}

iOS:

    namespace HelloMap.iOS
	{
		[Register ("AppDelegate")]
		public partial class AppDelegate : UIApplicationDelegate
		{
			UIWindow window;

			public override bool FinishedLaunching (UIApplication app, NSDictionary options)
			{
				Forms.Init ();
				FormsMaps.Init ();

				window = new UIWindow (UIScreen.MainScreen.Bounds);
				
				window.RootViewController = App.GetMainPage ().CreateViewController ();
				window.MakeKeyAndVisible ();
				
				return true;
			}
		}
	}

Android:

    namespace HelloMap.Android
	{
		[Activity (Label = "HelloMap.Android.Android", MainLauncher = true)]
		public class MainActivity : AndroidActivity
		{
			protected override void OnCreate (Bundle bundle)
			{
				base.OnCreate (bundle);

				Xamarin.Forms.Forms.Init (this, bundle);
				FormsMaps.Init(this, bundle);

				SetPage (App.GetMainPage ());
			}
		}
	}

Happy Cross-Platform Coding!

Screen Shot 2014-05-30 at 1.52.09 PM

Natively Recognize Barcodes/QR Codes in iOS 7 with Xamarin.iOS

There have been great barcode-reading libraries available for Xamarin for some time, but iOS 7 has built-in barcode-recognition support.

There’s only one tricky bit: you have to tell the AVCaptureMetadataOutput what types of barcodes you’re interested in after you’ve added it to the AVCaptureSession. (I suppose what happens behind the scene is that the AVCaptureSession registers with the AVCaptureMetadataOutput the various types of barcodes it could recognize.)

UPDATE: This also works for realtime face detection! s/AVMetadataMachineReadableCodeObject/AVMetadataFaceObject/ and set AVMetadataObject.TypeFace!

Here’s a complete program in Xamarin.iOS that recognizes QR and standard Ean13 barcodes:

using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Drawing;
using MonoTouch.CoreGraphics;
using MonoTouch.AVFoundation;
using MonoTouch.CoreFoundation;

namespace SingleFileSolution
{
	public class ContentView : UIView
	{
		AVCaptureVideoPreviewLayer layer; 

		public ContentView(UIColor fillColor, AVCaptureVideoPreviewLayer layer, MyMetadataOutputDelegate metadataSource)
		{
			BackgroundColor = fillColor;


			this.layer = layer;
			layer.MasksToBounds = true;
			layer.VideoGravity = AVCaptureVideoPreviewLayer.GravityResizeAspectFill;

			Frame = UIScreen.MainScreen.Bounds;
			layer.Frame = Frame;
			Layer.AddSublayer(layer);

			var label = new UILabel(new RectangleF(40, 80, UIScreen.MainScreen.Bounds.Width - 80, 80));
			AddSubview(label);

			metadataSource.MetadataFound += (s, e) => label.Text = e.StringValue;

		}

		public override void LayoutSubviews()
		{
			base.LayoutSubviews();
			layer.Frame = Bounds;
		}
	}

	public class MyMetadataOutputDelegate : AVCaptureMetadataOutputObjectsDelegate
	{
		public override void DidOutputMetadataObjects(AVCaptureMetadataOutput captureOutput, AVMetadataObject[] metadataObjects, AVCaptureConnection connection)
		{
			foreach(var m in metadataObjects)
			{
				if(m is AVMetadataMachineReadableCodeObject)
				{
					MetadataFound(this, m as AVMetadataMachineReadableCodeObject);
				}
			}
		}

		public event EventHandler<AVMetadataMachineReadableCodeObject> MetadataFound = delegate {};
	}

	public class SimpleViewController : UIViewController
	{
		AVCaptureSession session;
		AVCaptureMetadataOutput metadataOutput;

		public SimpleViewController() : base()
		{
		}

		public override void DidReceiveMemoryWarning()
		{
			// Releases the view if it doesn't have a superview.
			base.DidReceiveMemoryWarning();
		}

		public override void ViewDidLoad()
		{
			base.ViewDidLoad();
			
			session = new AVCaptureSession();
			var camera = AVCaptureDevice.DefaultDeviceWithMediaType(AVMediaType.Video);
			var input = AVCaptureDeviceInput.FromDevice(camera);
			session.AddInput(input);

			//Add the metadata output channel
			metadataOutput = new AVCaptureMetadataOutput();
			var metadataDelegate = new MyMetadataOutputDelegate();
			metadataOutput.SetDelegate(metadataDelegate, DispatchQueue.MainQueue);
			session.AddOutput(metadataOutput);
			//Confusing! *After* adding to session, tell output what to recognize...
			foreach(var t in metadataOutput.AvailableMetadataObjectTypes)
			{
				Console.WriteLine(t);
			}
			metadataOutput.MetadataObjectTypes = new NSString[] {
				AVMetadataObject.TypeQRCode,
				AVMetadataObject.TypeEAN13Code
			};

			var previewLayer = new AVCaptureVideoPreviewLayer(session);
			var view = new ContentView(UIColor.Blue, previewLayer, metadataDelegate);

			session.StartRunning();
		
			this.View = view;
		}
	}

	[Register("AppDelegate")]
	public  class AppDelegate : UIApplicationDelegate
	{
		UIWindow window;
		SimpleViewController viewController;

		public override bool FinishedLaunching(UIApplication app, NSDictionary options)
		{
			window = new UIWindow(UIScreen.MainScreen.Bounds);

			viewController = new SimpleViewController();
			window.RootViewController = viewController;

			window.MakeKeyAndVisible();
			
			return true;
		}
	}

	public class Application
	{
		static void Main(string[] args)
		{
			UIApplication.Main(args, null, "AppDelegate");
		}
	}
}

My Favorite iOS 7 APIs Part 3 : CoreMotion (iPhone 5S only)

The new M7 coprocessor in the iPhone 5S makes pedometer apps trivial:

    if(CMStepCounter.IsStepCountingAvailable)
    {
        var counter = new CMStepCounter();
        //Last 8 hours
	counter.QueryStepCount(NSDate.FromTimeIntervalSinceNow(-8 * 60 * 60), NSDate.Now, NSOperationQueue.CurrentQueue, StepQueryHandler);
    }

    void StepQueryHandler(int nssteps, NSError error)
    {
        Console.WriteLine(nssteps);
    }

My Favorite iOS 7 APIs: Multipeer Connectivity

Multipeer Connectivity allows you to discover and share data with other iOS devices within Bluetooth radio range or on the same WiFi subnet. It is much easier to use than Bonjour.

I wrote a simple MPC chat program in Xamarin.iOS.

There’s necessarily a few hundred lines of code, but 90% of it is just the scaffolding necessary to support a four-view application. The actual discovery and communication is done with just a handful of code.

There are two phases for Multipeer Connectivity: Discovery and the Session phase. During the Discovery phase, one device acts as a coordinator or browser, and many devices advertise their interest in connecting. Devices advertise their interest in sharing a protocol defined by a string.

I created a base class DiscoveryViewController : UIViewController for both the advertising and browsing:

//Base class for browser and advertiser view controllers
public class DiscoveryViewController : UIViewController
{
	public MCPeerID PeerID { get; private set; }

	public MCSession Session { get; private set; }

	protected const string SERVICE_STRING = "xam-chat";

	public DiscoveryViewController(string peerID) : base()
	{
		PeerID = new MCPeerID(peerID);
	}

	public override void ViewDidLoad()
	{
		base.ViewDidLoad();

		Session = new MCSession(PeerID);
		Session.Delegate = new DiscoverySessionDelegate(this);
	}

	public void Status(string str)
	{
		StatusChanged(this, new TArgs<string>(str));
	}

	public event EventHandler<targs <string>> StatusChanged;
}

This base class holds a PeerID (essentially, the nickname for the device in the chat), an MCSession (the actual connection), and a SERVICE_STRING that specifies what type of MPC session I support (“xam-chat”). Additionally, it exposes an event StatusChanged (which is subscribed to by a UILabel in the DiscoveryView class (not shown, because it’s trivial).

Events relating to the MCSession are handled by ChatSessionDelegate, but those occur after discovery, so putting that aside for now, let’s look at how simple are the AdvertiserController and BrowserController subtypes of DiscoveryViewController:

public class AdvertiserController : DiscoveryViewController
{
	MCNearbyServiceAdvertiser advertiser;

	public AdvertiserController(string peerID) : base(peerID)
	{
	}

	public override void DidReceiveMemoryWarning()
	{
		// Releases the view if it doesn't have a superview.
		base.DidReceiveMemoryWarning();

		// Release any cached data, images, etc that aren't in use.
	}

	public override void ViewDidLoad()
	{
		base.ViewDidLoad();

		View = new DiscoveryView("Advertiser", this);
		var emptyDict = new NSDictionary();
		Status("Starting advertising...");

		advertiser = new MCNearbyServiceAdvertiser(PeerID, emptyDict, SERVICE_STRING);
		advertiser.Delegate = new MyNearbyAdvertiserDelegate(this);
		advertiser.StartAdvertisingPeer();
	}
}

class MyNearbyAdvertiserDelegate : MCNearbyServiceAdvertiserDelegate
{
	AdvertiserController parent;

	public MyNearbyAdvertiserDelegate(AdvertiserController parent)
	{
		this.parent = parent;
	}

	public override void DidReceiveInvitationFromPeer(MCNearbyServiceAdvertiser advertiser, MCPeerID peerID, NSData context, MCNearbyServiceAdvertiserInvitationHandler invitationHandler)
	{
		parent.Status("Received Invite");
		invitationHandler(true, parent.Session);
	}
}

public class BrowserController : DiscoveryViewController
{
	MCNearbyServiceBrowser browser;

	public BrowserController(string peerID) : base(peerID)
	{
	}

	public override void DidReceiveMemoryWarning()
	{
		// Releases the view if it doesn't have a superview.
		base.DidReceiveMemoryWarning();

		// Release any cached data, images, etc that aren't in use.
	}

	public override void ViewDidLoad()
	{
		base.ViewDidLoad();

		View = new DiscoveryView("Browser", this);

		browser = new MCNearbyServiceBrowser(PeerID, SERVICE_STRING);
		browser.Delegate = new MyBrowserDelegate(this);

		Status("Starting browsing...");
		browser.StartBrowsingForPeers();
	}

	class MyBrowserDelegate : MCNearbyServiceBrowserDelegate
	{
		BrowserController parent;
		NSData context;

		public MyBrowserDelegate(BrowserController parent)
		{
			this.parent = parent;
			context = new NSData();
		}

		public override void FoundPeer(MCNearbyServiceBrowser browser, MCPeerID peerID, NSDictionary info)
		{
			parent.Status("Found peer " + peerID.DisplayName);
			browser.InvitePeer(peerID, parent.Session, context, 60);
		}

		public override void LostPeer(MCNearbyServiceBrowser browser, MCPeerID peerID)
		{
			parent.Status("Lost peer " + peerID.DisplayName);
		}

		public override void DidNotStartBrowsingForPeers(MCNearbyServiceBrowser browser, NSError error)
		{
			parent.Status("DidNotStartBrowingForPeers " + error.Description);
		}
	}
}

Quite a few lines, but very straightforward: the advertiser uses the iOS class MCNearbyServiceAdvertiser and the browser uses the class MCNearbyServiceBrowser. The browser’s delegate responds to discovery by calling MCNearbyServiceBrowser.InvitePeer and the advertiser’s delegate responds to an invitation by passing true to the invitationHandler.

The Chat Session

When the invitation is accepted, it’s time for the ChatSessionDelegate to take over:

public class ChatSessionDelegate : MCSessionDelegate
{
	public DiscoveryViewController Parent{ get; protected set; }

	public ChatViewController ChatController
	{
		get; 
		set;
	}

	public ChatSessionDelegate(DiscoveryViewController parent)
	{
		Parent = parent;
	}

	public override void DidChangeState(MCSession session, MCPeerID peerID, MCSessionState state)
	{
		switch(state)
		{
		case MCSessionState.Connected:
			Console.WriteLine("Connected to " + peerID.DisplayName);
			InvokeOnMainThread(() => Parent.NavigationController.PushViewController(new ChatViewController(Parent.Session, Parent.PeerID, peerID, this), true));
			break;
		case MCSessionState.Connecting:
			Console.WriteLine("Connecting to " + peerID.DisplayName);
			break;
		case MCSessionState.NotConnected:
			Console.WriteLine("No longer connected to " + peerID.DisplayName);
			break;
		default:
			throw new ArgumentOutOfRangeException();
		}
	}

	public override void DidReceiveData(MCSession session, MonoTouch.Foundation.NSData data, MCPeerID peerID)
	{

		if(ChatController != null)
		{
			InvokeOnMainThread(() => ChatController.Message(String.Format("{0} : {1}", peerID.DisplayName, data.ToString())));
		}
	}

	public override void DidStartReceivingResource(MCSession session, string resourceName, MCPeerID fromPeer, MonoTouch.Foundation.NSProgress progress)
	{
		InvokeOnMainThread(() => new UIAlertView("Msg", "DidStartReceivingResource()", null, "OK", null).Show());

	}

	public override void DidFinishReceivingResource(MCSession session, string resourceName, MCPeerID formPeer, MonoTouch.Foundation.NSUrl localUrl, out MonoTouch.Foundation.NSError error)
	{
		InvokeOnMainThread(() => new UIAlertView("Msg", "DidFinishReceivingResource()", null, "OK", null).Show());
		error = null;

	}

	public override void DidReceiveStream(MCSession session, MonoTouch.Foundation.NSInputStream stream, string streamName, MCPeerID peerID)
	{
		InvokeOnMainThread(() => new UIAlertView("Msg", "DidReceiveStream()", null, "OK", null).Show());

	}
}

Again, this is mostly scaffolding, but be sure to note that it expects to be called on a background thread and uses InvokeOnMainThread to manipulate the UI. It also relies on the ChatViewController:

public class ChatViewController : UIViewController, IMessager
{
	protected MCSession Session { get; private set; }

	protected MCPeerID Me { get; private set; }

	protected MCPeerID Them { get; private set; }

	ChatView cv;

	public ChatViewController(MCSession session, MCPeerID me, MCPeerID them, ChatSessionDelegate delObj) : base()
	{
		this.Session = session;
		this.Me = me;
		this.Them = them;

		delObj.ChatController = this;
	}

	public override void DidReceiveMemoryWarning()
	{
		// Releases the view if it doesn't have a superview.
		base.DidReceiveMemoryWarning();

		// Release any cached data, images, etc that aren't in use.
	}

	public override void ViewDidLoad()
	{
		base.ViewDidLoad();

		cv = new ChatView(this);
		View = cv;

		cv.SendRequest += (s, e) => {
			var msg = e.Value;
			var peers = Session.ConnectedPeers;
			NSError error = null;
			Session.SendData(NSData.FromString(msg), peers, MCSessionSendDataMode.Reliable, out error);
			if(error != null)
			{
				new UIAlertView("Error", error.ToString(), null, "OK", null).Show();
			}
		};
	}

	public void Message(string str)
	{
		MessageReceived(this, new TArgs<string>(str));
	}

	public event EventHandler<targs <string>> MessageReceived = delegate {};
}

Again, it’s the simplicity that stands out: Session.SendData is used to transmit a string. The SendRequest event is wired to a UITextField and the MessageReceived event is wired to a UILabel:

public class ChatView : UIView
{
	readonly UITextField message;
	readonly UIButton sendButton;
	readonly UILabel incoming;

	public ChatView(IMessager msgr)
	{
		BackgroundColor = UIColor.White;

		message = new UITextField(new RectangleF(10, 54, 100, 44)) {
			Placeholder = "Message"
		};
		AddSubview(message);

		sendButton = new UIButton(UIButtonType.System) {
			Frame = new RectangleF(220, 54, 50, 44)
		};
		sendButton.SetTitle("Send", UIControlState.Normal);
		AddSubview(sendButton);

		incoming = new UILabel(new RectangleF(10, 114, 100, 44));
		AddSubview(incoming);

		sendButton.TouchUpInside += (sender, e) => SendRequest(this, new TArgs<string>(message.Text));
		msgr.MessageReceived += (s, e) => incoming.Text = e.Value;
	}

	public event EventHandler<targs <string>> SendRequest = delegate {};
}

The ChatViewController.Message method is called by the ChatSessionDelegate.DidReceiveData method.

And that’s really all there is to it.

Dynamic Type in iOS 7: Not Quite as “Dynamic” as You Might Think

One of the nice features in iOS 7 for old fogeys such as myself is that the user can use the general Settings to increase and decrease the fonts used in apps. This is called “Dynamic Type.” Judging by developer forums, I’m not the only one who thought that this was something that was built in to the various widgets. It’s not. To do this in your own app, you have to respond to the ContentSizeCategoryChanged notification and invalidate the layout in any widgets you want to have change size. In Xamarin.iOS, the code looks like this:

public class ContentView : UIView
{
    public ContentView()
    {
       var txt = new UITextView(UIScreen.MainScreen.Bounds);
       txt.Text = "Lorem ipsum dolor ...";
       ResetDynamicType();
       //Respond to notification of change
        UIApplication.Notifications.ObserveContentSizeCategoryChanged((s,e) => {
          ResetDynamicType();
        });
        AddSubview(txt);
    }
    public void ResetDynamicType()
    {
        txt.Font = UIFont.PreferredFontForTextStyle(UIFontTextStyle.Body);
    }
}

The crucial point being that you have a ResetDynamicType method (or whatever you want to call it) that you call both at initialization and then again every time you get notified of a request to change font size (if you want, you can read the new size from the e in the lambda). So “Dynamic Type” isn’t really anything special in terms of display: it’s still up to the application developer to have a function that’s called. What is dynamic is the value returned by UIFont.PreferredFontForTextStyle, which varies based on the user’s Settings.