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

What's new in AppKit

元の動画を見る

ハイライト

macOS Sonoma では AppKit に列カスタマイズメニュー、インスペクターパネル、シンボルアニメーション効果、メニューの書き換え、協調的アプリアクティベーション、NSView のデフォルト描画クリッピング無効化などの改善が加わり、Mac アプリ開発がよりシンプルで効率的になりました。

主要内容

コントロール API の進化

以前、NSTableView に列カスタマイズメニューを追加するには、開発者が UI 全体と状態保存ロジックを自分で実装する必要があり、コード量も多くローカライズも必要でした。macOS Sonoma では tableView(_:userCanChangeVisibilityOf:) デリゲートメソッドが導入され、3 行のコードで AppKit がすべてを処理—メニューのローカライズ、列状態の復元を含みます。

NSProgressIndicator がついに Foundation の Progress 型と連携しました。以前は進捗変化を手動で監視し UI を更新していましたが、現在は observedProgress プロパティに直接代入するだけで、バックグラウンドスレッドの進捗変化が自動的にプログレスバーに同期されます。

ボタンの bezel style もモダン化されました。新しい .automatic スタイルはボタンの位置に応じて最適なスタイルを自動選択: ウィンドウ内では push button、ツールバーでは toolbar スタイル。旧スタイル名は外観の説明から意味的用途の説明に変更され、「Recessed」は「Accessory Bar」になり、意図がより明確になりました。

インスペクターパネル: もう自分で組み立てる必要なし

Mac アプリの右側パネル(インスペクター)は一般的なデザインパターンですが、以前は標準実装がなく、各開発者が NSSplitView で組み立てていました。Sonoma では inspectorWithViewController イニシャライザが追加され、inspectorTrackingSeparator.toggleInspector ツールバー項目と組み合わせ、標準的なインスペクターパネルを数分で構築できます。macOS Big Sur へのバックデプロイもサポート。

メニューの書き換え: パフォーマンス向上 + 新機能

AppKit のメニュー実装が根本から書き換えられ、完全に Cocoa ベースになりました。メモリと CPU 使用量が大幅に削減されました。同時に 4 つの新機能が追加されました:

  • Section headers: sectionHeader(title:) で 1 行でグループタイトルを作成
  • Palette menus: メニュー項目を水平配置、カラーピッカーなどに最適
  • Selection behaviors: .selectAny.selectOne で複数選択/単一選択ロジックを制御
  • Badges: メニュー項目に文字列バッジとカウントバッジ(新着メッセージ、リマインダー、更新)をサポート

協調的アプリアクティベーション

以前、activate(ignoringOtherApps:) を呼ぶとアプリが強制切り替えされ、ユーザー入力が中断されました。Sonoma ではリクエストベースのアクティベーションに変更され、システムが現在のコンテキストが切り替えに適しているか判断します。新しい yieldActivation API により、現在アクティブなアプリが自発的にアクティベーション権を対象アプリに渡し、よりスムーズな切り替えが可能になりました。旧 API は非推奨です。

NSView はデフォルトでクリップしない

NSView のデフォルト clipsToBounds = true は多くの描画問題を引き起こしていました: テキストシャドウの切り取り、バッジの不完全表示、複雑なグリフの境界超え。Sonoma ではデフォルト動作をクリップなしに変更し、ヒットテストは引き続き幾何学的境界で計算されます。新しい clipsToBounds プロパティ(10.9 へのバックデプロイ対応)で必要に応じて制御できます。

シンボル効果と HDR

SF Symbols がアニメーション効果に対応: bounce、replace、pulse。addSymbolEffect で 1 行のコードで symbol image にアニメーションを追加できます。NSImageView も HDR コンテンツ表示に対応し、XDR 画面で高ダイナミックレンジを自動表示します。

Swift と SwiftUI の統合

  • NSColorNSShadow などのスレッドセーフクラスが Sendable プロトコルに準拠
  • NSImageNSColorNSSoundTransferable に準拠し、SwiftUI でのドラッグ&ドロップと共有が容易に
  • @ViewLoading@WindowLoading プロパティラッパーでオプショナル型を排除
  • Xcode 15 の Preview マクロが AppKit ビュープレビューに対応
  • SwiftUI の toolbarnavigationTitle などの修飾子が NSWindow で動作

詳細

列カスタマイズメニュー

01:36

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

キーポイント:

  • userCanChangeVisibilityOf は NSTableViewDelegate の新メソッド
  • true を返すとその列をユーザーが非表示にできる
  • AppKit がメニューを自動生成し、ローカライズと非表示状態の保存・復元を処理

Progress と NSProgressIndicator の連携

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()
}

キーポイント:

  • observedProgress は NSProgressIndicator の新プロパティ
  • 代入後、プログレスバーが Progress オブジェクトに自動追従
  • バックグラウンドスレッドに対応、手動でメインスレッドに戻す必要なし

インスペクターパネルの追加

03:48

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

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

キーポイント:

  • inspectorWithViewController で全高さの右側パネルを作成
  • inspectorTrackingSeparatortoggleInspector の前に配置し、ボタンをパネル位置に揃える
  • macOS Big Sur へのバックデプロイをサポート

Toolbar ポップオーバー

04:38

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

キーポイント:

  • show(relativeTo:) で toolbar item をアンカーにできる
  • toolbar item がオーバーフローメニューにある場合、popover は自動的にオーバーフローボタンにアンカー

シンボルアニメーション効果

18:30

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

キーポイント:

  • symbol image にのみ有効
  • .variableColor.iterative はレイヤーごとの色変化効果
  • .repeating でアニメーションをループ再生

@ViewLoading でオプショナル型を排除

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)
    }
}

キーポイント:

  • @ViewLoadingloadViewIfNeeded 後にプロパティが利用可能になることを保証
  • プロパティを Optional として宣言する必要がない
  • アクセス時にアンラップや guard が不要

AppKit ビュープレビュー

25:26

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

キーポイント:

  • Xcode 15 の Preview マクロが NSView と NSViewController に対応
  • コード変更後にプレビューが自動更新
  • 名前付きプレビューでプレビューパネルでの識別が容易

重要ポイント

  1. テーブルに列カスタマイズメニューを追加

    • 何を作るか: Mac アプリのテーブルでユーザーが表示列をカスタマイズ可能に
    • なぜ価値があるか: 3 行のコードでネイティブメニュー、ローカライズ、状態復元を取得、ゼロコストで UX 向上
    • 始め方: tableView(_:userCanChangeVisibilityOf:) デリゲートメソッドを実装
  2. カスタム右側パネルをインスペクターパネルに置き換え

    • 何を作るか: 既存のカスタム右側パネルを標準インスペクターに変更
    • なぜ価値があるか: 全ウィンドウ高さ、標準 toolbar 切り替えボタン、自動レイアウトでコードが少なくなる
    • 始め方: 既存の split view item を NSSplitViewItem(inspectorWithViewController:) に置き換え
  3. 状態変化にシンボルアニメーションを追加

    • 何を作るか: WiFi 接続、Bluetooth ペアリング、バッテリー充電などの状態変化時に symbol アニメーションを再生
    • なぜ価値があるか: 視覚的フィードバックでアラートダイアログを代替、より生き生きとしたインターフェース
    • 始め方: symbol image を作成し、addSymbolEffect(.bounce) または .variableColor を呼び出し
  4. Preview マクロで AppKit UI 開発を加速

    • 何を作るか: カスタム NSView/NSViewController に Xcode Preview を追加
    • なぜ価値があるか: コンパイル&実行なしで UI を確認、レイアウト調整がより高速
    • 始め方: ビューファイルの先頭に #Preview { YourView() } を追加
  5. SwiftUI で AppKit の Transferable 型を再利用

    • 何を作るか: SwiftUI ビューで NSImage、NSColor をドラッグ
    • なぜ価値があるか: AppKit 型が Transferable に準拠し、SwiftUI の Drag and Drop に直接使用可能
    • 始め方: SwiftUI で .draggable(image) を使用、image は NSImage

関連セッション

コメント

GitHub Issues · utterances