Discover the latest advances in Mac app development. We'll share improvements to controls and menus and explore the tools that can help you break free from your (view) bounds. Learn how to add motion to your user interface, take advantage of improvements to text input, and integrate your existing code with Swift and SwiftUI.
♪ ♪ Aasim: Hello, I'm Aasim Kandrikar, and later, I'll be joined by Raleigh Ledet. We are both Engineers on the AppKit team. In this video, we are going to talk about "What's New in AppKit" in macOS Sonoma.
We'll be covering a broad range of topics, starting with new features and API enhancements to AppKit Controls, improvements to menus powered by a brand-new implementation, changes to how app activation works on macOS, graphics refinements and new features for images and symbols, a new text input experience and improvements to text layout in non-English languages, and improvements to working in Swift and SwiftUI.
macOS Sonoma includes some exciting new features and API refinements to AppKit controls. NSTableView and NSOutlineView provide a lot of functionality out of the box, making it a critical element in designing great Mac apps using AppKit.
In macOS Sonoma, there is new API to present column customization menus. The menu allows users to toggle visibility of columns in your table. Previously, this would have required a custom implementation to create and present this menu, but now you can add this in as little as three lines of code. Adopt the new delegate method tableView userCanChangeVisibilityOf. Specify which columns the user can hide, and AppKit will handle the rest, including localizing the menu and restoring hidden column state on relaunch.
The Progress type from Foundation represents work your application performs. You may be using this in your apps to represent a download in progress or images that are being processed. In macOS Sonoma, you can now use the Progress type from Foundation with NSProgressIndicator. Assign the progress to the new observedProgress property and progress indicator will automatically update its value as the progress changes, even on background threads.
The Button bezel style API has been updated, starting with a brand-new bezel style, automatic. This bezel style will adapt to the most appropriate style depending on the contents of the button and where it is in the view hierarchy. For example, when the button is in a window, it will pick the push button style. When the button is placed in a toolbar, it will pick the toolbar style. For tall content, the button will pick the flexible push button style. The automatic bezel style is now the default bezel style for all button initializers.
The existing bezel style names have been updated from descriptions on their appearance to modern names based on their semantic usage. For example, the formerly named "Recessed" button is now "Accessory Bar" button, indicating that this bezel style is most commonly used in Accessory Bars. Discouraged bezel styles are now deprecated. The deprecations now refer to alternative bezel styles with clear semantic usage. We've introduced a brand-new split view type, inspectors. Inspectors are a trailing split view item that displays contextual information about content that is currently selected in a document.
Similar to sidebars, inspectors use the full height of the window when the full size content view mask is set. The new inspectors back deploy to macOS Big Sur. Adding an inspector to your app is straightforward. First, create a new split view item using the new inspectorWithViewController initializer. Then, add the new splitViewItem to your existing split view controller. Next, update your toolbar delegate to include the new toggle inspector toolbar item. You generally want the toggle inspector item to be placed over the inspector on the trailing edge of the window. To do so, add the new inspector tracking separator and a flexible space before your toggle inspector item.
We've brought some improvements to NSPopover. First, we've added support for anchoring popovers from toolbar items. We've also added a way to support full size popover content, so your views fill the entire popover bounds.
I'll start with toolbar anchoring. There is a new method to present your popover relative to a toolbar item.
When the toolbar item is in the overflow menu, the popover gracefully appears anchored to the overflow chevron.
Next, popover content can now extend into the popover chevron. If you had a popover with a colored background header view, it might have looked something like this. Notice that the color in the header background doesn't extend into the popover chevron. To extend popover content into the chevron area, set the new hasFullSizeContent property to true. Use the safe area rect to layout content that shouldn't be obscured inside the popover.
And now I'll pass it over to Raleigh to talk about exciting changes to Menus. Raleigh: Thanks Aasim. Menus have been re-written to fully use Cocoa. This reduces AppKit's carbon footprint by significantly reducing memory and CPU usage. It also enables new features.
Specifically, I'm going to cover section headers, palette menus, new selection behaviors, and badges. These features open up new opportunities and will reduce the amount of code you need to write.
Section headers are a new addition that aid in conveying groups in your menu and can be created with a single line of code. Use the new class function, sectionHeader(title:) to create one, and add it to a menu, like any other menu item. In this example, three menu sections are created, each with a section header and two items.
Palette menus are an exciting new feature allowing you to build menus where the items are laid out in a horizontal series. For example, this simple color picker.
You can turn any menu into a palette menu by setting the menu's presentationStyle to .palette. For each menu item, set its image. For template images, AppKit will automatically add the appropriate selection tint. Alternatively, you can set the offStateImage and the onStateImage. The onStateImage is used to indicate selection.
There are a couple of selection modes you can choose from. .selectAny toggles the state of individual menu items, but will not change the state of the other items in the group. .selectOne will set the selected menu item's state to on while setting the other members of the group's state to off. You can also get or set which items are in the on state via the selectedItems property. Note: selectionMode and selectedItems work by organizing menu items that have the same target/action pair into a logical group. When manually creating palette menu items, give each item the same target/action pair to take advantage of the new selectionMode and selectedItems behavior. And here's a tip: This technique is not limited to palette menus. It also works for menu items that have the same target/action pair in regular menus. NSMenu also provides a convenience function to create common palette menus. The colors array determines the number of palette items and their tint. Titles are used for accessibility, so make sure you add them. The optional template parameter specifies which template image to use for tinting. For example, the flag symbol image used here.
If you don't specify a template, then AppKit will default to filled circles.
There is also an optional closure parameter. This closure is called any time the user toggles a menu item in the palette. Menu is passed to the closure. From it, you can get an array of the menu items in the on state with the selectedItems property.
Menu items can now be badged in a variety of ways. You can use a simple string or just a count.
There are also three specialized count badges: Newitems, alerts, and updates. When you use one of these badges, AppKit will automatically add the appropriate text. Further, AppKit will properly localize the text as well. In this example, Japanese. Notice, though, that you are still responsible for localizing the menu item itself and the generic string badge variant.
That's the completely new menu implementation: improved performance, badges, palettes, and section headers.
In macOS Sonoma, we have introduced Cooperative App Activation. Cooperative app activation reduces unexpected application switches, for example, an app switch while you are in the middle of typing.
There are two parts to cooperative app activation. Activate is now a request, as opposed to a command. The system considers the broader context of what the user is doing to decide if the activate request is appropriate. The new yield API allows an application to influence the context of a future activation request.
Now that activate is a request, the ignoringOtherApps parameter and option are ignored. As such, in macOS Sonoma, the activate(ignoringOtherApps:) function and the activateIgnoringOtherApps option are both deprecated. Replace them with the new activate APIs for NSApplication and NSRunningApplication.
Only the active application can influence the activate context. It does so by yielding to an explicit target application before the target application is activated. Then, when the target application requests activation, the system will use the yield as part of the context when making its decision. If the request is honored, the active app will deactivate, and the target app will activate. Otherwise, the active app remains active. NSWorkspace automatically handles this for you when opening URLs or applications.
In other cases, to manually hand activation to another application, yieldActivation to the target NSRunningApplication or bundle identifier.
The system will use the yield context when the target app requests activation itself or is otherwise activated on its behalf. That's the new cooperative app activation behavior. We've also made some significant changes and added new API to graphics and drawing in macOS Sonoma.
You can now create CGPaths from NSBezierPaths and vice versa. NSBezierPath gains a new init(cgPath:) initializer and a cgPath property. Initing with, setting, or getting the cgPath always results in a copy of the path. Further mutations of the NSBezierPath are not reflected in the original or copy CGPath instances. That is, they are not toll-free bridged. This addition makes using NSBezierPath with CGPath API, such as the path property on a CAShapeLayer, a single line of code.
You can now create a CADisplayLink object on macOS. This is the same CADisplayLink that you may be familiar with from iOS. For those not familiar with CADisplayLink, it is a timer object that allows your app to synchronize its drawing to the refresh rate of the display. A directly initialized display link object is synchronized with the main display, but macOS is not limited to a single display. Therefore, on macOS you can get a display link object directly from an NSView, NSWindow or NSScreen with their new displayLink(target:selector:) functions. The best approach is to get a CADisplayLink object directly from the most specific applicable element, usually a view. This is because, when created from a view or window, the CADisplayLink will automatically track whatever the display or view the window is on as it moves around the desktop, including suspending itself when not on a display.
In this view subclass, when startAnimating is called, with two lines of code, it creates a DisplayLink object to call its stepAnimation function, synchronized to whatever display the view is on, and adds the displayLink to the main runloop for the common modes.
When the animation is complete, it calls invalidate to stop the display link and remove it from all registered runloop modes.
NSColor now offers five new system colors for filling background shapes. The fill colors provide varying level of emphasis for shapes of various sizes. Smaller shapes on the scale of a slider track or a progress bar background use a higher level of emphasis to stand out, such as system fill or secondary system fill. Larger shapes, like group boxes and font backgrounds prefer a more subtle level of emphasis, such as quaternary or quinary system fill. These fill colors are dynamic, so they automatically adapt to different appearances, including Increased Contrast and Dark Mode. If you're building custom UI elements, these new fill colors are a convenient way to fit in with the system design and support accessibility.
NSViews clip their drawing contents to their bounds. That sometimes leads to drawing not displaying the way you want, like the bottom of this Hindi glyph in a FreeForm alert window.
Common places this can occur is with font rendering, shadows, or other sub-view accents, like a badge or a flame on that "hot" item for sale. There are ways to solve this. For example, embed the combined views as siblings in a larger view. However, each technique has its own drawbacks. In this case, combining the enclosing view with a button in a simple horizontal stack doesn't line up the base lines of the text by default. And now you have another problem to solve.
There is a better way. When linked on macOS Sonoma, most NSViews no longer clip to their bounds by default.
Hit testing remains unchanged and is determined by the geometry of the view. Of course, you can override hitTest to change this. Now that a view may draw outside of its bounds, its calculated visibleRect may also extend past its bounds. Review any code that uses visibleRect and adjust accordingly. This also impacts the dirtyRect parameter of the draw function. Specifically, the dirtyRect is not constrained by a view's bounds.
AppKit reserves the right to pass a dirtyRect that is larger than the view's bounds. AppKit also reserves the right to subdivide drawing into as many rectangles as it needs. What this means for you is that you should use the dirtyRect to decide what to draw, not where to draw.
This is an example of unexpected drawing results that may occur. This draw override fills the passed in dirtyRect with a background color, causing the fill to spill outside the view's bounds, covering other UI in the window. The view isn't using the dirtyRect to draw the frame.
Likewise, its background fill should precisely fill what your design requires, no more, no less.
Drawing outside the dirtyRect is always safe. The performance benefits of the dirtyRect occur when used to decide which parts of your data you can avoid drawing in this pass. Perhaps calculating the stroke paths of my name in this fancy style is expensive. If the dirtyRect is just this little corner, it won't intersect with the text frame, so the view can avoid those expensive calculations. Drawing the background and the frame are still required, but, filling the entire bounds and drawing the entire frame in this pass won't affect the other already drawn portions of the view because AppKit clips the drawing to the dirtyRect.
The new NSView .clipsToBounds property is available all the way back to OS X Mavericks 10.9. But note, on older OSes turning off .clipsToBounds may have some rough edges. Test accordingly. Most views will behave just fine with both clipping on and off. Some container views make their own explicit decisions. NSClipView, true to its name, behaves this way. There may be specific instances where you disagree with a view's default clipping behavior and need to selectively change it.
Consider on a case-by-case basis which of your own views require an explicit clipsToBounds value. The right choice is the one that realizes your vision for your app. Now back to Aasim to talk about images. Aasim: Thanks Raleigh. Symbols are an essential part of designing your apps. In macOS Sonoma, symbols gain brand-new functionality, symbol effects. With symbol effects, your symbols can now have effects like bounce, replacement transitions, and pulse animations.
Symbol effects are great way to emphasize an action that has occurred or state change in your apps. Adding a symbol effect is simple. First, set the imageView's image property to a symbol image. Then, when you want the effect, simply call addSymbolEffect to the image view. Note that this will only work if your NSImageView uses a symbol image.
For more info on using symbol effects, check out the "Animate symbols in your app" video. In macOS Ventura, we introduced support for SF Symbols to automatically adapt to the user's current locale. Now in macOS Sonoma, your asset catalog images and symbols get that same ability. Like SF Symbols in macOS Ventura, they follow the system locale by default. Use the image locale method to obtain an image with a fixed locale. Next, I'll talk about High Dynamic Range content, or HDR for short. HDR content can express light levels far beyond standard content. macOS has supported extended dynamic range for several releases, allowing you to take full advantage of displays such as the Liquid Retina XDR in Macbook Pros and the Pro Display XDR. macOS Sonoma makes it easier than ever to display HDR content in your apps with NSImageView gaining support for HDR content. Images containing HDR content will now be displayed in HDR on Extended Dynamic Range capable hardware. To display HDR content in standard dynamic range, use the preferredImageDynamicRange property to override.
For more info on adopting this API, check out the "Support HDR images in your apps" video. Starting in Xcode 15, images and colors in your Asset Catalog are automatically reflected into your code as static properties on NSImage and NSColor. This allows you to use clean dot notation to access the image instead of initializing it with a string. The images are non-optional, so you can also remove any force unwrapping or guard checks. If you modify the Asset Catalog to remove or rename an image, the compiler will catch any mismatch with your code and produce an error when building your apps, which allows you to fix it immediately, instead of catching it later at runtime. macOS Sonoma introduces significant changes to the typing experience and improvements to text layout in non-English languages.
It starts with a brand-new insertion indicator that adapts to the current accent color, and leaves a trailing glow as you dictate text.
Next, there is now a cursor accessory below the insertion indicator that displays key information, like input mode, dictation state, and caps lock state. The accessory tracks the current insertion position and will be pinned to the bottom of the document if the insertion position is outside the visible view.
Apps using standard AppKit text views will get this automatically. If you have a custom text view, we have new API that you can adopt. You can replace your custom text insertion indicator drawing with the NSTextInsertionIndicator view. Add this view as a subview on your custom text view, and you will get the new insertion indicator that is consistent across the OS. Note that you will be in charge of updating the frame of the insertion indicator and whether it is visible. Update the displayMode property to hidden when your text view resigns first responder to hide the indicator. MacOS Sonoma features several improvements to text layout for non-English languages. An important highlight is the changes we've made for wrapping and hyphenation, as some languages require different rules for line breaking, depending on the text context. For example, in traditional typesetting for Korean, body text may have a line break in the middle of a word, but title text will only wrap at word boundaries. Breaking within words in titles can feel jarring in Korean. In this sheet from Maps, the Korean word for “time” is split between two lines. macOS Sonoma will now perform different line breaking depending on the text style font used. In Korean, title and Headline text styles like in this sheet will not wrap at word boundaries. While the body text style may have a line break within words if appropriate. Here is another example. In narrow layouts, some German words can be longer than an entire line width, which can cause individual characters to spill over into the next line. This wrapping isn't ideal. It looks unbalanced, and there's a component of the word-- which is called a morpheme-- that's being split across lines. In macOS Sonoma, if you have a title text field with hyphenation disabled, macOS will automatically hyphenate the text at a morpheme boundary instead of character wrapping. The resulting layout is more balanced and easier to read. This is a great time to adopt text styles in your app. In macOS Sonoma, AppKit has been updated to make it easier to adopt Swift-first features like Swift concurrency and transferable. SwiftUI has also been updated to allow you to use SwiftUI views and modifiers in more places in your AppKit app.
The majority of AppKit classes are restricted to the main thread. Under Swift concurrency, these classes are marked as main actor to generate appropriate compiler errors. However, there are certain classes in AppKit such as NSColor and NSShadow that can be safely accessed outside the main thread. In macOS Sonoma, these classes conform to the Sendable protocol, which indicates that they can be freely transferred across actor boundaries.
Transferable is a Swift protocol to describe how objects can be serialized and deserialized. This powers features like Drag and Drop and Sharing in SwiftUI. In macOS Sonoma NSImage, NSColor, and NSSound conform to the Transferable protocol. This makes it easier for AppKit apps to adopt features like Drag and Drop or Sharing in SwiftUI Views.
In macOS Ventura 13.3, we introduced a new property wrapper for NSViewController, ViewLoading. Use ViewLoading on properties that are initialized in loadView. If these properties were previously optional, you can remove the optionality and any related checks. The view controller will make sure that the property is initialized by calling loadViewIfNeeded. A similar property wrapper, WindowLoading, is also available for properties on NSWindowController. With Xcode 15, you can now use Previews to display your AppKit views and view controllers. Use the new Preview macro, provide a name, and return your view or view controller. The preview will stay up to date as you make changes to your code. Check out the “Build programmatic UI with Xcode Previews” video for more info.
NSHostingView and NSHostingController are a great way to incrementally adopt SwiftUI into your AppKit app. In macOS Sonoma, there are some new features that allow you to adopt SwiftUI in more places. SwiftUI modifiers like toolbar and navigation title now work on NSWindows. If the hostingView is the contentView of the window, SwiftUI will automatically bridge all available scene modifiers to your NSWindow. For more control, there is a new property on NSHostingView and NSHostingController, sceneBridgingOptions. Using this, you can explicitly state which properties should be bridged from your SwiftUI view to the NSWindow.
That's just some of the new features for AppKit in macOS Sonoma. What's next? First, compile your apps using the macOS Sonoma SDK and audit it to ensure that the changes to clipping and activation don't cause any unwanted side effects. Next, adopt the new controls API, like the new full-height inspector and the new table column customization API. Update your app's design to take advantage of symbol effects in macOS Sonoma. And finally, use the new Swift focused AppKit additions, like Transferable and improvements to NSHostingView, to adopt SwiftUI in more places in your app.
Thanks so much for watching. We hope you enjoy all the new features of macOS Sonoma! ♪ ♪