Catalyst Sample Code Roundup

June 05 2021

I thought it would be a good idea to round up the various pieces of sample code I've created for Mac Catalyst just to get everything in one place. These are by no means step-by-step guides, merely illustrated examples that have come about over the course of developing my own apps and responding to questions from developers.

No license or attribution is required for any of these codebases. Most, if not all of them, require macOS 11. While Catalyst is present and supported in macOS 10.15, it lacks the Mac idiom (Optimized for Mac) which renders some of this optimization for macOS moot.


Preference Windows

"Catalyst preferences window"

CatalystPrefsWindow showcases how to add a SwiftUI-based preferences panel to your Catalyst app, using a custom window scene.


Translucent sidebars in SwiftUI

"Translucent sidebar with SwiftUI in Catalyst"

CatalystSwiftUISidebar demonstrates a way to wrap UISplitViewController in SwiftUI to get a translucent sidebar in your app.


Advanced three-column app shell & search toolbar item

"Advanced three-column app shell"

advancedcatalystexample — I took the core of my new app, Scarlet, and rewrote it as an example app. It shows one method of building a three-column app, and includes some AppKit glue to add a search field in the toolbar.


Building a menu bar app (NSStatusItem) that uses HomeKit

"HomeKit Air Quality Monitor status item"

airqualitymonitor shows a method of adding a menu bar item — an NSStatusItem — from a Catalyst app, in this particular case using HomeKit, a framework only available to Catalyst. It also hides the main app window, and its Dock icon, so that it's only visible from the menu bar.


Basic sidebar/toolbar app

"Basic sidebar & toolbar app"

CatalystSidebarToolbar is a trivial example just showing how to get a macOS 11-style sidebar & toolbar using UIKit.


Mini document app with color & font pickers

"SmallText"

SmallText — although not intended as sample code, it happens to be a mini-app built with Catalyst that could be instructive. It uses the UIKit document model to open files, and demonstrates using a respresented file as a titlebar for your window.


AppleScript/Cocoa Scripting in Catalyst (also with SwiftUI)

"CatalystAppleScript"

CatalystAppleScript will show you everything you need to add AppleScript support to your Catalyst app. Perform commands (with arguments), set variables, read variables.


macOS Services in Catalyst

"CatalystServices"

CatalystServices shows you how to set up a Services provider in your Catalyst app, and relay the results from AppKit back to your main codebase.


Supporting Catalyst's Optimize for Mac with Manual Layout

May 16 2021

"Optimize Interface for Mac checkbox"

A key part of supporting Catalyst's new Mac Idiom (Optimize Interface for Mac) is having a way to ensure that every hardcoded number you use for layout has a value both for iOS, and for macOS, which no longer uses an automatic 0.77 scale and now lets you address every pixel on the display.

There are plenty of strategies to come at this problem, but the one I've settled on (and which I find very easy to use) is an extension to CGFloat called 'UIFloat'.

Now, whenever I'm defining a value that's going to be used on screen, either for layout frames, margins, or text sizes, I make sure to wrap it in a UIFloat() and then just continue to design my layout using the same metrics I'm using for iOS, using UIFloat anywhere that accepts a CGFloat. This can be used in UIKit or SwiftUI code.

let margin = UIFloat(20)

As a result, your Mac Catalyst app won't require you to completely redo your layouts with alternate metrics, and you can continue to design just one interface that works great on both iOS and macOS.

This also makes back-deploying to macOS Catalina a breeze, as you can ship a dual-mode Catalyst app that uses both the original Scaled mode for macOS 10.15 and the Optimized mode for macOS 11 using this little non-obvious Xcode settings trick.

This has become the first piece of code I add to all new projects over the past year, and I find it invaluable. I kinda wish it were built in to the SDK, so that we always define our UIs in terms of UI points and not CGFloats. Hopefully you might find it useful too.


import UIKit

public let supportsMacIdiom = !(UIDevice.current.userInterfaceIdiom == .pad)

@inlinable func UIFloat(_ value: CGFloat) -> CGFloat
{
    #if targetEnvironment(macCatalyst)
    return round((value == 0.5) ? 0.5 : value * (supportsMacIdiom ? 0.77 : 1.0))
    #else
    return value
    #endif
}

Manual UIKit Layout

May 13 2021

Whenever I mention that I don't use Interface Builder, Storyboards or Auto Layout, developers, especially newer developers, ask me how it's even possible to write UIKit apps like that. So many iOS tutorials dump you straight into Interface Builder and constraint-building, and while that does have benefits at the mid-to-high-end, like with localization and right-to-left languages, it's still a fairly sharp learning curve and mental overhead that leaves many beginners not really understanding what is going on under the hood. It's no wonder that SwiftUI seems so refreshing and easy to use, in comparison.

As an alternative, here's a tiny Swift example that uses programmatic, relative layout, the likes of which I use across all of my newer apps (when I'm not using SwiftUI). No magic, no layout constraints. If you're new to iOS development as of SwiftUI and need to use UIKit but really don't want to have to learn Interface Builder, perhaps this is a technique that could make things easier for you. It can be a struggle in SwiftUI to perform some simple layouts, like placing two container views side by side with an identical width & height, so knowing you have straightforward options in UIKit can be useful.


"Programmatic Layout Example"

import UIKit

class PUIMainViewController: UIViewController {

    let mainView = UIView()
    let leftButton = UIButton(type: .system)
    let rightButton = UIButton(type: .system)

    init() {
        super.init(nibName: nil, bundle: nil)

        /*
            Prepare all your views. Subclassing can prevent a lot of repeated code
        */
        view.backgroundColor = .systemBackground

        leftButton.setTitle("Left", for: .normal)
        rightButton.setTitle("Right", for: .normal)
        mainView.backgroundColor = .systemRed

        view.addSubview(mainView)
        view.addSubview(leftButton)
        view.addSubview(rightButton)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Layout Here

    override func viewDidLayoutSubviews() {
        let buttonAreaHeight = CGFloat(60)
        let padding = CGFloat(20)

        /* Can't forget your safe area insets */
        let safeContentRegion = view.bounds.inset(by: view.safeAreaInsets)
        let contentRegion = safeContentRegion.insetBy(dx: padding, dy: padding)

        let mainViewArea = contentRegion.divided(atDistance: buttonAreaHeight, from: .maxYEdge)
        let buttonsArea = mainViewArea.slice.divided(atDistance: mainViewArea.slice.width/2, from: .minXEdge)

        mainView.frame = mainViewArea.remainder
        leftButton.frame = buttonsArea.slice
        rightButton.frame = buttonsArea.remainder
    }
}

Going Further

Choosing to use manual layout vs Auto Layout vs Springs & Struts vs SwiftUI will always involve a performance vs ease-of-development tradeoff, so one size may not fit all — be smart and measure this in your apps to make the right decisions. If you are designing for multiple languages and/or right-to-left layouts, maybe the additional boilerplate will be too much overhead compared to using Auto Layout or a mix of the techniques. Or perhaps some layouts can be just as simple as reversing an array of views you're looping over when calculating frames.

Eschewing Interface Builder might also make your project less accessible to your designers, even if it completely eliminates complex merges conflicts. YMMV.

As with SwiftUI, it makes a lot of sense to encapsulate views — in a parent view or view controller — so you can ensure that each component knows how to resize its contents, and they can be moved or reused freely around your app as its design evolves. Since this layout pass happens as your view resizes, you can add logic here to completely change the arrangement of your views for certain width or height thresholds, or size classes. In Pastel, for example, its primary custom view controller expands to three columns when the window is large enough, but shrinks to just one for iPhone or iPad splitscreen. Ensuring your app is safely resizable puts you in the best position to add support for future screen sizes or platforms with resizable windows like macOS.

Sometimes just knowing an option is available to you is enough to inspire new workflows and ways of thinking, so hopefully this post is useful to someone. If so, let me know!


Broadcasts 2 + Swift

May 10 2021

"Broadcasts 2 for Mac"

It's been a whole year since the release of Broadcasts on iOS & macOS, with a couple dozen significant updates in the in-between adding major new features, including:

In that time, too, I have launched Pastel on iOS and macOS, updated all of my Mac apps for Universal Purchase and Apple Silicon, and shipped a ground-up rewrite of Lights Off. In short, it's been a long year, for many reasons!

In July, feeling confident that Broadcasts v1.x was in a good place, I transitioned all of Broadcasts development towards the next major release, Broadcasts 2, an ambitious rewrite of the app's UI using Swift and SwiftUI.


Swift

"Broadcasts Swift migration stats"

Broadcasts Swift migration stats via GoSwifty


After WWDC 2020, I felt it was the right time to move all my development to Swift, from Objective-C. I originally made that leap of faith in 2014 and very quickly regretted it, as early Swift was an entirely different beast. Reversing course, I spent most of the following year excising Swift from my codebases, anticipating that there would come a point in time in the future when the language had settled down and most of my initial pain points were gone. I think we're there now — so, after a summer of experimentation with Swift 5 & SwiftUI, I determined to go Swift-only for future development across all of my projects. Up to now, I've felt strongly that I could use Objective-C to my competitive advantage — it did let me launch both Broadcasts and Pastel in the same year, neither small apps, and across iPhone, iPad and Mac no-less — but I started to feel like if I waited until 2021 to begin migrating my codebases it might be a little too late. I might never get that window of opportunity again, so I rewrote all of my apps simultaneously in the haze of lockdown madness and Apple-Silicon-processor-transition euphoria.

To ease the pain on this kind of migration, I decided not to touch any of Broadcasts' library or sync code in 2.0, instead focusing on the UI. That still meant a rewrite for a good 75% of the Broadcasts codebase, with little direct user benefit, as I've used the strictest warnings & analysis features for years in my ObjC development which brings it much closer to Swift in terms of reducing bugs, crashes & subtle logic failures, but it has streamlined the project massively and made it much easier to add new features and adopt new frameworks like SwiftUI.


SwiftUI

"AppKit vs SwiftUI editing panel in Broadcasts"

AppKit (top) vs SwiftUI (bottom) editing panel in Broadcasts


In Broadcasts on macOS, I have, up to now, used tailored AppKit panels for its various editing sheets and alerts. With the changes last year to SwiftUI & Catalyst's UIKit, I feel much more confident that I can build a richer UI, retaining the Mac-like elements & behaviors, without having to resort to using AppKit anymore. I must admit, it was rather cathartic removing a bunch of NIBs, classes, headers, and relay controllers and replacing them with a single panel made in SwiftUI.

The primary toolbar in Broadcasts also moves to SwiftUI for layout, which makes the various modes it switches to (for iPhone, iPad, and with window resizing on macOS) so much easier to organize and extend with new features. On iOS, SwiftUI also powers the new Now Playing screen and external display UI, and is sure to feature heavily in the next version of Broadcasts for Apple Watch.

I'm excited to use SwiftUI for layout in future apps — I do think that's where it excels, even if I wouldn't trust it for navigation or core app logic right now. SwiftUI is just another tool in the developer tool belt, and, even though it's very situational, it can be applied skillfully to great effect. Far from competing with UIKit, it is entirely additive to Catalyst development — a useful and fun layer on top and a great accelerator. For certain, SwiftUI means the end of Interface Builder, and I can easily recommend it as an IB replacement. I know there's a lot more integration with AppKit that I'd love to see from Catalyst, and I can't help but feel that SwiftUI will be that glue layer, abstracting developers away from hosting AppKit views & controls completely.


The story continues…

Broadcasts 2 for Mac is available now for macOS 11, and will be coming to iPhone & iPad in time. This being a developer-interest post, I haven't even talked about the new user features in Broadcasts, like the Now Playing screen, Sleep Timer, homescreen Widgets, AirPlay route picker, vastly improved free user tier, lost-connection handling, and non-intrusive errors & alerts. I hope to give some love to the watchOS & tvOS apps too, but have nothing to announce at this time. As always, you can follow along with development over at Twitter.


Pastel

June 10 2020

Pastel for iPad


All of us have a list of projects we'd like to do someday, when the stars align. One of the things I've had in the back of my mind for a while now was 'something to do with colors'.

There's a missing part of iOS that's always bugged me, and that's comprehensive support for colors at a system level. One of my favorite bits of UI since the early days of Mac OS X was the system color panel, originally from NEXTSTEP. It's beautifully comprehensive, with wheels, sliders, palettes, crayons, swatches, and the amazing eyedropper tool, and it's supported throughout Cocoa apps on the Mac. Most Mac apps make use of a color well control somewhere in their UI, and you can drag and drop colors between apps, and even onto rich text in all system editors.

NSColorPanel

Color is a fundamental type of object in desktop macOS, and I love that.

iOS never gained a standard color picker UI; a platform used by artists everywhere, with amazing creative apps and a first-party stylus accessory from Apple, and yet everybody has to reinvent the [color] wheel. Now that UIKit apps have come to the Mac, via Catalyst, it's even more jarring that UIKit has no way to invoke a system color picker.

One thing iOS does support is drag and drop for colors — but you'd never know it. The iWork suite lets you drop colors onto shapes and objects to fill them, Mail and Safari let you drop colors onto rich text to style it, and third-party apps have been free to implement color drag & drop for years, although only a select few have, like MindNode, and Concepts.


"The very first build of Pastel"

From humble beginnings…


Just before Broadcasts development kicked into high gear I started prototyping an app to manage and collect colors, with inter-app drag and drop support, called Pastel. You might have been following along with the development journal I kept on Twitter.

Like most of my projects these days, it started on the Mac as a Catalyst app and spent much of its early development there before being brought to the iPad, and later iPhone. I really love the direction of that workflow, the opposite direction most Catalyst developers would go, as it forces you to care about the complexity of the Mac first before distilling it down into something that fits on a tiny screen, and it allows for a faster development cycle than building for the iOS Simulator or device.

There are really three scenarios in which I'd want a color palette library app for myself:

I built Pastel entirely around the first two scenarios, and have prototypes for future updates around the third. I think it's reflected throughout the UI: you can copy a variety of iOS developer representations, for instance, or a tiny bitmap version of your palette, from the context menus, but I've (for now) intentionally avoided building the kinds of features that professional artists or designers might want or expect.


"Pastel's context menu"


I lovingly recreated a whole bunch of macOS' NSColorPanel on iOS, including my favorite tab, the Crayon picker. This wasn't as much work as it sounds, and it was a pretty fun experience — especially the little things, like determining just how exactly those color sliders work as they update in realtime.


"Pastel's color picker"


Drag & Drop

Key to making Pastel part of advanced workflows on iPad is supporting drag and drop. You can pick up any color from any palette and drop it inside another app, and it's been crazy cool to see other developers pledge support for this in their apps (❤️) now that I've raised awareness about the potential of this capability. Color drag & drop is part of iOS, open to anybody, and it would be great to see it in more places. I'd love for Pastel to be a fun halo app for this kind of scenario, and I'd love it even more if this spurs Apple into providing a systemwide color picker & more-comprehensive color support in future versions of iOS.

To make this even more interesting, in the next update to Pastel (likely available by the time you read this), there's a mode that lets you use Pastel as a splitscreen color picker window from which you can drag into any supporting apps.


Available on the App Store

If any of this sounded interesting to you, Pastel is now available for iPhone and iPad — free to try, with a library cap of 20 items, and a $4.99 in-app-purchase if you like it and want to unlock a larger library. The Mac version of Pastel will be something I pick up sometime after WWDC with the intention of launching later this summer, and it's all Universal Purchase so you won't have to pay anything extra when it does launch.

It's been a blast working on Pastel, and I really hope you like it. Feel free to send pictures of the pretty palettes and libraries you create, and please consider supporting color drag & drop in your own apps!


"Pastel's palette view"

"Pastel's large palette display"

"Pastel's image import panel"


Broadcasts: Streaming Radio

April 01 2020

Way back in 2016 I built myself a mini streaming radio player app to use at home and give to family. It included all our local Irish radio stations, and had the most simplistic vanilla UI — a true minimum viable product.

MobileRadio's initial UI from 2016

It worked! Barely! But it was enough to satisfy what I wanted, so I put 'MobileRadio' on the shelf at the time, forgotten with the piles of other abandoned projects, and never really thought about going back to it.


After the announcement of Catalyst (UIKit on macOS) at WWDC 2019, I figured it would be fun to build a Catalyst app from the ground up for Mac, with no initial intention of bringing it to iPhone or iPad. I trawled my projects folder and came across MobileRadio and figured it would be the perfect example.

I put a lot of love into building this Radio app for Mac, using UIKit, and delved deep into talking to AppKit where necessary to try and build an experience worthy of being on macOS. Using Apple's new Podcasts app as a template, I replicated its UI using all of the latest APIs and technologies from iOS 13, relying heavily on context menus.

HCC Radio on macOS

At this point I had no intention of shipping this radio app, on macOS or otherwise, but I was inspired by all the developers debuting their Catalyst apps with the launch of macOS Catalina and, in my excitement, figured I could make a showcase product of sorts. I named it HCC Radio (High Caffeine Content, that is), included just the pre-set Irish radio stations, and unleashed it upon the world. I figured it was a pretty good example of Catalyst, respectful of the Mac user experience; thus far, HCC Radio has done well for itself, in its limited niche of Irish radio streaming apps!

HCC Radio reviews


One of the more interesting questions I had last year was around AppleScript support in Catalyst (then, Marzipan) apps — it was possible, for sure, but I had no idea if such an app would make it through App Review. I took it upon myself to build AppleScripting into HCC Radio, just to illustrate some macOS, uh, deep cuts in an app built with iOS APIs.

AppleScript in HCC Radio


HCC Radio was a fun side project, but there's something really motivating about actually shipping software, and I was compelled to try bringing the app to iOS. Unlike most Catalyst projects, HCC Radio was built first for Mac, so the next obvious choice was to bring it to iPad — conceptually similar in many ways, iPad would be able to reuse the existing structure of the app, where iPhone would require a rethink of navigation. I wanted to build a kind of iPad app for people like me who want so much more than just a blown-up iPhone app, and I'm really happy with how it's turned out.

If I was going to bring this to iOS, and expand the app with new features, I needed a better product name: and so 'Broadcasts' was born!

Broadcasts on iPad

Broadcasts, unlike HCC Radio, has full support for adding your own stations and collections, syncs over iCloud, and runs on iOS and iPadOS. On the Mac, it retains the AppleScript support it had before, whereas on iOS it supports mouse & trackpad and has a pull-down Command menu with discoverable keyboard shortcuts.

Like HCC Radio, Broadcasts is a free app with the preset Irish Radio stations, but, if you want to unlock editing features it asks for a simple $5 in-app purchase — no subscription or monthly fees required. Being an app with Universal Purchase, if you buy it on iPhone or iPad you get the Mac version for free, and vice versa. This is the first time I've ever shipped an app with in-app purchase, so I'm pretty curious to see how it lands.


If you've been following my development threads on Twitter, you'll be pleased to know that Broadcasts is now live on the App Store for iPhone, iPad, and Mac. 🎉

It's free, go try it out, and I hope you like the iPad UI as much as I do. Check out its mouse & trackpad support if you can, and let me know what kind of features you'd like to see next! 😬


Shortcuts for Mac

June 10 2019

One of the most interesting rumors before WWDC was that Shortcuts might be coming to the Mac, which of course raises all kinds of questions re the future of automation on macOS.

WWDC came and went, and while Shortcuts got some huuge upgrades in iOS 13 (like a new conversational editor, and third-party actions with inputs and outputs), Shortcuts did not come to the Mac.

…or did it?

Turns out, Catalyst on macOS Catalina includes all the Shortcuts frameworks, including all the ones necessary to bring up almost its entire UI. So I built a dummy app that does just that, which you can find on GitHub.

This test harness isn't really useful for anything except to explore the idea of Shortcuts on the Mac, how it would fit in to the system, and who would want such a product. But it does make for a fun demo 😄

Shortcuts is a consumer product, a brand unto its own; I think there are many of us who are building libraries of Shortcuts on iOS who would love a way to bring them to the Mac, and many iOS apps coming to the Mac via Catalyst, that will likely never adopt AppleScript (even though that's indeed possible for Catalyst apps), that could benefit from it.

Let's hope that the Shortcuts team finds the time in their very busy taking-over-the-world schedule to bring Shortcuts to macOS — submit feedback and make sure they know it's important to you, too!


Older posts…