7 min read

iOS Apps on M1 Macs

Why I'm incredibly excited about iOS apps on the Mac, and what I had to do to get FoodNoms ready.
iOS Apps on M1 Macs

M1 Macs are here, and with it a new era of Mac computing. No, I'm not talking about the astonishing performance of the new Apple Silicon chips. I'm talking about how users can now download and run their favorite iPhone and iPad apps on macOS.

This is not Catalyst. It's also not the future™ (aka SwiftUI). This is a practical, albeit compromised, solution for today. A solution to give the Mac App Store a kick in the butt, an adrenaline shot, a splash of cold water. While not universally popular, it's an incredibly bold, smart strategic move on Apple's part.

Mac lovers ask, rightly so, what about the sanctity of macOS? Won't the ecosystem degrade; the bar lowered? Doesn't anyone care about building high quality macOS apps anymore?

My take: I think at this point it's hard to argue that the Mac has not been on a steady decline over the past decade. Electron has taken over as the standard for desktop software teams. Electron is cross-platform, easy to develop for, and good enough™. Even besides Electron, many people have gotten used to running web apps inside their browser tabs. The future of macOS never looked so bleak in my lifetime than it did a year or two ago.

While the Mac App Store doesn't reflect the entire ecosystem of Mac apps, I've always found it to be a bit of a sad place compared to its iPhone/iPad counterpart. The ugly duckling. Even still, I'd occasionally wander in every month or so, and I'd check out what was featured and what apps were in the top charts. I rarely found anything of interest; most of the time the store just felt lifeless.

It's past time that Apple shakes things up. It hasn't been fully clear until recently, but they have realized this too. Their full strategy, a multi-pronged approach, is now clearly evident:

  • Catalyst: the stop gap for motivated UIKit developers
  • iPhone & iPad Apps on M1+ Macs: Act II – the adrenaline shot to help make the Mac a (more) vibrant platform again
  • SwiftUI: the long-term solution – a cross-platform UI framework for building best-in-class apps

SwiftUI still has a long, long, long way to go before it's a suitable, mature solution for developing macOS apps. Catalyst, on the other hand, is not a "click the checkbox and ship" solution some assumed it would be. Actually not because of a technical limitation, but because of user expectations. Users understandably expect developers to optimize the apps to look and feel like a true Mac app. This means menu bars, smaller click targets, Mac toolbars and sidebars, etc. It's not a trivial amount of work. For an indie developer like me, it's too much work.

Enter "iPhone & iPad Apps on the Mac". Now users should have a totally different set of expectations. Since it's opt out (i.e. I intentionally have to go out of my way to prevent FoodNoms from showing up in the Mac App Store), it would be wrong to not release it, right? And users shouldn't expect it to behave and look like a Mac app, because well, it isn't one.

Most importantly, for users that are OK with running the FoodNoms iPad app on their Mac and would get value out of it, now they can, as long as they have an M1 Mac. It's clearly branded and marketed as an iPhone/iPad app, so I don't have anything to apologize about – it's not a Mac app!

I think FoodNoms is a prime example of why Apple's decision to support running iPad apps on the Mac was the right one, strategically. It's a win-win-win. For users, for developers, and for Apple. FoodNoms users get additional value, expectations are set correctly, and the Mac App Store becomes more vibrant. "Done today is better than perfect tomorrow." If Apple hadn't made this move, it could've been years, if ever, that FoodNoms for macOS would've arrived.

What It Took to Get FoodNoms Ready

I decided to opt FoodNoms out of the Mac App Store initially so that I could be the first one to try it out. I never ordered a DTK, so I had no idea how well it'd run. After bad impressions with Catalyst, I was quite skeptical of how well it would work.

I was pleasantly surprised how well it seemed to work out of the box. That said, there were a few bugs and issues that needed to be fixed. As part of this blog post, I figured I'd share what those bugs were. I think it could give a good idea for developers and non-developers of a) how trivial it is to get an iPad app running without known issues and b) how good of a job Apple did with getting all of the iOS frameworks working smoothly on macOS.

Bug #1: Cannot change the app icon on macOS

Easy solution: hide the "App Icon" table view row in the Settings table view controller if the app is running on macOS.

How do you know if you're running in macOS? Well, I present to you the most important piece of code you will need in order to patch macOS specific behaviors:

public func isiOSAppOnMac() -> Bool {
    if #available(iOSApplicationExtension 14.0, *) {
        return ProcessInfo.processInfo.isiOSAppOnMac
    } else {
        return false
    }
}

Bug #2: Widgets not updating until app quits

Due to serious throttling constraints Apple has imposed on updating widgets, FoodNoms limits widget updates as much as possible. One strategy that has been quite effective has been to defer widget updates until the app has been backgrounded. This is done from the UISceneDelegate sceneDidEnterBackground callback.

On macOS, the sceneWillEnterForeground and sceneDidEnterBackground do not get called when the primary window is focused and blurred. They get called on app open and termination instead.

The solution? Hackily listen for AppKit notifications. Yep, a solution I found on Stack Overflow for Catalyst apps works for these sort of apps, too.

if isiOSAppOnMac() {
  NotificationCenter.default.addObserver(
    self,
    selector: #selector(onEnterBackground),
    name: NSNotification.Name("NSApplicationDidResignActiveNotification"),
    object: nil
  )

  NotificationCenter.default.addObserver(
    self,
    selector: #selector(onEnterForeground),
    name: NSNotification.Name("NSApplicationDidBecomeActiveNotification"),
    object: nil
  )
}

Bug #3: Notifications scheduled in the past keep getting delivered

I realized I had a flaw in my notification scheduling logic, where I would sometimes schedule notifications for earlier in the day. I never noticed these notifications ever appearing on iOS, but on macOS they were getting delivered. It's possible this has been a bug in FoodNoms that no one has ever reported and I never noticed, but I suspect it's simply a slight behavioral different between macOS and iOS. In the end, it was an easy solution to simply never schedule notifications in the past.

Bug #4: The FoodNoms logo does not appear in the onboarding UI

There should be a FoodNoms logo at the top in the whitespace there.

This was a weird one. I was initially confused why the icon artwork is appearing but the logo was not. Both are PDF vector assets. Xcode notified me of a runtime SwiftUI warning: SwiftUI: No image named 'foodnoms_logo.pdf' found in asset catalog.

Eventually I learned about this tool to inspect the generated .car file that stores asset information in the built app binary:

xcrun --sdk iphoneos assetutil --info Assets.car

I noticed one thing that was different about the logo PDF – it used a P3 color profile. I've actually run into a few bugs in the past before where P3 color profile assets are "missing", so I was fairly confident at this point that this was the issue.

Thankfully this was the issue, so the solution was simple. I converted it from a vector asset to a bitmap PNG asset (I didn't want to lose that P3 goodness, and it didn't really need to be vector art anyways).

Feedback ID: FB8917517

Bug #5: Safari View Controller opens an empty modal

FoodNoms uses Safari View Controller to present the FAQ page, available from the settings screen.

When clicking this button on macOS, it first opens the URL in Safari.app, but then it also opens this empty modal.

Again, there is an easy workaround using the handy isiOSAppOnMac. If the condition is true, simply open the URL using UIApplication.shared.open instead.

Feedback ID: FB8917528

Bug #6: App reports iOS in analytics data

FoodNoms collects anonymous usage analytics. Privacy is a core pillar of FoodNoms, and as such, I've taken many measures to ensure analytics data is private. FoodNoms does not use a third-party analytics SDK. Users can opt out. Analytics identifiers are anonymized and not associated with user IDs used for other services (e.g. subscription processing, crash reporting, etc.)

You may wonder, how does the analytics data get collected then if there's no SDK? It's simple – I wrote all of the code that sends the data to Amplitude's HTTP API. This allows me total control and confidence over what gets sent and how. As such, the fix here is quite simple, again leveraging the function I shared above:

"platform": isiOSAppOnMac() ? "macOS" : "iOS",
"os_name": isiOSAppOnMac() ? "macOS" : "iOS",

That's it! Only six bugs and the app is ready to ship. Only took me an hour or so to patch all of these.

Is FoodNoms for macOS something I'm particularly proud of? No. But is it something I think my users will use and get value out of? Absolutely! Therefore, I'm excited about shipping the next update to FoodNoms that will be available on the Mac App Store.