Background
I wrote Amber a few years ago to solve a very simple problem: Secure my multifactor authentication tokens without the need for a 3rd-party cloud hosted service. This is what set it apart from apps like Authy, as 1Password at the time had not yet implemented the TOTP feature for mobile. I quickly threw it together using React Native, with the react-native-keychain package. I build the components from
Not too long afterwards, I had a friend ask how they can get the same tokens from the safety of their desktop. I gave it some thought, I threw together a StatusBar application for macOS, which would also read from the keychain and render a WebView in the status bar popup. It was another quick hack to solve a problem I mentally moved on from a year prior, however I saw value in it. Sitting in the coffee and crepe house where I coded it together, I made the decision to not worry about my future-self and made a few terrible design choses when it came to architecting the code. This made the app almost non-maintainable from software architecture perspective. There was to much hodgepodge and duct tape holding the dependencies to the build scripts, which had some CocoaPods thrown into the mix which never sat well with the ever-changing React Native framework.
Fast forward another few years and it’s time to give it some new life since I still find myself using the app on the daily. The problem is my code existed in two different repos, nothing was shared between them, and the code is so out of date resolving dependency issues would take longer than rewriting the app.
Getting Started
Let’s go ahead and get a new project started within Xcode by selected Create a new Xcode project.

Next, we are going to select Multiplatform and App as the Application type.

Next, you’ll need to name your project and choose a location. Once completed, you’ll find yourself on the new project boilerplate.

The next steps I took was to create a new folder within the project called iOS. You may have noticed that Xcode will create a macOS folder by default, so to keep the code clean, I did the same for iOS.
Note: This is my first attempt at a purebred SwiftUI app. I highly recommend the SwiftUI tutorials on developer.apple.com for anyone interested. You may notice that some of the examples below use samples from these exercises.
Determining What Gets Compiled
Using preprocessor macros, we can isolate which OS which specific code blocks are in scope for the compiler. This allows Swift to automatically handle the code needed to compile based on the conditional macro we wrap it with. I found this approach to be very similar to the Go build flags and filename suffixes.
Below, you’ll notice that for macOS, we will include Cocoa since status bar apps still rely on it as far as I am aware. This framework is not available on iOS, so wrapping it in the macOS check prevents the framework from failing with “not found” during compilation.
Next, we need to configure the app delegate, yet again only for macOS. Also in the App struct, our SwiftUI must conform to the Scene interface, which is why we pass the Settings { EmptyView() }
, otherwise if it is iOS, we pass it the actual iOSContentView()
.
Lastly, we wrap the AppDelegate function in another check for macOS.
Next, I created a ContentView.swift in the macOS folder. I named this struct macOSContentView specifically to prevent duplicate struct definitions since folders in Xcode are not unique namespaces.
Lastly, I created the ContentView for iOS within its respective folder.
Compiling
When you compile, select the operating system and device you would like to build for by changing the build target. Now when Xcode compiles, it will use the build target, pass it to the compiler and scope in the appropriate preprocessor macros.

Conclusion
This approach will allow you to reuse all of your components you wish across iOS and macOS, which allows you to create two unique entry points for each application. I’m excited to finish this up, learn more about SwiftUI and finally have a first class citizen app that’s not hard to maintain.
Member discussion