UIDebuggingInformationOverlay and Xamarin
Earlier today I was browsing Hacker News as I usually do
when I’m bored I want to learn something cool and I stumbled across
this post
by Ryan Peterson about a private iOS API called UIDebuggingInformationOverlay
.
As Ryan puts it:
UIDebuggingInformationOverlay
is a privateUIWindow
subclass created by Apple, presumably to help developers and designers debug Appleās own iOS apps.
The post also has code for using it in your Swift apps, but what if you’d want to use it in your Xamarin apps?
Turns out it’s quite easy, let’s see what Ryan’s code does step-by-step:
let overlayClass = NSClassFromString("UIDebuggingInformationOverlay") as? UIWindow.Type
This gets us the UIDebuggingInformationOverlay
’s class
object, the
equivalent of a Type
object in C#.
Next, he proceeds calling a static method, prepareDebuggingOverlay
, using a
selector:
_ = overlayClass?.perform(NSSelectorFromString("prepareDebuggingOverlay"))
We need to call this method, otherwise the overlay will be empty. Now we can
just get a reference to the overlay itself, calling the overlay
static method:
let overlay = overlayClass?.perform(NSSelectorFromString("overlay"))
.takeUnretainedValue() as? UIWindow
At this point showing the overlay is as simple as calling toggleVisibility
:
_ = overlay?.perform(NSSelectorFromString("toggleVisibility"))
And that’s it!
It’s C# time
Now, let’s do it with Xamarin!
The first step is to get an handle to the native Objective-C class
.
Luckily, Xamarin provides a method just for that:
var overlayClass = Class.GetHandle("UIDebuggingInformationOverlay");
overlayClass
is a IntPtr
object since it’s a pointer to an underlying
native object.
Now we need to call prepareDebuggingOverlay
, but how? We have just an
opaque pointer!
DllImport to the rescue
We must do it like how the Objective-C runtime does: we must send a message to
the object, using the native objc_msgSend
function. Since that’s a C
function we can just use P/Invoke.
We don’t need any arguments, so we can import the most basic version of it:
[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern IntPtr objc_msgSend(IntPtr target, IntPtr selector);
and then just call it:
objc_msgSend(overlayClass, new Selector("prepareDebuggingOverlay").Handle);
we can now reuse this function for the next step, getting a reference to the overlay:
var overlay = objc_msgSend(overlayClass, new Selector("overlay").Handle);
and also for toggling it:
objc_msgSend(ovrlay, new Selector("toggleVisibility").Handle);
And we’re done! We should now see the debugging overlay over our app!
Let’s clean it up
I like my code to be well contained within its own class, I wouldn’t use this code as is, so I encapsulated all of it in a simple wrapper:
using System;
using System.Runtime.InteropServices;
using ObjCRuntime;
namespace debugoverlay
{
public class UIDebuggingInformationOverlay
{
[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern IntPtr objc_msgSend(IntPtr target, IntPtr selector);
static IntPtr _overlayClass = Class.GetHandle("UIDebuggingInformationOverlay");
static Selector _prepareSelector = new Selector("prepareDebuggingOverlay");
static Selector _overlaySelector = new Selector("overlay");
static Selector _toggleSelector = new Selector("toggleVisibility");
IntPtr _overlay;
public static void PrepareDebuggingOverlay()
{
objc_msgSend(_overlayClass, _prepareSelector.Handle);
}
public UIDebuggingInformationOverlay()
{
_overlay = objc_msgSend(_overlayClass, _overlaySelector.Handle);
}
public void ToggleVisibility()
{
objc_msgSend(_overlay, _toggleSelector.Handle);
}
}
}
It may need another bit of polishing (like making it a proper NSObject binding), but for debugging purposes it’s good enough. Feel free to use it!
Wrap-up
I’ll try to come up with a better version of the wrapper just for the sake of learning a bit more about C#/Objective-C bindings, if you have some advice feel free to reach out!
UPDATE 7 July 2017: The binding code is now on GitHub and on NuGet.