Welcome to the second part of my Appreciating AppKit post. In the previous post I gave an overview of the many views and controls of AppKit that either don't exist or are not as powerful in UIKit. In this post I want to cover some of the more "behind the scenes" aspects of AppKit, things that help your productivity as a developer and can aid in the architecture of your apps.
One of the big features missing from UIKit is any form of "bindings" functionality. This has led to the rise of tools such as ReactiveCocoa, RxCocoa, and possibly others. However, none have quite the same level of integration with the APIs and tools as Cocoa Bindings in AppKit.
Cocoa Bindings were first introduced back in 2003 with macOS 10.3. You may be familiar with the Key Value Observing APIs built into Foundation on both Mac and iOS, which allow you to observe an object's properties for changes. Bindings are the other side of the equation, allowing you to tie properties of objects together.
One of the most basic examples is binding the "Value" of an NSTextField to a string property of a model object. This can be done entirely in Interface Builder, eliminating a huge amount of boilerplate code. The Binding will handle updating the text field with the model's current state, and updating the model when the user edits the text field.
The other key part of Cocoa Bindings is the abstract class NSController and its 3 subclasses: NSObjectController, NSArrayController, and NSTreeController. These can handle even more of the boilerplate code including adding, removing, validating, filtering, and sorting model objects. They can also manage selection, including handling cases of no selection and multiple selection (even supporting editing multiple objects at once).
You start to see the true power of NSControllers and Cocoa Bindings when you need to populate a table view. By binding an NSTableView to an NSArrayController you can populate the entire table, including sorting columns, editing cells, and live updating in response to model changes, without needing to write a single line of code. And if you use CoreData for your model you can go one step further, as NSControllers can talk directly to your NSManagedObjectContext.
Bindings do have their flaws, such as making problems a bit harder to debug (though this is the case for any technology that reduces the management of state). However, for any functionality focused on simply displaying or editing data, Cocoa Bindings can drastically reduce the work you need to do. Every time I've worked on an iOS app there have been 100s (and in some cases 1000s) of lines of code that I would not have had to write on the Mac due to the availability of Bindings.
Document handling is a core part of many content creation apps. Unfortunately it has been rather lacking for many years on iOS. iOS 5 saw the introduction of UIDocument to help encapsulate aspects of the loading and saving of documents, but there was very little infrastructure around managing documents until the introduction of UIDocumentBrowserViewController in iOS 11.
Meanwhile AppKit has a very robust document model that has been refined and enhanced over decades of development. A large part of this difference is down to the different apps each platform started with. The Mac has been a file and document based system from day 1, whereas iOS started with just "utility" apps. It is only in recent years that iOS has started seeing more traditional document-based apps, largely spurred on by the iPad.
This may be one of the big problem points for Marzipan as there are a lot of mismatches in the document models. AppKit allows functionality that UIKit is not currently set up to handle, such as:
- Untitled Documents: An NSDocument does not need to exist on disk and the user can hold off saving to a location on their file system for a long time. Associated with this is support for the OS persisting these untitled documents as drafts, so they persist across app launches (and even reboots) without the user needing to explicitly save them.
- Relocating open documents: NSDocument provides support for renaming and moving documents that are open. This functionality is provided automatically by Appkit in the title bar of document windows.
- Multiple open documents: One of the key advantages of a window-based system is you can have multiple windows open at once. With AppKit this can mean handling multiple documents at once, each of which can potentially have multiple windows showing that one document. Much of this is handled by the NSDocumentController class, which currently has no direct equivalent in UIKit.
These features may require a whole swathe of new APIs in Marzipan for document-based UIKit apps to be able to not feel out of place on the Mac. Thankfully there are other features that Apple should be able to provide Marzipan apps (mostly) for free. Features such as system open/save panels and the title bar proxy icon (which allows you to drag the document somewhere, or command click to navigate to somewhere in the document's file path).
Interacting with the system
iOS and the Mac have very different philosophies on apps interacting with the system. iOS has a significantly more locked down system. This is great for security, but makes it hard for apps to work with one another, or interact with the rest of the system. On the other hand the Mac has a few unique classes that allow Mac apps a greater degree of freedom. How Apple supports this functionality, and if it will even attempt to, is one of the biggest open questions around Marzipan going forward.
NSWorkspace allows apps to replicate some of the features users find in the Finder and the Desktop. It provides a bunch of functionality for dealing with files in a similar manner to Finder, including:
- Open files & URLs, with the option to open in a specific app
- Duplicating files and moving files to the trash
- Revealing a file location in Finder
- Getting icons for files
- Unmounting and ejecting devices
On top of that it allows you to get information about and interact with other applications, including:
- Launching an application, with the option to set launch arguments, environment variables, etc
- Hiding all other applications
- Getting a list of all running apps or the frontmost app
Finally, it provides a way to get or modify the Desktop picture, open a spotlight search, or delay a log out/shutdown to allow your app to finish work.
We just saw that NSWorkspace allows you to get information about the various apps running on the user's system. These methods return instances of NSRunningApplication. On top of giving access to various properties about an app and its process, it allows your app to interact with other apps, including bringing them to the front, hiding/unhiding them, and even terminating them.
NSTask isn't an AppKit class, instead residing in Foundation. However, it is a Mac-only class and plays a key role in allowing Mac apps to interact with the system, so is worth covering here.
NSTask is sort of a lower level NSWorkspace. Rather than interacting with applications it interacts with processes, meaning you can run pretty much any command you could in Terminal. While Apple's APIs are feature rich, sometimes the functionality you need is best found in a command line tool. And sometimes an app can be built entirely around providing a GUI for a command line tool (such as many version control apps, diffing apps, etc).
One of the benefits of iOS is that, in general, it is more intuitive for users than desktop systems. However, much like "self-documenting" code, this only gets you so far before you need to provide additional information, and this is one area where the Mac excels.
To start with, one of the most prevalent help features on the Mac are tooltips. These are just localised strings you can put on your controls to give the user more context of what the control does. Ironically, these aren't as necessary on the Mac as they would be on iOS given the lower frequency of icon-only controls, but their presence massively improves discoverability of your app in a non-intrusive way.
However, the true difference between the Mac and iOS is the Help menu, and especially the concept of Help Books. Help Books are essentially self-contained web sites, usually included in your app bundle (though they can be partially or wholly internet-based). These are then displayed in the built-in Help Viewer, which can be opened from the Help menu.
These help books are indexed by the help system, allowing users to search for terms, both within the Helper Viewer, and by using the search field in the Help Menu.
It will be interesting to see how Apple integrates these with Marzipan. Based on the initial Marzipan apps released with macOS 10.14, it seems the Help System is already fully functional. However, these initial apps lack any sort of tooltips which is something that urgently needs fixing for the public release of Marzipan.
Finally, I'd like to go through a few of the smaller (but still significant) differences between AppKit and UIKit
NSFormatter and it's various subclasses have grown over the years to provide localisable (and in some cases user customisable) formatting of data for a plethora of types, from numbers to dates to names of people. These classes work pretty much the same across all of Apple's platforms. However, with UIKit you need to interact with these formatters directly in code before setting the result to your controls.
NSControl in AppKit provides a more generic interface for dealing with controls, and part of this is a
formatter property. This moves the job of running a raw data object through a formatter for display to the control itself. This means that the control will always give you the raw data object when you query it. On top of this, it allows you to set and configure the formatters on your control from inside Interface Builder, further reducing the code you need to write.
One of the key design principles behind the Mac is consistency. An icon representing an action should be the same across the whole OS, including 3rd party apps. Similarly, the colours used in your UI should be consistent with the rest of the system. This not only helps the whole OS feel more consistent, but also ensures fewer issues when Apple inevitably changes everything (such as introducing Dark Mode).
iOS has some degree of system images and colours. The colours are mostly limited to a few text colours, and the images are almost all locked away behind other controls (such as UIBarButtonItem). On the other hand AppKit has a full range of system assets.
There are named colours for labels, placeholders, text background, links, control accents, selection, etc. It is important to use these colours where possible in a Mac app, as the user can customise things such as accent & selection colours.
Meanwhile, a huge variety of system images are accessible through the
+imageNamed: method on NSImage. Some of these are available through constants, but some (the more "private" ones) require raw strings to access. Thankfully you can find a full list of icons here (including some that no longer work)
While not strictly AppKit related, AppleScript does represent a key piece of functionality for many Mac users, not only allowing high level control over apps, but also direct manipulation of an app's UI. iOS is starting to see a greater level of automation with the introduction of the Shortcuts app. It is not too far-fetched to imagine this coming to the Mac in the near future, but it remains to be seen how (or even if) it will achieve the same level of control that AppleScript has provided to users over the years.
Over the course of these two posts you've hopefully gained an appreciation for the power and versatility of AppKit, from user customisation to system integration to a rich set of UI controls. We should find out a lot more in the coming months and years about how Marzipan fits into Apple's software landscape, but it should be clear that it needs to spur on a lot of changes and improvements to UIKit in order to allow developers to produce the caliber of apps that the Mac is famous for.
It took Apple far too many years to change the iPad from "just a bigger iPhone" to a device that truly allows content creators to do their daily work on. There is a risk that, if Marzipan is just "UIKit on the Mac", the Mac itself could become "just a bigger iPad". However, I feel that Apple has shown its desire to build something more, something that is capable of delivering high quality Mac apps. It may take a few years for them to fine tune everything and work out what needs porting to UIKit, but I believe the end result will give us a much stronger set of APIs both for the Mac and for all other Apple devices.