In previous life I was a Windows developer, mainly using Delphi. Whenever I needed a tool I would build one, and it would run on my PC, just like any of the apps I was developing for work.

When I want to do the same, in iOS world, I end up in an absurd situation, because my tools only work on my iPhone. So here I am, sitting in front of my big 27 inch iMac screen, staring at a small iPhone screen, feeling silly 🤪.
Something is fundamentally wrong here. Let’s fix it.

So my goal was not only to learn MacOS development, but to reuse code between Apple platforms as much as possible.

It’s not only a matter of a custom tool that I might need, but while developing for iOS, it might be a good idea to have the same code running on MacOS for testing and prototyping. Auto Layout comes to mind, because of the resizable nature of NSView. While on iOS we might test by changing orientations, on MacOS we might squeeze a view into absurd dimensions where Auto Layout cannot possibly satisfy constraints, and see what happens.

My idea was to typealias common UI classes and structs, so I checked if someone else has done it already. And I found a nice repo soffes/X.

I wanted a different naming structure, so I renamed some classes and added some that I will need in the future. I also added a small utility class to instantiate a window on both iOS and MacOS, for the purpose of eliminating the Storyboards

I made a little Swift package AllApples which can be used to make a shared-code multi-target apps.

The example of such usage is available here: AllApplesApps.

Common UI Classes

The UI classes look like this:

#if os(iOS) || os(tvOS)
import UIKit

public typealias AViewController = UIViewController
public typealias ATableView = UITableView
public typealias AWindow = UIWindow
//...
#endif

#if os(OSX)
import Cocoa

public typealias AViewController = NSViewController
public typealias ATableView = NSTableView
public typealias AWindow = NSWindow
// ...
#endif

A common ViewController might look like this:

import AllApples

#if os(iOS) || os(tvOS)
import UIKit
#endif

#if os(OSX)
import Cocoa
#endif

class CommonViewController: AViewController {
  
  // INFO: Need to create the appropriate `View` type for the OS -
  override func loadView() {
    let aView = AView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
    
    // INFO: To work with layers on the MacOS -
    #if os(OSX)
    aView.wantsLayer = true
    #endif
    
    aView.myColor = AColor.red
    self.view = aView
  }
//...

Removing the Storyboard

After deleting the Storyboard form the project, it needs to be removed form the Info.plist file as well.

On the MacOS a main.swift needs to be added

import Cocoa
let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

The window needs to be created in code:

iOS example (Scene.delegate):

import UIKit
import AllApples

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

  var window: UIWindow?

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let aScene = (scene as? UIWindowScene) else { return }
    window = AppSceneDelegate.makeWindow_iOS(theScene: aScene, theVC: CommonViewController())
  }

}

MacOS example (AppDelegate):

import Cocoa
import AllApples

class AppDelegate: NSObject, NSApplicationDelegate {
  
  private var window: NSWindow?
  
  func applicationDidFinishLaunching(_ aNotification: Notification) {
    window = AppSceneDelegate.makeWindow_Mac(theVC: CommonViewController())
  }

}

At the end CommonViewController (above), might be a starting point for sharing a code between platforms.

Next Steps

I’m want to make a few Xcode templates to generate the skeleton code for both MacOS and iOS in the same project, as well as individually.
AllApples Swift package is going to be used for that purpose. I’m also gonna make a SplitViewController templates (for both platforms), with SectionedDataSource protocol that I’m going to publish into another small Swift package.