Viewing console output in iPad Swift Playgrounds

I’m learning Swift and SwiftUI, and as part of this journey I’m working my way through Donny Wals‘ excellent Practical Combine: An introduction to Combine with real examples!

The problem

I’ve been working on my 11″ iPad Pro running iPad OS 13.5.1, with the book open on one half of the screen and Swift Playgrounds on the other, and I’ve noticed that the results from calls to print in Combine callbacks are not output on my iPad:

A Workaround

I worked around this by just entering the variable name:

Another problem

This feels like a bug to me, but I had a good workaround. Until, that is, I wanted to use the Combine print function. I needed to see that output.

This is what Donny’s book showed, and what my iPad Swift Playground showed:
Combine print showing nothing

The console output was lost for the prints both in the reactive chain and in the sink. Googling gave me no obvious answers.

When learning, this is exactly the kind of thing I like to dig into, and try solve.

A solution

This is the solution I came up with:

import Combine

class PlaygroundConsole : TextOutputStream, CustomStringConvertible {
    var text = String()
    func write(_ string: String) {
        text.append(string)
    }
    var description : String { text}
}
let console = PlaygroundConsole()

// Based on https://stackoverflow.com/a/47223166
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
    let output = items.map { "\($0)" }.joined(separator: separator)
    console.write(output + terminator)
}

I added a TextOutputStream which I could pass to the Combine print and I shadowed the global print function to write to the same stream. The implementation simply stores up the text, and it can then be output at the end.

If I change the Combine print to send it’s output to the PlaygroundConsole and then examine the console variable’s content at the end, this is what I see:

Note the modified .print(to: console) in the Combine chain.

Disclaimer: I’m new around here

As I said, I am new to Swift. This is a very simple solution, but hopefully it will help someone else too.

This could already be a solved problem, and I didn’t stumble upon it when Googling. Also, the above code is newbie code – feel free to let me know of any mistakes or non-idiomatic code (I’ve been doing C# for many years).

In any event, if you are also learning Swift and SwiftUI and want to start from the beginning using Apple’s Combine framework, Practical Combine is a great way to learn.

Code snippets for Xamarin Forms XAML Platform-specifics

This post is part of Luis Matos Code Snippets’ Xamarin Month

In the early days of Xamarin Forms, if you wanted to use a platform-specific feature of a page or a view, you’d have to write a custom renderer.

Later came effects, which were a little less painful.

But the game-changer was when Microsoft decided to build-in common platform-specific features, right into the XAML, so that you could turn on a platforms-specific feature for one platform only, with the directive being ignored for others.

The canonical example in my mind is telling iOS to use the Safe Area, depending on the specific phone, so parts of your UI are not cut off on certain phones. But here is the thing: I can never remember the magical incantation you use to do that. I have to google Xamarin ios safe area every time. There has to be a better way.

             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="true"

Of course there is – a Code Snippet. Now all I have to remember is psios to include the namespace and then pssa to include the safe-area platform specific … I type the snippet, and hit tab, and bingo, the iOS Platform-specific XAML is entered into the editor (ios:Page.UseSafeArea="true" in case you were wondering).

No more googling. In that spirit I’ve defined a whole slew of snippets for these platform-specifics, including one to import the ios namespace used in the pssa snippet.

The snippet file is here. To use it go to Tools->Code Snippets Manager and hit Import and import this file. Here are some of the Snippets I’ve defined

Shortcut Explanation
psios Snippet to bring in the iOS namespace for platform specific code
pssa Snippet for a iOS safe area
psnpbs Hide NavigationPage Bar Separator on iOS
psmdps Apply MasterDetailPage Shadow on iOS
pslpt Show Large Page Titles on iOS
pshpi Show Home Indicator Visibility on iOS
pspsv Set Page Status Bar Visibility on iOS

There are a lot more platform-specifics than these, and of course they are available for Android too. If I find myself typing them more than once, I’ll create a snippet.

But really, if you find yourself typing the same thing over and over, do yourself a favor and define some snippets. It’s why you became a programmer in the first place, remember? Make the computer do the easy stuff for you, and you can focus on the hard stuff.

Building and releasing a Xamarin WatchOS app to TestFlight

I’m working on my third Apple Watch app built using Xamarin. In this post I’m going to describe how I develop and what some of the issues were that I encountered when testing and uploading to the App Store for beta testing.

The edit/build/run development loop

I build and run and test on the WatchOS simulator when doing general development, since this is the fastest, and I can debug apps on the WatchOS simulator

At the time of writing (early June 2020), the released version of Visual Studio for Mac has an error where I can’t run my apps on real devices, so I have switched to the preview channel which contains a fix for the issue.

When I’m ready to commit a new feature, I’ll test on a physical watch. To do that I build the iOS host app in release mode, and deploy that to my iPhone (I don’t deploy to the Apple Watch). On the iPhone I open Apple’s Apple Watch app, scroll down to my app:

I then force-quit this iOS Apple Watch app by swiping it away, and go back to my app as above, and it shows as updating (assuming I already installed it). 30-120 seconds later, and the app is installed on my watch and I’m ready to test it:

Uploading to TestFlight

To release to TestFlight I do a full build of the iOS app and then right-click and select Archive for Publishing:

Then I fire up Xcode, go to Window->Organizer and tap Distribute App to kick off the upload:

A word about certificates: I use fastlane match to manage my certificates and it is a lifesaver. When I want to set up my certificates on a new dev machine I just type

fastlane match developer

and that is it.

Blank complications (not a Xamarin issue)

While testing my Apple Watch complications (little icons on the watch face), I saw that some of them were displayed as tinted squares:

The images I’d created for an Apple Watch Complication were in the wrong format. The Apple documentation says “Complication images use only the alpha channel of the image.” … I don’t know about you, but this didn’t mean much to me.

I fixed this by downloading ImageMagick and using this command to turn the icons into black and white:

bash
magick convert Icon.png -threshold 50% Icon.png

I did this for all the non-color ones (Circular, Extra Large, Modular and Utilitarian).

Crash on launch #1

Even though my app ran fine on my Apple Watch when built and run locally, when I installed it and ran it from TestFlight it crashed immediately on launch.

I installed a sysdiagnose for watchOS profile to let me see the logs from the Apple Watch in Apple’s Console app, and got a clue. This error:

WatchOS app with Intent Exension: DYLD, dependent dylib '...libmonosgen-2.0.1.dylib' not found for TestFlight beta

I googled it, and found that I’d reported the same issue for another app I’d created, and there was a workaround.

Normally an Apple Watch app has two projects: The main project (which contains a storyboard and not much else), and an extension project where all the code resides. Because I want my app to be able to respond to Siri to create notes in my app, I have a third project highlighted in blue below, an Intents App Extension to handle Siri request on the Apple Watch:

Xamarin tries to share code between the assemblies, and it doesn’t work in this situation.

I needed to specify an mtouch flag in the build settings for both the extension, and the intents extension projects: --nodevcodeshare

This fixed the issue, but had unintended consequences.

Error uploading to TestFlight

I was pretty confident after fixing the issue with the shared code by enabling the nodevshare flag. Indeed the app uploaded to TestFlight, and I waited for it to be processed. I received this email:

From: App Store Connect <no_reply@email.apple.com>
Subject: App Store Connect: Your app "WatchGit" (Apple ID: 1234 Version: 2020.1 Build: 4) has one or more issues

Dear Developer, We identified one or more issues with a recent delivery for your app, "WatchGit" 2020.1 (4). Please correct the following issues, then upload again.  

ITMS-90389: Size Limit Exceeded - The size of watch application /Payload/WatchGit.iOS.app/Watch/WatchGitWatchOSWatchOSApp.app' (92MB) has exceeded the 75MB size limit. 

Best regards,
 The App Store Team

It seems as though by removing the sharing of code between my watch app’s extension, and the intents extension, I’d exceeded the max app size.

All was not lost however, I knew how to handle this one: I could tell the linker to link everything which would remove all code that wasn’t needed. You’ll see I changed the Linker behavior from Link Framework SDKs Only to Link All.

I also told the linker to skip my “Core” project, where I put all non platform-specific code that can be shared between iOS, WatchOS, MacOS, Android, UWP, Wear OS, Tizen and xBox (which is the whole point of using Xamarin in the first place, perhaps second only to being able to use C#):

Crash on launch #2

Once again, brimming with confidence, I uploaded my new build to TestFlight in the App Store. I was so confident I didn’t even test locally on a real phone, just in the Simulator. That was a mistake.

Once again it crashed on launch, but I was able to reproduce this crash on a local Apple Watch – I sprinkled Exception handlers in all the places I’d missed, and discovered the cause of a crash.

I use a Shared Project to share code between my iOS app and my WatchOS app:

specifically a class I use to to transfer information between the devices as serialized JSON:

  public class TokenInfoMessage : Message {
    public string AccessToken { get; set; }
  }

This was being linked away because I wasn’t explicitly instantiating it anywhere, instead I let the JSON deserializer do that:

var credentials = SimpleJson.DeserializeObject<TokenInfoMessage>(json));

The solution was once again straight-forward and well documented. If you want to tell the linker not to throw code away you can define and use a Preserve attribute:

using System;
namespace WatchGit.iOS.Shared {
  [Preserve(AllMembers = true)]
  public class Message {
    public string MessageKind { get; set; }
    public string MessageVersion { get; set; }
  }

  [Preserve(AllMembers = true)]
  public class TokenInfoMessage : Message {
    public string AccessToken { get; set; }
  }
}

Menus not working

On the real watch, when testing, I found that I got a crash when hard-pressing the screen to display my app’s menu:

ObjCRuntime.RuntimeException: Can't register the class xyz when the dynamic registrar has been linked away.

Fortunately I remembered getting this error in my first Apple Watch app, and remembered the magic formula to stop the dynamic linker from being thrown away in my main Interface Controller:

void DummyMethod() {
  Log.D("This is it");
}

public override void Awake(NSObject context) {
  base.Awake(context);

  // Forces dynamic linking not to be thrown away
  Action action = DummyMethod;
  try {
    ObjCRuntime.Runtime.ConnectMethod(action.Method, new ObjCRuntime.Selector("xyzzy"));
  } catch(Exception) { }

I don’t remember the details, but I guess the menu system must use dynamic registrar, and this code keeps it from being linked away.

My menus worked:

Finally, bliss, I could download my app from TestFlight and test it myself, and perhaps even one day make it available as public TestFlight beta…

Bonus – Beta review rejection

After a lot of testing I decided to make my app available as a public TestFlight beta. This involves adding a tester that isn’t one of the registered developers. Apple do some manual checks, which, while not as exhaustive as when you publish the app to the store, invariably they throw up an issue or two.

So I was not surprised when I received my rejection email. I was surprised at the reason.

App Store Connect

Dear Atadore SARL,

Your app, WatchGit 2020.1 (12), has been reviewed, but cannot be made available for TestFlight beta testing.

For details, or to contact App Store Review, visit Resolution Center in App Store Connect.

Best regards,
App Store Review

When I checked out the resolution center, expecting some deeply technical reason as to why it was not accepted, I saw that all they wanted was a video of the app in action:

Jun 2, 2020 at 7:56 AM
From Apple
Guideline 2.1 - Information Needed

We have started the review of your app, but we are not able to continue because we need access to a video that demonstrates the current version of your app in use on a physical watchOS device.

Please ensure the video you provide shows a physical iOS device (not a simulator).

Next Steps

I updated the TestFlight test details with a link to a video, and a couple of days later, the app was approved for public TestFlight testing!

What next?

Now comes the scary part. Will anyone care about my new app? I guess I’ll find out!

All these hassles do sometimes make me question my decision to use Xamarin for Apple Watch apps, especially when it seems as though there may be doubt about support going forward.

I am tempted to use Swift for my next app, just to get the experience. But I do love C# – going back to using closures to handle async task completion rather than using async/await seems like going back to the dark ages.

I realize I’ve not said what my app does. If you’ve made it this far, and you use GitHub and have an Apple Watch, please give my app, called WatchGit, a spin. You can use it to create issues using Siri, view issues, comment on issues, add emoji, close issues, assign issues, and change labels. It’s all about the issues.

Get it here.

I provide Xamarin consulting via my company Atadore. I’ve built and released apps using Xamarin for iOS, WatchOS, MacOS, Windows, Xbox, Android, Tizen, and WearOS. I’m engaged until the end of 2020, but feel free to contact me at damian@mehers.com if you have opportunities, or need a hand.

Running older versions of Xcode and Xamarin

I’m inherently optimistic. A new version of the app is available? Install it straight away! And this generally works well, I’m aware of the updates and new features and don’t get left behind.

Except when it doesn’t.

I’d updated to the latest version of Xcode (11.4) and updated Visual Studio for Mac and the corresponding Xamarin.iOS, and … let’s just say it did not go well: This method contains IL not supported when compiled to bitcode is not a message you want to see when you launch your app.

Xcode

To get back to an environment that worked, I first downloaded and installed the previous version of Xcode, 11.3.3 from https://developer.apple.com/download/more/ expanded the downloaded Xcode_11.3.1.xip and them moved the Xcode.app to my /Applications folder and renamed it to Xcode11.3.1.

I used the xcode-select command line tool to choose this new version of Xcode:

sudo xcode-select --switch /Applications/Xcode11.3.1.app

Xamarin.iOS

I didn’t need to change the version of Visual Studio for Mac that I had installed, but what I did need to do was install an older version of Xamarin.iOS which I found on https://github.com/xamarin/xamarin-macios – I installed Xamarin.iOS d16.4 which corresponds to 13.10.0.21. Once downloaded I ran the package and it installed itself. You can verify the current version from the command line:

ls -l /Library/Frameworks/Xamarin.iOS.framework/Versions/Current 
lrwxr-xr-x  1 root  wheel  10 Apr 27 07:38 /Library/Frameworks/Xamarin.iOS.framework/Versions/Current -> 13.10.0.21

This is also visible in the About page in Visual Studio.

Visual Studio

Finally in Visual Studio I went to the Visual Studio->Preferences->Projects->SDK Locations page and clicked the Browse... button to select my /Applications/Xcode11.3.1.app Xcode.

Once I’d done all this I am able to build, run and support my customers.

Returning HTML from an Azure Function

After spending a lot of time with AWS Lambdas, I’m exploring Azure functions. The out of the box example just returns a OkObjectResult as a IActionResult.

Despite googling I couldn’t work out how to return HTML in the OkObjectResult with the correct content-type (“text/html”) but it turns out I needed instead to return a ContentResult:

    [FunctionName("HelloHTML")]
    public static async Task<IActionResult> HelloHTML([HttpTrigger(AuthorizationLevel.Function, "get", Route = "hello_html")]
                                                      HttpRequest req,
                                                      ILogger log) {
      return new ContentResult { Content = "<html><body>Hello <b>world</b></body></html>", ContentType = "text/html" };
    }

Tips for building Xamarin Apple Watch apps as of December 2019

I’ve been building Apple Watch apps using Xamarin for a couple of years. I love it. I get to write apps using my favorite programming language, C#, and get all the .NET goodness that comes with it. When I’ve structured my software well I’ve re-used the core code across WearOS, Tizen, and beyond.

It isn’t always easy. That is largely because what Xamarin is doing isn’t easy. They track changes in the underlying OS and supporting technologies, and sometimes they have the rug pulled from under their feet, for example when Apple released an entirely new processor architecture with the Apple Watch 4. We are still dealing with the consequences of that one.

I’m going to share some of the things I’ve learned on the way. Some are temporary glitches, as Microsoft catch up with Apple’s changes, and some are more permanent. If you are reading this much into the future beyond December 2019, you might want to double-check the situation.

No console log messages for your Apple Watch

This one is not specific to Xamarin, but I wish I’d known about it when I started.

If you fire up the Console app on your Mac, you’ll see log messages from your Mac, from iPhone Simulators, from real iPhones, from Apple Watch Simulators, but not from your real Apple Watch.

Perhaps this is something everyone knew about but me. I always assumed that it just wasn’t possible to receive logs from the Apple Watch, but recently I found out otherwise.

The trick is that you have to install a special profile on your watch that causes it to generate logging. You’ll need to be an Apple Developer to do this.

Go to Profiles and Logs in the Apple developer site, go to the watchOS tab and download the sysdiagnose for watchOS profile on your iPhone, and install it. I generally download it onto my Mac and then AirDrop it to my phone.

Note that it expires after three days

Once it is installed, you should see log messages in the Console for your Apple Watch:

The 126MB Watch app

App Store Connect

Dear Developer,

We identified one or more issues with a recent delivery for your app, "Kanbann" 2019.1 (2). Please correct the following issues, then upload again.

ITMS-90389: Size Limit Exceeded - The size of watch application '/..WatchOSApp.app' (126MB) has exceeded the 75MB size limit.

Best regards,

The App Store Team

There are some NUGET packages that we install without really thinking when we start a new project. Newtonsoft Json.NET is one such package. Think again. This isn’t one you are going to want to use in Apple Watch apps.

The reason is that the Ahead Of Time Compilation (AOT) generates a massive codebase for code linked with Json.NET. To the point where I was having to use full linking just to get my app to be accepted into the store.

When I moved from Json.NET to an alternative, my app size reduced to around 43MB and I had space for, you know, my own code. Also moving away from link all took away a world of pain.

This makes an enormous difference to your own code/build/debug cycles, and more importantly makes the app much easier to install for end-users.

Can’t debug and run on Series 4 and 5 watches

Simply put, debugging on physical Series 4 and 5 watches isn’t possible at the moment. Frankly debugging is so horrendously slow on real devices that I only ever used it as a last resort. As of right now you can only build and run RELEASE builds on Series 4 and 5 watches. The good news is that this is likely to change soon #7012.

Can’t debug on the Simulator

Right now (December 2019) you cannot debug non-trivial apps on the Simulator either. I’ve found that as soon as I make an HttpClient call the debugger detaches. This is a known issue and the underlying cause appears to be in mono itself and a fix is on its way.

Speeding up install

Deploying your app to your Apple Watch used to be incredibly slow. I’ve found that putting your Apple Watch on the charger, and ensuring it is on the same WIFI network as your phone speeds up install-time considerably.

I generally build and install the iOS app that hosts my Apple Watch app (in Release mode), and then wait for the Watch app to be installed automatically. You can check the Apple Watch app on your iPhone to see the install progress. I occasionally have to kill that app to make it refresh the install status.

No symbols in the stack trace in App Store crashes for WatchOS apps

Although crash reports for iOS apps are symbolicated and you get to see a proper stack trace for your Xamarin iOS apps, this is not the case for Apple Watch apps at the moment.

Can’t open WatchOS storyboards in Visual Studio

There are two ways to open WatchOS Storyboards. You can use the Xamarin editor that is built in to Visual Studio, or you can editing using Xcode. As of right now, editing using Xcode seems to be the only option that is working. This is also a known issue.

Project TheWatchAppWatchOSApp is not compatible with xamarinios10 (Xamarin.iOS,Version=v1.0)

If you create a Xamarin iOS app and add a WatchOS app to it, as I just did to test something out, you’ll get an error Project YourWatchOSApp is not compatible with xamarinios10 (Xamarin.iOS,Version=v1.0. The fix is a simple change to your iOS app’s csproj file.

Resources

You can find known issues in the xamarin-macios issues page on GitHub. Despite the name it is also where you’ll find WatchOS issues.

It is very quiet, but I monitor the Xamarin Slack WatchOS channel, and will answer questions there if I can – feel free to pop in and say hello and ask and answer questions.

Final words

Despite these issues, I really enjoy building and releasing WatchOS apps using Xamarin. The issues are relatively minor, and there are almost always workaround. The Xamarin team do a stirling job.

I’ve built and released two Xamarin WatchOS apps as side-projects (Voice in a Can and Kanbann), and work as an independent consultant via my own company. I’m fully engaged until at least the middle of next year, but feel free to reach out (damian@mehers.com).

Visual Studio for Mac: “Error updating Xcode project” opening Storyboard in xCode

I’ve been getting the error Error updating Xcode project on my Mac when trying to open a storyboard using Xcode in Visual Studio, by right-clicking the storyboard and choosing Open With|Xcode Interface Builder.

Going to Help|Open Log Directory and then opening Ide.log I got a clue:

WARNING [2019-11-26 18:42:18Z]: Slow command update (28ms): Command:MonoDevelop.Ide.Commands.ViewCommands.OpenWithList, Method:MonoDevelop.Ide.Gui.Pads.ProjectPad.ProjectFileNodeCommandHandler.OnOpenWithUpdate, CommandTargetType:MonoDevelop.Ide.Gui.Pads.ProjectPad.ProjectFileNodeCommandHandler
ERROR [2019-11-26 18:42:22Z]: Error updating Xcode project
MonoDevelop.MacDev.ObjCIntegration.ObjectiveCGenerationException: Could not generate Objective-C code for action 'LanguagePicker_Activated' in class 'AppleWatchAlexa.WatchOSAppExtension.LaunguageInterfaceController' as the type 'System.nint'of its parameter 'sender' could not be resolved to Objective-C
...

For some reason it doesn’t like the System.nint in the generated code for the View Controller. I edited the generated code and removed the offending lines.

Before:

        [Action ("LanguageButton_Activated")]
        [GeneratedCode ("iOS Designer", "1.0")]
        partial void LanguageButton_Activated ();

        [Action ("LanguagePicker_Activated:")]
        [GeneratedCode ("iOS Designer", "1.0")]
        partial void LanguagePicker_Activated (System.nint sender);

        [Action ("LogoutButton_Activated")]
        [GeneratedCode ("iOS Designer", "1.0")]
        partial void LogoutButton_Activated ();

after:

        [Action ("LanguageButton_Activated")]
        [GeneratedCode ("iOS Designer", "1.0")]
        partial void LanguageButton_Activated ();

        [Action ("LogoutButton_Activated")]
        [GeneratedCode ("iOS Designer", "1.0")]
        partial void LogoutButton_Activated ();

I committed the cardinal sin of editing generated code, but at least now it opens!

Build, Notarize, Repeat: Some tips to avoid endlessly failing to notarize your Xamarin Mac app.

As with most of my posts, this is a message to my future self who will have forgotten everything I did today, to get my Xamarin Mac app notarized. I hope it helps other people too.

Apple rejected my app from the store … please include specific macOS features that the application will use and not aggregated content. I’ve no idea what it means, but I interpreted it as we like Siri and we don’t like Alexa and your app brings Alexa to the Mac so go away. I needed to release it outside the store, and that means Notarization.

It took most of a day, and I had many errors. This is my story.

Disclaimer: These instructions worked for me as of 29 Jul 2019. The further out into the future you are reading this, the less likely these instructions are to be relevant.

Resources

This blog post by Microsoft’s David Ortinau is the best starting point. It took me 50% of the way there.

I also found this Github issue to be useful … as of writing it is still open.

Apple’s documentation on notarization and a corresponding trouble-shooting guide are useful if you can understand them. I finally felt like I understood them after I got everything working.

Install the right version of Xamarin.Mac

Although David’s post mentions installing a specific version of Xamarin.Mac (d16-1), at this point I think that release had made it into the stable branch, because I was able to get this working using the latest stable release:

The easy part: Update the plist and csproj

Per the instructions I edited my csproj to include the UseHardenedRuntime:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Mac' ">
    <Optimize>true</Optimize>
    <OutputPath>bin\Release</OutputPath>
    <DefineConstants>__UNIFIED__;__MACOS__</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
...
    <CodeSignEntitlements>Entitlements.plist</CodeSignEntitlements>
    <CodeSignProvision>Developer ID Mac PP</CodeSignProvision>
    <PlatformTarget>x86</PlatformTarget>
    <UseHardenedRuntime>true</UseHardenedRuntime>
  </PropertyGroup>

I also updated my entitlements.plist to allow jit (also Microphone access because how else are you going to talk to Alexa, and Location so that Alexa knows where you are when you ask for the time or weather, although it works if the user denies location access):

Sign your app with the appropriate cert and profile

This part was the trickiest for me. Apple’s documentation talks about using a Developer ID certificate, which I stupidly thought was just my developer certificate. no No NO. This is something different.

Creating the certificate and profile

Sign in to the Apple Developer web site, and open the Certificates, Identifiers & Profiles page and then Certificates.

Check the list of certificates you have for your Mac app. Do you see one with the TYPE called Developer ID Application?

If not, this is what you need. Tap the + at the top, and on the Create a New Certificate page in the Software section, select Developer ID Application option.

Tap continue and generate and download the certificate.

I am assuming that you have already set up an Identifier for your Mac App, since you’ve likely been running it locally to develop it already.

The final step is to create a profile which combines the Developer ID Application certificate and your App Identifier. Go to the Profiles section, tap the + in the title and select the Developer ID option in the Distribution section.

On the next page select the App Id for your app from the dropdown:

On the next page select the Developer ID Certificate that you created above:

Finally give your provisioning profile a name and generate and download it:

Ensuring they are downloaded to your machine

This is the thing that screwed me up I thought that every time I downloaded the certificate or provisioning profile to my machine using Apple’s web site, that was enough. It wasn’t.

What I finally figured out was that I also needed to open Apple’s Xcode app, go to Preferences and then Accounts, and tap Download Manual Profiles (sign in first if you’ve not already done so). This makes the profiles visible to Xcode and thus Visual Studio.

Signing your app

Now that you’ve done all of the above, all being well, you should be able to open Visual Studio on your Mac, go to your Mac Project’s settings, in the Mac Signing section and select the Application Developer ID Provisioning Profile you created.

Build your app in Release mode.

Uploading the binary to Apple

That was the easy part. Next comes what I really hope will also be easy for you too. You’ll be using some Apple command-line tools that need passwords, which you can generate as an app-specific password.

In all the examples below I’ll use appspecificpassword for my password. There is also a way to store the password in the keychain, which is perfect for CI type scenarios.

To upload the binary (the .app that was generated), open a terminal and navigate to your project’s bin/Release folder where you should find your project.app, which is actually a folder.

You need to upload it for authorization. First you need to zip it. Since my app is called Voice in a Can, my .app file is VoiceInACan.app

$ zip -r VoiceInACan.zip VoiceInACan.app
  adding: VoiceInACan.app/ (stored 0%)
...
  adding: VoiceInACan.app/Contents/Info.plist (deflated 32%)
  adding: VoiceInACan.app/Contents/PkgInfo (stored 0%)
$ 

To upload the zip file you created use the xcrun altool. You’ll need to use your Apple account. The bundle-id is just a placeholder. Use what you want:

$ xcrun altool --notarize-app --primary-bundle-id "com.atadore.VoiceInACanForMac.Zip" --username damian@mehers.com --password "appspecificpassword" --file VoiceInACan.zip
2019-07-30 08:55:54.038 altool[1624:421061] No errors uploading 'VoiceInACan.zip'.
RequestUUID = 46ecfa36-4338-48de-b9ac-087d14826fad
$

This doesn’t give a response immediately. It uploads the file for processing. Note the RequestUUID that is output by the tool (in this case 46ecfa36-4338-48de-b9ac-087d14826fad). You’ll need this below to get the status of the notarization.

Always an edge-case

There is a 99.9% chance that you won’t get the error I describe here, but I did. If your Apple ID is associated with more than one developer account you will get the error Your Apple ID account is attached to other iTunes providers. You will need to specify which provider you intend to submit content to by using the -itc_provider command.

To find the ids associated with your accounts use the iTMSTransporter command (again use your Apple ID):

$ /Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/itms/bin/iTMSTransporter -m provider -u damian@mehers.com -p appspecificpassword 
...
[2019-07-30 08:50:23 CEST] <main> DBG-X:   parameter Atadore SARL = AtadoreSARL

Provider listing:
   - Long Name -  - Short Name -
1  Atadore SARL   AtadoreSARL
...
$

After much grinding and processing this will eventually spit out a Provider listing with a long name and a Short Name. Take a note of the Short Name for the account you wish to use, and in every xcrun command append -itc_provider "The Short Name"

For example in my case the above command became:

$ xcrun altool --notarize-app --primary-bundle-id "com.atadore.VoiceInACanForMac.Zip" --username damian@mehers.com --password "appspecificpassword" --file VoiceInACan.zip -itc_provider "AtadoreSARL"
$

Getting the status

Once you’ve uploaded the app for notarization you will have to wait for it to be processed. Continually checking the status will definitely speed things up. To do so, use this command:

$ xcrun altool --notarization-info edab1ebc-29be-45f1-8bca-98b89066196f --username "damian@mehers.com" --password "appspecificpassword"  -itc_provider "AtadoreSARL" 
2019-07-30 09:05:53.314 altool[1799:441307] No errors getting notarization info.

   RequestUUID: edab1ebc-29be-45f1-8bca-98b89066196f
          Date: 2019-07-30 07:05:28 +0000
        Status: in progress
    LogFileURL: (null)
$ 

The GUID (edab1…) that is passed as a parameter is the one returned when you uploaded the zip file above.

After a while of repeating this, you will eventually get the processing response. And it will fail. It always fails the first time.

$ xcrun altool --notarization-info edab1ebc-29be-45f1-8bca-98b89066196f --username "damian@mehers.com" --password "appspecificpassword"  -itc_provider "AtadoreSARL" 
2019-07-30 09:07:14.468 altool[1803:445374] No errors getting notarization info.

   RequestUUID: edab1ebc-29be-45f1-8bca-98b89066196f
          Date: 2019-07-30 07:05:28 +0000
        Status: invalid
    LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/b2/f3/ed/b...d/developer_log.json?accessKey=1...D
   Status Code: 2
Status Message: Package Invalid
$ 

To get the details, use the curl command to show the contents of the LogFileURL:

$ curl https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/b2/f3/ed/b...d/developer_log.json?accessKey=1...D
{
  "logFormatVersion": 1,
  "jobId": "e...f",
  "status": "Invalid",
  "statusSummary": "Archive contains critical validation errors",
  "statusCode": 4000,
  "archiveFilename": "VoiceInACan.zip",
  "uploadDate": "2019-07-30T07:05:28Z",
  "sha256": "c...3",
  "ticketContents": null,
  "issues": [
    {
      "severity": "error",
      "code": null,
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MacOS/VoiceInACan",
      "message": "The binary is not signed with a valid Developer ID certificate.",
      "docUrl": null,
      "architecture": "x86_64"
    },
    {
      "severity": "error",
      "code": null,
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MonoBundle/libMonoPosixHelper.dylib",
      "message": "The binary is not signed with a valid Developer ID certificate.",
      "docUrl": null,
      "architecture": "x86_64"
    },
    {
      "severity": "error",
      "code": null,
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MonoBundle/libmono-native.dylib",
      "message": "The binary is not signed with a valid Developer ID certificate.",
      "docUrl": null,
      "architecture": "x86_64"
    }
  ]
}
$ 

I deliberately signed the app with the wrong provisioning profile to get this error. Remember you must use the profile that incorporates the Developer ID Application certificate.

To add insult to injury I also received a taunting email Your Mac software was not notarized..

You may have other errors. Perhaps you didn’t enable the hardened runtime, or allow JIT, or you use need to enable an entitlement.

This is the Live, Die, Repeat section where you keep on making changes, building, uploading, waiting for it to be processed, reading the reason why it failed, and trying again. For me it was just the certificate.

Once I used Developer ID Application certificate and generated a profile, and crucially used Xcode to Download Manual Profiles I was good:

$ xcrun altool --notarization-info 5f56223e-3d13-4e47-9d01-cc572ba1ca04 --username "damian@mehers.com" --password "fappspecificpassword" 
2019-07-30 09:24:15.210 altool[1953:476339] No errors getting notarization info.

   RequestUUID: 5f56223e-3d13-4e47-9d01-cc572ba1ca04
          Date: 2019-07-30 07:21:39 +0000
        Status: success
    LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/bd/7c/98/b...D
   Status Code: 0
Status Message: Package Approved
$ 

When it finally worked, I couldn’t believe it. I felt like someone who’d won the lottery and could not believe their luck, obsessively comparing the numbers on their ticket with the ones on the screen. But sure enough I received the celebratory email too:

I used the curl to check the log for warnings but all was good. My situation was actually trickier than I let on above because my app contains another app embedded inside it to allow my app be to launched at startup if the user wishes:

$ curl https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/bd/7c/98/b...D
{
  "logFormatVersion": 1,
  "jobId": "5..4",
  "status": "Accepted",
  "statusSummary": "Ready for distribution",
  "statusCode": 0,
  "archiveFilename": "VoiceInACan.zip",
  "uploadDate": "2019-07-30T07:21:39Z",
  "sha256": "6..f",
  "ticketContents": [
    {
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/Library/LoginItems/AppleMacLoginHelper.app",
      "digestAlgorithm": "SHA-256",
      "cdhash": "5...1",
      "arch": "x86_64"
    },
    {
      "path": "VoiceInACan.zip/VoiceInACan.app",
      "digestAlgorithm": "SHA-256",
      "cdhash": "d...b",
      "arch": "x86_64"
    },
   ...
    {
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MonoBundle/libmono-native.dylib",
      "digestAlgorithm": "SHA-256",
      "cdhash": "f...9",
      "arch": "x86_64"
    }
  ],
  "issues": null
}
$ 

Staple

The final step is to staple the notarization information to the app. You do this using the staple command:

$ xcrun stapler staple VoiceInACan.app
Processing: /Users/damian/Projects/AppleWatchAlexa/VoiceInACan.AppleMac/bin/Release/VoiceInACan.app
Processing: /Users/damian/Projects/AppleWatchAlexa/VoiceInACan.AppleMac/bin/Release/VoiceInACan.app
The staple and validate action worked!
$ 

You can now make your app available for download, and it should run on anyone’s Mac without them having to do anything special to their security settings.

Conclusions

Whilst the arrival of Catalyst is causing many questions, including the value of creating Xamarin Mac apps as opposed to simply bringing over an iPad app, there are some situations where native Mac apps make sense.

My app sits in the toolbar. It has a login helper to launch it at login. Two things that are likely not possible with Catalyst.

It is perhaps an edge-case, but in my experience everyone is an edge case, just in different ways. Maintaining the ability to create Xamarin Mac apps gives developers more flexibility and a chance to do something that otherwise would not be possible. Although I would understand if Microsoft dropped Xamarin Mac support, I’m hoping they won’t.

If you’d like to try out the Mac app, it is available here. And guess what? It is notarized!

Not even worth one star: Reacting constructively to App Store reviews

Instant rage

I’ve done it, you’ve done it, we’ve all done it. We’ve reacted with rage to some frustrating failure in an app, and left a negative, one star review. It’s a different experience though, when you are on the receiving end of these kinds of reviews. Trying to not immediately respond to the review with that same flash of anger takes experience, detachment, and maturity.

The user doesn’t understand the intricacies of your app. They don’t know of the hours you spent, not even sure if what you were trying to do was possible. Hundreds of evenings and weekends locked in a battle between what you imagined should be possible to program, and reality. All they know is that the app would not work properly, and guess what? It’s your fault.

I’m sure that some people get a feeling of release, letting go of the anger by leaving a one star review… it will hurt the developer, and makes the person leaving the review feel a little less upset. And you know what? They are right, as an app developer, when you receive a one-star review it does hurt, it does make you feel bad, and if you’re not careful, you’ll respond to the review in kind.

It’s not about the money

It is tempting to think that people that reacting with vitriol to a $1.99 app, are kind of getting things out of perspective. It’s only two bucks after all: the price of a cup of coffee. If you are on the US minimum wage, US$7.25/hour, two dollars is a lot of money.

But no matter what the app costs, it is not about the money. Nobody likes to feel like they have been cheated, and that is how people feel, rightly or wrongly, when they have an issue with an app, and it doesn’t behave as they expect. Cost is irrelevant. Nobody likes to feel like they have been taken for a fool.

You know what? They might be right

I try to keep an open mind to the possibility that sometimes they are reporting genuine issues. I cringe when I remember that time I unwittingly targeted people with small hands and bad eyesight: A code displayed to login to Amazon wasn’t visible if you had a small watch with the font size set to large. D’oh.

Then there are the people that genuinely need my app. The visually impaired or people with mobility issues. I did appallingly bad job in the first version of the app in supporting people who are visually impaired. It is kind of obvious with hindsight that I should have realised that an Alexa implementation for Watches, phones, tablets and computers might be useful for the visually impaired.

Responding

No matter whether the person has found a bug in the app, or if they simply don’t understand how software works, my approach to app store reviews is to try to be polite, kind, and empathise with the person, especially if it is clear that they are clueless and blaming you unfairly.

I’ve found that the passionately negative person, if reacted to with sympathy and genuine willingness to help can become your most passionate advocate and you can flip a person from hating to really supporting your app if you show them that you are listening, and that you are willing to work with them to try and address their issues.

Don’t respond immediately if you “know” how stupid, and idiotic, and clueless the person leaving the review is. Take a step back, move away from they keyboard, try and understand from the other person’s perspective. Try and understand where they are coming from, and leave a helpful response, assuming they best possible interpretation of their review.

Remember the many positive reviews you’ve received … try to not just focus on the negative. Don’t forget that you are doing this for the fun of it, don’t let them bring you down. Don’t spread the hate … try and understand.