Growing Your First Xamarin.Forms Mac App

The big buzz ever since Microsoft Build has been about Xamarin.Forms 3.0 - and rightfully so - there's a lot of cool features in it.

But I don't want to overlook the next release of Xamarin.Forms - and that's 2.3.5. There are a couple of big time features in this release that are hiding under that last version number being bumped from 2.3.4.

A show stopping feature of Xamarin.Forms 2.3.5 is the ability to create a macOS app. And according to everything I've read, it "should just work".

That's what I want to take a look at in this article - how much work it really takes to create a Xamarin.Forms macOS app.

The Setup

I want to prove out, at least to myself, the viability of adding a macOS app to a Xamarin.Forms solution; and have it work well enough where I could potentially deliver it to a customer.

In other words, how much work would it be for me to create a macOS app from an existing Xamarin.Forms application and are there any shortfalls that I would hit immediately?

And most importantly of all, would I be able to stay away from having to write any macOS specific code?

The TL;DR is yes, it is viable... kind of.

Read on for the full story. And all the code can be found on GitHub, of course.

The App

In order to test that out, I decided to throw together a quick app that contains a lot of the UI elements, navigation metaphors, and Xamarin.Forms techniques (like Behaviors) that I have found myself using a lot lately.

The idea is to get a generic app working on iOS and Android with 100% shared code (the intialization code notwithstanding). Then add in the macOS app, and see how it works without adding any platform specific code or making any compromises to functionality.

So... what kind of app should I make?

Well, it's summer right now in Wisconsin. I love to garden, in fact, I'm sitting in my backyard typing this looking at my garden ... So the app that I decided to create would be a "Garden Tracker". So I could theoretically keep track of what I have planted, what managed to die on me, and then restock plants.

The app holds a list of plants that are "currently planted in the ground" in a ListView.

Via a ContextAction on a cell in the ListView the app allows me to record that a plant died. Taking it off the list of "planted" plants and moving it to another screen of "dead plants". (Unfortunately though I love to garden, I do sometimes I have the touch of death.)

Those two pages are both comprised of ListViews with straight up TextCells and are navigated to via a MasterDetailPage.

There is a third page which is also reached via the MasterDetailPage and that's the garden center page - where I can restock on plants - which adds them back into the "currently planted" page. This third page groups plants by type, vegetables, herbs, and flowers - arranged on a TabView.

There's also a page that's pushed onto the navigation stack via tapping on a plant on the "currently planted" page - which shows the plant's name along with a Wikipedia page - so I could test out a WebView.

So while not a totally rigorous app - I felt it exercised a lot of the common functionaity found in a Xamarin.Forms app, and would be a good test to see how well it ported over to macOS.

The macOS Porting

I was pleasantly surprised that getting the app to run on macOS was super simple. It was much more work to create the app in the first place than it was to add in the macOS portion of it.

There's a tutorial on Xamarin's site going over what to do, but I'll run over it here as well with some commentary.

I should mention that I started development with Xamarin.Forms 2.3.5 from the get-go, which is currently still in beta. So you'll need to update all of your NuGets to a 2.3.5 version - so make sure "show pre-releases" is checked in the NuGet Package Manager.

First up, I went to File -> New Project and added a Mac Cocoa App, as you can see in the screenshot below.

After running through the naming of the app and where it should be saved it's added to your solution and looks deceptively like an iOS app. There are storyboards, view controllers, and an app delegate.

Basic Housekeeping

The first things that need to be done are to add references - to Xamarin.Forms 2.3.5 and to the core project where all of your custom code is stored.

While you're doing maintenance, go into the Info.plist file and delete the entry that says NSMainStoryboardFile. (You'll probably have to go into the source view to see it.) This will make sure the storyboard doesn't get launched when the app opens.

So, if how do you make sure the app opens the proper Xamarin.Forms window?

AppDelegate

Just like in iOS, the AppDelegate in a Cocoa app is what gets called when lifecyle events such as DidFinishedLaunching occured.

The next step, and this is an important step, is to make the AppDelegate class inherit from FormsApplicationDelegate.

The definition will look like this:

public class AppDelegate : FormsApplicationDelegate

Since there's not a storyboard file to define the window the app should take up, we need to override the MainWindow property within the AppDelegate.

That MainWindow returns an NSWindow, which is best setup in the constructor of the AppDelegate.

Stealing... I mean drawing inspiration from... how that NSWindow is setup from the linked tutorial above from Xamarin, we get the following:

NSWindow mainWindow;

public AppDelegate()
{
	var style = NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Titled;

	var rect = new CoreGraphics.CGRect(200, 1000, 1024, 768);
	mainWindow = new NSWindow(rect, style, NSBackingStore.Buffered, false);
	mainWindow.Title = "The Garden Center";
	mainWindow.TitleVisibility = NSWindowTitleVisibility.Hidden;
}

public override NSWindow MainWindow
{
    get => mainWindow;
}

It's pretty self-explanatory on what's going on. Within the constructor the properties of the window are getting set, and it's being returned in the MainWindow property.

There is one more boilerplate, ininitialization, step that needs to be taken care of.

Go into the MainClass and add the following line into the static constructor:

NSApplication.SharedApplication.Delegate = new AppDelegate();

So, that's it. There was some macOS specific code ... but it was all intialization, so I won't count it. Plus I'm sure once Xamarin gets the templates setup within Visual Studio, these steps won't even be necessary.

Only thing left to do is run the app...

The Results

And ....

It's ugly!!

Who would have ever guess that a macOS app, by default, would be so ugly!!

To be fair, the out of the box Android apps aren't exactly pretty either, but I expected more from Apple!

Alright - looks aside - how does it work?

The MasterDetailPage automatically laid itself out so the Master portion was always visible. Cool! Just like I would have expected.

Tapping on those ugly, ugly, buttons brings up the different pages - and they work as expected too. In fact, the App.Properties data store works great - even better than it does on the iOS and Android simulators in that it actually does save to 'disk' when hard exiting (pressing the stop button) from the IDE.

The ListViews appear to be working as expected as well.

The Behavior I have to bind the ListView.ItemSelected event to a Command in the view model works flawlessly to bring up a child navigation page.

But there is a problem...

An Issue

I had the detail page defined with a StackLayout with a Label and a WebView as its children. Worked great on iOS and Droid.

But on macOS the StackLayout only shows the bottom portion of the WebView. Weird.

It was easily solved by putting those 2 controls into a Grid.

Not a big deal on this small app, but it might be a pain on a larger app to correct in many places.

Another Issue

Now this may be due to the way macOS works ... but I could not get the ContextAction on the TextCell to fire on the "Planted" page.

So there's no way for me to kill a plant!

Not a bad thing in real life ... but if an app relies on those ContextActions there's some work you need to do in your app.

Overall

I would say ... not too bad.

According to this blog post, there are still some known issues - but some of those have been fixed as I write this.

The layout engine has some bumps yet and I was able to find another bug with the ContextActions.

But it is a preview yet.

I am pretty excited to be able to write macOS apps - without too much effort - with Xamarin.Forms ... now to work on how they look.

Again, check out all the code on GitHub here!