Explore the latest advancements in Mac app development with AppKit. We'll show how you can enhance your app's design with new control features and SF Symbols 3, build powerful text experiences using TextKit 2, and harness the latest Swift features in your app.
♪ Bass music playing ♪ ♪ Jeff Nadeau: Hi, and welcome to "What's new in AppKit." I'm Jeff Nadeau. I work on AppKit, and I've got some great updates to share with you. macOS Monterey includes some great new features and enhancements for Mac apps. There's really something for everyone in this release, including updates to our user interface design, enhancements to many of our controls, a big update to SF Symbols, an all-new text engine called TextKit 2, powerful new Swift language features, and automation with Shortcuts, which is now available on the Mac. Let's dive right in, starting with some updates on the design of the macOS user interface. We're thrilled by the response to the system-wide redesign that debuted in macOS Big Sur. So many of our favorite apps have updated to adopt the new design in only a few short months. In macOS Monterey, we've continued to iterate and refine this new design, so you'll notice a few new touches across the system. Some of them are big and noticeable, like popovers that now appear and recede with a brand-new animation, or sliders that now smoothly glide into position when you click them. There are also much more subtle changes, like refined metrics in toolbar controls, and spring-loading support on the Search button, so it's easy to drag in text and tokens. And increased spacing between table sections to improve clarity. We've continued to iterate and enhance the new design down to the smallest details. And that leads me to some control enhancements that you can use to refine your own designs. The first big one is control tinting. We've enabled custom tinting of individual buttons, segmented controls, and sliders. Each of these controls accepts a custom tint color using one of these APIs: bezelColor, selectedSegmentColor, and trackFillColor. We introduced these APIs in macOS Sierra to allow tinting of individual controls in the Touch Bar. And starting in macOS Monterey, they're functional for in-window controls as well. To recap, most controls draw using the accent color, which is configurable in System Preferences. This allows people to theme their Mac to match their favorite color. The Multicolor option allows each app to define its own custom color. For pervasive theming, you can define a custom accent color in your app's asset catalog. The new tinting API provides a way to override the color for one specific control. This is great for controls where you'd like to apply a semantically meaningful color. For example, if your app uses a specific shade of orange to distinguish a preorder from a normal purchase, you can reinforce that design using a tinted button. Another example would be a video conferencing app, where the Start and End Call buttons perform two of the most meaningful actions in the app. Tinting them green and red adds emphasis and it makes their function instantly recognizable. One important note is that tinted buttons always show their tint color in every active state, unlike ordinary push buttons which are white or gray. This puts your tint color front and center. However, take care in your design not to create confusion with the default button, which also uses a colorful appearance. Finally, it's important to convey your controls' purpose using more than just color. You want to make sure that people who have difficultly distinguishing between colors can still easily identify the purpose of a given control, so be sure to provide a clear label or icon. Another important button design change is that push buttons no longer highlight using the accent color on click. This brings them in line with other clickable elements, like segmented control segments, slider knobs, and steppers. This is an important change, because if you're doing any custom drawing that assumes a colorful appearance while highlighted -- say, if you're choosing to draw white content over top -- it might not look correct on macOS Monterey. Instead of checking for the highlight state, you can drive your drawing decisions using the interiorBackgroundStyle property. This property reflects the underlying style of the button bezel, so it'll always return .normal for colorless states, and it'll return .emphasized for those colorful emphasis states, like tinted buttons, default buttons, and buttons that are toggled into an "on" state. Finally, we've updated the Flexible Push style of button -- previously known as Regular Square -- to serve as a variable-height push button. It now supports all of the same configurations as an ordinary push button, which means that it can serve as the default button, and you can tint it. The corner radius and content padding now match those of a standard push button at each control size, so they'll fit in great with your other controls, but their vertically resizable design means that they can accommodate larger icons or text with line breaks. While the vast majority of push buttons should continue to use the standard fixed-height style, this newly expanded style offers some flexibility for those special cases when you need to accommodate taller content. Another very important control update comes in the form of automatic localization for menu keyboard shortcuts. There are a lot of keyboard layouts out there in the world, and that can make localizing keyboard shortcuts fairly complex. Some key combinations are hard to reach on some keyboards, and others can't be typed at all. In addition, some directional keyboard shortcuts really ought to mirror in right-to-left languages. The great news is, in macOS Monterey, AppKit can do it for you. Here's an example of a shortcut that could benefit from localization: Command-backslash. While this works fine on a US English keyboard, it's actually impossible to type on a Japanese keyboard, which doesn't have a backslash key at all. Starting in macOS Monterey, the system remaps the shortcut automatically, providing an equivalent shortcut that's natural to type. Another common case is a keyboard shortcut that has directional meaning. For example, Safari uses Command-square brackets to go backward and forward in history. In a right-to-left language, using the left bracket to go backward isn't very intuitive, so AppKit now automatically swaps it to the right bracket when running in a right-to-left language. This behavior applies to brackets, braces, parentheses, and arrow keys. In some cases, you might want to disable this mirroring behavior. For example, if your menu item has absolute directionality -- like align left, for example -- you'd want to use the left bracket even in a right-to-left language. We've provided an opt-out for cases just like this. You can control this behavior using some new properties on NSMenuItem. allowsAutomaticKey EquivalentMirroring lets you control the mirroring behavior for directional keys like brackets, and allowsAutomaticKey EquivalentLocalization controls all of the localization features, including keyboard mapping and mirroring. If you've already carefully localized your keyboard shortcuts, or if your application has a heavily custom implementation of keyboard shortcut bindings, you might want to disable this feature for your entire app. To do that, you can implement this application delegate method, applicationShouldAutomatically LocalizeKeyEquivalents, and return false. Most apps won't need to use this. The individual menu item APIs are strongly preferred for opting out specific items, and most apps shouldn't need to opt out at all. Next up, some big enhancements to symbol images. We brought SF Symbols to macOS in Big Sur, opening up a massive repertoire of beautiful, typographically balanced symbol images to all Mac apps. In macOS Monterey, we've built a new generation of symbol APIs and tooling that we call SF Symbols 3. It's a huge update that spans every aspect of the symbol image workflow, and it's all available to your AppKit applications. SF Symbols 3 expands the capabilities of the SF Symbols app for previewing, exporting, and importing symbol images. It also comes with an updated format for annotating custom symbols, which allows you to define distinct layers within a symbol image, and we've expanded our APIs in AppKit, UIKit, and SwiftUI to allow to you to individually color each layer of a symbol. In Big Sur, we offered two distinct rendering modes for symbols. First, there was the traditional Template style, which draws using a single tint color or effect over the entire symbol. The second is Multicolor, which draws more like a full-color image, drawing each path element using a color that's defined in the symbol image itself. In SF Symbols 3, we've added two new rendering modes which take advantage of the layer information provided by the new symbol image format. The first, Hierarchical, draws using a single color, while putting emphasis on specific parts of the symbol while deemphasizing others. And the second, Palette, lets you assign any color you'd like to each layer of the symbol. You can access these rendering modes using new API on NSImage.SymbolConfiguration. I'm representing the NSColor parameters as dots to show how they correspond with the rendered symbol. The Hierarchical rendering mode take a single color, which is applied to the symbol layers with decreasing opacity. The Palette mode accepts an array of colors which are applied to the layers verbatim. You can also create a configuration that prefers the Multicolor rendering mode.
We've also added a new API that lets you map from one base symbol into one of its variants. For example, you could map the heart symbol to its filled variant, or an inscribed variant like circle, or even to the variant that has a slash through it. This is really useful for situations where you prefer a particular style of symbol for a particular context. For example, you might have a picker control where you prefer outlines for the unselected states, but a filled style for your selection. Normally, this control would need two different image properties to specify the selected and unselected versions of the image. But with the variants API, it could just accept a single base symbol and derive the filled state automatically. This control could call image(with: .fill) to prefer a filled symbol in its selection state, and it'll automatically choose the right version of the symbol whenever a filled variant is available. There are constants for each type of symbol variant, and you can combine them to request multiple variants together, like circle and fill. This was just a brief introduction to the features of SF Symbols 3. There's a new collection of videos where you can get the full details on everything that's new across the entire symbols workflow. Be sure to add them to your watch list. macOS Monterey also features a huge update to the text system in the form of TextKit 2. TextKit is the text layout and rendering engine for all Apple devices, and it's a great text engine with a proven track record. TextKit is a linear text layout engine, which means that it typesets a block of text from beginning to end. As we've evolved our platforms and technologies, we've found a lot of cases where nonlinear text layout would serve us much better. So we went back to the basics and created a new version of TextKit and that's TextKit 2. We had a few key goals for TextKit 2. As always, we want to provide the very best experience for international text of every script and layout. We also want to make it easier to mix text with other types of content. And finally, TextKit 2 is designed to be super fast, efficiently shaping and rendering text, whether it's a short label or scrolled partway through a massive document. TextKit 2 coexists with TextKit 1, so you can choose which engine to use for a given text view. Now I'm going to let you in on a secret. You're already running TextKit 2 on your Mac because starting in Big Sur, TextEdit uses it for plain text documents, and AppKit text fields use TextKit 2 for most configurations. So you've actually been getting a sneak peek this entire time. One of the key differences in TextKit 2 is that it always uses a nonlinear layout system. That means that it can perform text layout at a more granular level, which allows it to avoid unnecessary work. For example, if we have a large document where only a portion of the text is scrolled to be visible, a linear system still has to lay out all of the previous text in order to show this region. By comparison, a nonlinear system like TextKit 2 can start layout at the nearest paragraph boundary. For large documents, this is a huge speedup. The new layout engine provides a robust set of customization points, making it simple to extend the layout system and add your own behaviors. The nonlinear layout system also lends itself well to mixing nontext elements into your text layout. And it does all of this while improving performance for even the largest documents. To learn more, check out "Meet TextKit 2," which goes into full detail about the design of the new API and how to use it. Next up, I'd like to share some updates about AppKit in Swift starting with concurrency. Swift 5.5 introduces some important language-level features for managing concurrency. The first, async/await, allows for asynchronous method calls that behave a lot like coroutines. The second, actor types, protect mutable state from data races by isolating accesses to a single thread of execution. AppKit works great with both of these new features. For async/await, many asynchronous methods in AppKit -- that is, methods that take a continuation as a completion handler block -- have been transformed in the SDK to offer async variants as well. One simple example is NSColorSampler, which allows the user to pick a color from anywhere onscreen. This call is asynchronous because it waits for the user to pick the color they'd like, and then it runs the completion handler when they're done. With async/await, you can express this as an async function call. It yields its thread of execution while it's waiting for the asynchronous work to complete, and then it picks right back up where it left off. You can even do it from within a guard statement. Now, the continuation -- the work to be done next -- is no longer nested inside a completion handler block. Instead, the code reads naturally in sequence. Actors work to protect state from concurrent access by isolating it to a single thread of execution. If that sounds familiar, it's because most of AppKit's state, like the view hierarchy and responder chain, should also be accessed from a single thread of execution, specifically the main thread. This restriction fits the actor model nicely, so we've introduced the concept of a MainActor, which is a type that has to be accessed from the main thread. In AppKit, we've designated NSResponder including its subclasses of NSView, ViewController, WindowController, and Application, plus NSCell, Alert, Document, and DocumentController as MainActors. Code running in a MainActor can freely call methods on other MainActor types, since you know that you're already on the main thread. However, code that's not running in the MainActor must use the new async/await features to perform that UI work in the context of the MainActor. By enforcing this at the compiler level, Swift can help you avoid a common source of bugs when mixing concurrency with UI code. The Swift concurrency videos go into great depth about this powerful new language feature. You don't want to miss them. Also new in Swift 5.5 is a value-typed version of AttributedString. On top of providing value semantics, it also has type-safe attributes and great Swift ergonomics for enumerating and manipulating ranges of attributes. When you're using AttributedString with AppKit, you'll automatically get access to the attributes that are relevant to our text drawing system; things like foreground color, paragraph style, and so on. You can convert between struct AttributedString and the reference-typed NSAttributedString, so you can interoperate with APIs that use NSAttributedString. The "What's new in Foundation" video goes into more detail on AttributedString's design and how to use it. Our last Swift enhancement has to do with driving updates to your NSViews. It uses Swift's property wrapper feature to dramatically reduce the boilerplate that's common in view properties. Let's go over an example. Here's a custom view that's configurable via a handful of properties. And while this looks fine, the reality is that our view's property declarations are much more likely to contain a lot of didSets and extra side effects, because it's so common that you need to redraw, or layout, or update your constraints when something changes. Now, that's a lot of boilerplate. So we made this scenario better by creating a new Swift property wrapper just for this case. It's called Invalidating. It's nested under NSView, and it allows you to specify one or more aspects of the view to invalidate when the wrapped property changes. All of the didSets and brackets fall away, making it easier to focus on the definition of the property itself.
AppKit provides several built-in invalidation cases: display, layout, constraints, intrinsic content size, and restorable state. Since these invalidations only make sense on views, we've constrained the property wrapper so that it only works on subclasses of NSView. We also require that the value conforms to Equatable. We use this conformance to check for meaningful changes in value before performing a potentially expensive redraw or layout pass. If you'd like to do something custom, you can even define your own invalidation by conforming a type to the NSViewInvalidating protocol. Finally, Shortcuts. In macOS Monterey, we're excited to bring the full power of Shortcuts to the Mac, and that includes integrating Shortcuts with your AppKit apps. The great news is, if you app supports Services, then it already supports Shortcuts, and Shortcuts appear in all of the same places where you can access Services today. If you're new to this functionality, it's really easy to adopt. AppKit decides which shortcuts are contextually-appropriate by checking the responder chain. It asks each responder whether it can provide or receive the types of data expected by each shortcut. We express this using a pasteboard type, which in most cases, corresponds to a uniform type identifier. All you have to do is implement the validRequestor for sendType and returnType method. And if you can accept or provide the specified types, return an object conforming to the NSServicesMenuRequestor protocol. In most cases, you'll just implement that protocol directly and then return self. Once a Shortcut is invoked, you'll get calls to write and/or read data from a special pasteboard that the Shortcut uses for its input and output. And that's it! It's that easy to integrate your app with Shortcuts. We've also brought Siri Intents to macOS. You can now handle Intents by creating an Intents Extension in Xcode. Or, if you need to handle them from your main application, you can return an Intent handler from your application delegate. In your app delegate, implement the application handlerFor intent: method, returning a handler object for any intent that you can handle. Every type of intent has its own handler protocol that the returned object must conform to, so check the Intents framework documentation for more details on implementing a handler object. Now that you're equipped with all of this new information, what's next? First, think about how features like control tinting and SF Symbols 3 can help you enhance the design of your app. Next, if your app uses TextKit to create a custom text experience, watch the "Meet TextKit 2" video and try out the new API. You're going to love how easy it is to express custom layouts and rich content with TextKit 2. The that way your app manages concurrency is a key part of its architecture, and Swift's new concurrency features will be a huge improvement in this area. Now is the time to start planning for how features like async/await will shape the way your app manages its concurrent workloads. Finally, get your app ready for Shortcuts by adopting AppKit's automation features. Thanks for your time and attention. We hope you enjoy all of these great enhancements in macOS Monterey. ♪
Use to adjust your drawing for the underlying state of the bezel
Returns .normal for colorless states
Returns .emphasized for colorful/emphasis states
*/var interiorBackgroundStyle: NSBackgroundStyle