WWDC Quick Look 💓 By SwiftGGTeam
What's new in AppKit

What's new in AppKit

Watch original video

Highlight

macOS Sonoma brings column customization menus, inspector panels, symbol animations, menu rewrites, cooperative app activation, and NSView no longer clipping drawn content by default—making Mac app development simpler and more efficient.

Core Content

Control API evolution

Previously, adding a column customization menu to NSTableView required developers to implement the full UI and state persistence logic themselves—a fair amount of code plus localization. macOS Sonoma introduces the tableView(_:userCanChangeVisibilityOf:) delegate method; three lines of code let AppKit handle everything, including menu localization and column state restoration.

NSProgressIndicator finally integrates with Foundation’s Progress type. Previously you manually observed progress changes and updated the UI; now assign directly to the observedProgress property and background thread progress syncs to the progress bar automatically.

Button bezel styles got a modernization pass. The new .automatic style picks the most appropriate style based on context: push button in windows, toolbar style in toolbars. Old style names shifted from describing appearance to describing semantic purpose—“Recessed” became “Accessory Bar” for clearer intent.

Inspector panels: no more DIY

Right-side panels (inspectors) are a common Mac app pattern, but there was no standard implementation—every developer assembled their own with NSSplitView. Sonoma adds the inspectorWithViewController initializer, plus inspectorTrackingSeparator and .toggleInspector toolbar items—a standard inspector panel in minutes. Back deploys to macOS Big Sur.

AppKit’s menu implementation was rewritten from the ground up, fully Cocoa-based. Memory and CPU usage dropped significantly. Four new features arrived:

  • Section headers: create grouped titles with sectionHeader(title:) in one line
  • Palette menus: horizontally arranged menu items, great for color pickers and similar
  • Selection behaviors: .selectAny and .selectOne control multi-select/single-select logic
  • Badges: menu items support string badges and count badges (new messages, reminders, updates)

Cooperative app activation

Previously, calling activate(ignoringOtherApps:) force-switched apps, interrupting user input. Sonoma uses request-based activation—the system decides whether the context is right for switching. The new yieldActivation API lets the currently active app voluntarily hand activation to the target app for a smoother transition. The old API is deprecated.

NSView no longer clips by default

NSView’s default clipsToBounds = true caused many drawing issues: text shadows clipped, badges cut off, complex glyphs extending past bounds. Sonoma changed the default to no clipping while hit testing still uses geometric bounds. Use the new clipsToBounds property (back deploys to 10.9) to control clipping as needed.

Symbol effects and HDR

SF Symbols now support animated effects: bounce, replace, pulse. addSymbolEffect adds animation to a symbol image in one line. NSImageView also supports HDR content display, automatically presenting high dynamic range on XDR screens.

Swift and SwiftUI integration

  • Thread-safe classes like NSColor and NSShadow conform to Sendable
  • NSImage, NSColor, and NSSound conform to Transferable for drag-and-drop and sharing in SwiftUI
  • @ViewLoading and @WindowLoading property wrappers eliminate optionals
  • Xcode 15’s Preview macro supports AppKit view previews
  • SwiftUI’s toolbar, navigationTitle, and other modifiers now work on NSWindow

Detailed Content

Column customization menu

01:36

func tableView(_ tableView: NSTableView, 
               userCanChangeVisibilityOf column: NSTableColumn) -> Bool {
    return column.identifier != "Name"
}

Key points:

  • userCanChangeVisibilityOf is a new NSTableViewDelegate method
  • Return true to let users hide that column
  • AppKit auto-generates the menu, handles localization, and saves/restores hidden state

Progress and NSProgressIndicator integration

01:53

func fetchData() {
    let url = URL(string: "https://developer.apple.com/wwdc23/")!
    let task = URLSession.shared.dataTask(with: .init(url: url))
    progressIndicator.observedProgress = task.progress
    
    task.resume()
}

Key points:

  • observedProgress is a new NSProgressIndicator property
  • After assignment, the progress bar follows the Progress object automatically
  • Works from background threads—no manual main-thread dispatch needed

Adding an inspector panel

03:48

let inspectorItem = NSSplitViewItem(inspectorWithViewController: inspectorViewController)
splitViewController.addSplitViewItem(inspectorItem)

func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
    [.toggleSidebar, .sidebarTrackingSeparator, .flexibleSpace, .addPlant, 
     .inspectorTrackingSeparator, .flexibleSpace, .toggleInspector]
}

Key points:

  • inspectorWithViewController creates a full-height right-side panel
  • Place inspectorTrackingSeparator before toggleInspector to align the button with the panel
  • Back deploys to macOS Big Sur

Toolbar popover

04:38

func toolbarAction(_ toolbarItem: NSToolbarItem) {
    let popover = NSPopover()
    popover.contentViewController = PopoverViewController()
    popover.show(relativeTo: toolbarItem)
}

Key points:

  • show(relativeTo:) anchors to a toolbar item
  • When the toolbar item is in the overflow menu, the popover anchors to the overflow button automatically

Symbol animation effects

18:30

wifiImageView.image = NSImage(systemSymbolName: "wifi", accessibilityDescription: "wifi icon")
wifiImageView.addSymbolEffect(.variableColor.iterative, options: .repeating)

Key points:

  • Only works on symbol images
  • .variableColor.iterative is a layer-by-layer color change effect
  • .repeating loops the animation

@ViewLoading eliminates optionals

24:56

class ViewController: NSViewController {
    @ViewLoading var datePicker: NSDatePicker
    var date = Date() {
        didSet {
            datePicker.dateValue = date
        }
    }

    override func loadView() {
        super.loadView()
        datePicker = NSDatePicker()
        datePicker.dateValue = date
        view.addSubview(datePicker)
    }
}

Key points:

  • @ViewLoading ensures the property is available after loadViewIfNeeded
  • No need to declare the property as Optional
  • No unwrapping or guard needed on access

AppKit view previews

25:26

#Preview("Tree Species") {
    let treeCellView = TreeCellView()
    treeCellView.species = .spruce
    return treeCellView
}

Key points:

  • Xcode 15’s Preview macro supports NSView and NSViewController
  • Preview updates automatically when you change code
  • Named previews help identify them in the preview panel

Core Takeaways

  1. Add column customization to tables

    • What to build: Let Mac app tables support user-customizable visible columns
    • Why it’s worth doing: Three lines of code for native menu, localization, and state restoration—zero-cost UX improvement
    • How to start: Implement tableView(_:userCanChangeVisibilityOf:)
  2. Replace custom right panels with inspector panels

    • What to build: Convert existing custom right panels to standard inspectors
    • Why it’s worth doing: Full window height, standard toolbar toggle, automatic layout—less code
    • How to start: Replace existing split view items with NSSplitViewItem(inspectorWithViewController:)
  3. Add symbol animations for state changes

    • What to build: Play symbol animations on WiFi connect, Bluetooth pair, battery charge, and similar state changes
    • Why it’s worth doing: Visual feedback instead of alert dialogs—a livelier interface
    • How to start: Create a symbol image, call addSymbolEffect(.bounce) or .variableColor
  4. Speed up AppKit UI development with Preview macros

    • What to build: Add Xcode Previews to custom NSView/NSViewController
    • Why it’s worth doing: See UI without compile-and-run; faster layout iteration
    • How to start: Add #Preview { YourView() } at the top of your view file
  5. Reuse AppKit Transferable types in SwiftUI

    • What to build: Drag NSImage, NSColor in SwiftUI views
    • Why it’s worth doing: AppKit types now conform to Transferable for SwiftUI Drag and Drop
    • How to start: Use .draggable(image) in SwiftUI where image is an NSImage

Comments

GitHub Issues · utterances