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 private UIWindow 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!


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.

542 Words

2017-05-26 20:20 +0000