WWDC Quick Look 💓 By SwiftGGTeam
Build an AppKit app with the new design

Build an AppKit app with the new design

元の動画を見る

ハイライト

macOS Tahoe では、AppKit に Liquid Glass マテリアル、フローティング toolbar と sidebar、新しいウィンドウの角丸とコントロールサイズが導入されました。ほとんどの効果は Xcode 26 で再ビルドするだけで有効になり、残りの調整は NSGlassEffectViewNSBackgroundExtensionViewNSView.LayoutRegion などの新しい API で行います。


主要内容

従来の AppKit アプリを macOS Tahoe 上で動かすと、最も多い問題はビジュアルのずれです。Sidebar に NSVisualEffectView が重なっていて、新しいガラスマテリアルが完全に遮られてしまう。toolbar のステータステキストに自動的にガラス背景が適用され、まるで疑似ボタンのように見える。ウィンドウの角丸が大きくなったことで、角に近い「新規フォルダ」ボタンが一部切れてしまう。高密度な Inspector はコントロール全体が高くなったことでレイアウトが崩れる。これらは、Session の冒頭で Apple が提示した移行の現実です。

Apple は変更を外側から内側へ向かう順序で4段階に分けています(00:52)。App 構造(toolbar、sidebar、ウィンドウの角丸)、Scroll Edge Effect(スクロールコンテンツとガラス要素が重なった際のビジュアル)、Controls(新しいサイズ、新しい形状、prominence、ガラス bezel)、Liquid Glass のカスタマイズ(NSGlassEffectView / NSGlassEffectContainerView)。ほとんどの効果は Xcode 26 で再ビルドするだけで直接有効になります。toolbar は自動的にガラス上にグループ化され、sidebar は自動的にフローティングし、コントロールは自動的に新しい外観に変わります。手動で対応が必要な箇所は、それぞれ具体的な API に対応しており、「View 全体を書き直す」という解決策はありません。


詳細

Toolbar のガラスは NSToolbar が自動的に適用します02:53)。同種のボタンは1つのガラス上にまとめられ、segmented、pop-up、search など異なるタイプは別々に分けられます。問題は、ステータステキストやカスタムタイトルといった非インタラクティブなコンテンツにもガラスが適用され、ボタンのように見えてしまうことです。解決策は bordered をオフにすることです。

// Removing toolbar item glass

toolbarItem.isBordered = false

キーポイント

  • isBordered = falseNSToolbarItem のガラス背景を非表示にします。Photos の toolbar にあるようなステータステキストに適しています(03:11)。
  • 逆に、ステータスや重要な操作を強調したい場合は、toolbar item に style = .prominent を設定すると、ガラスが accent color で染色されます。backgroundTintColor で色を指定することもできます(03:30)。
  • 「未読がある」や「新着通知がある」を表現したい場合は、NSItemBadge.count(_:)NSItemBadge.text(_:)NSItemBadge.indicator を使用します(03:58)。

Sidebar は新しいデザインでコンテンツの上に浮かびます04:12)。横方向にスクロールするコンテンツや地図、ポスターなどのリッチコンテンツを sidebar の下まで伸ばしたい場合は、以下のスイッチをオンにします。

// Content under the sidebar

splitViewItem.automaticallyAdjustsSafeAreaInsets = true

キーポイント

  • このプロパティは sidebar に隠されるコンテンツ側に設定し、sidebar 自身には設定しないでください(05:25)。
  • NSSplitView は frame を sidebar の下まで伸ばし、safe area layout guide を使ってコンテンツが隠されていない領域にのみレイアウトされます。
  • そもそも「余白」を持たないアートワークの場合は、NSBackgroundExtensionView を使用します。これを split item 全体に敷き、contentView を設定すると、AppKit が自動的に safe area 外の領域をミラーリングしてぼかし、延伸させます(06:24)。
  • sidebar 内の古い NSVisualEffectView は忘れずに削除してください。そうしないと新しいガラスマテリアルが遮られてしまいます(04:46)。

ウィンドウの角丸が大きくなり、ボタンが角にぶつかりやすくなります07:07)。toolbar 付きウィンドウの角丸はより大きくなり、ガラス toolbar と同心になります。角に近いコントロールには新しい layout region API が必要です。

// Avoiding a window corner


func updateConstraints() {
    guard !installedButtonConstraints else { return }

    let safeArea = layoutGuide(for: .safeArea(cornerAdaptation: .horizontal))

    NSLayoutConstraint.activate([
        safeArea.leadingAnchor.constraint(equalTo: button.leadingAnchor),
        safeArea.trailingAnchor.constraint(greaterThanOrEqualTo: button.trailingAnchor),
        safeArea.bottomAnchor.constraint(equalTo: button.bottomAnchor)
    ])
    installedButtonConstraints = true
}

キーポイント

  • layoutGuide(for: .safeArea(cornerAdaptation: .horizontal)) は、通常の safe area より水平方向に一段内側に縮んだ guide を返し、角丸を避けるために使用します(08:47)。
  • cornerAdaptation.horizontal または .vertical を選択でき、どの方向で角丸を避けるかを示します。
  • layout region から直接 edge insets や現在の rect を取得し、Auto Layout を使わない場面でも利用できます(08:17)。

Scroll Edge Effect は明示的に有効にする必要はありません09:27)。NSScrollView は上側のフローティング要素に応じて自動的に効果のサイズを調整します。soft-edge はグラデーションぼかし、hard-edge はより不透明になります。カスタムフローティングコントロールをこの効果に接続するには、NSSplitViewItemAccessoryViewController を使って top/bottom aligned accessory を追加します。これは scroll edge effect の形状とコンテンツの safe area の両方に影響します。

Controls に extra large サイズが追加されました11:54)。mini から extra large まで5段階あり、mini/small/medium は以前より少し高くなっています。そのため、コントロールの高さをハードコードしないで、Auto Layout を使用してください。高密度な場面(Inspector、Popover)では、NSViewprefersCompactControlSizeMetrics = true を設定すると、ビュー階層に沿って継承され、コントロールは以前の高さに戻ります(12:56)。

コントロールの形状は、mini 〜 medium が角丸矩形、large/extra large がカプセル形です。同心にしたい場合は、borderShape でボタン、pop-up、segmented control の形状を上書きできます(13:52)。ガラスボタンが必要な場合は、新しい glass bezel style を使用し、bezelColor で染色します。

Tint prominence は tint の濃さを制御します15:31)。

// Create buttons with varying levels of prominence

// Prefer a "secondary" tinted appearance for the shuffle and enqueue buttons
shuffleButton.tintProminence = .secondary
playNextButton.tintProminence = .secondary

// The "play" will automatically use primary prominence because it is the default button
playButton.keyEquivalent = "\r"

キーポイント

  • tintProminence には .automatic / .none / .secondary / .primary の4段階があります。
  • shuffle や enqueue のようなサブアクションには .secondary を使い、tint を弱めてメインボタンと差別化します。
  • play ボタンに keyEquivalent = "\r" を設定してデフォルトボタンにすると、自動的に primary prominence が得られます。
  • slider にも同様に有効で、tintProminence = .none にすると track が塗りつぶされません。新しい neutralValue を使えば、塗りつぶしのアンカーをトラックの任意の位置に置けます。例えば、再生速度の 1x などです(16:20)。

Liquid Glass の適用は2段階です18:13)。まず、ガラスにしたい要素を NSGlassEffectView で包みます。

// Adopting NSGlassEffectView

let userInfoView = UserInfoView()
let activityPickerView = ActivityPickerView()

let userInfoGlass = NSGlassEffectView()
userInfoGlass.contentView = userInfoView

let activityPickerGlass = NSGlassEffectView()
activityPickerGlass.contentView = activityPickerView

let stack = NSStackView(views: [userInfoGlass, 
                                activityPickerGlass])
stack.orientation = .horizontal

キーポイント

  • 「ガラスにする」ビューを contentView に設定すると、AppKit が自動的に Auto Layout でガラスのジオメトリをコンテンツにバインドし、可読性を保証する視覚処理を施します(18:42)。
  • NSGlassEffectView をコンテンツの後ろに兄弟ノードとして配置しないでください。必ず外側に包む必要があります。
  • cornerRadius と tint color は NSGlassEffectView 上で調整できます。

隣接するガラス要素は NSGlassEffectContainerView でグループ化する必要があります。

// Adopting NSGlassEffectContainerView

let userInfoView = UserInfoView()
let activityPickerView = ActivityPickerView()

let userInfoGlass = NSGlassEffectView()
userInfoGlass.contentView = userInfoView
userInfoGlass.cornerRadius = 999

let activityPickerGlass = NSGlassEffectView()
activityPickerGlass.contentView = activityPickerView
activityPickerGlass.cornerRadius = 999

let stack = NSStackView(views: [userInfoGlass, 
                                activityPickerGlass])
stack.orientation = .horizontal

let glassContainer = NSGlassEffectContainerView()
glassContainer.contentView = stack

キーポイント

  • NSGlassEffectContainerView は複数のガラスを1回のレンダリングにまとめ、近づくとガラスが液状に融合します(19:35)。
  • spacing プロパティで融合距離の閾値を制御します。
  • ガラスマテリアルは周囲から色をサンプリングして屈折を生成しますが、ガラスは別のガラスをサンプリングできません。グループ化しないと色の不整合が生じます。グループ化することで複数のガラスが同じサンプリングを共有します(20:23)。
  • コンテナと contentView も同様に Auto Layout でバインドされ、既存のレイアウトノードをそのまま置き換えられます(21:03)。

重要ポイント

1. Xcode 26 で一度再ビルドし、デフォルトの動作を確認してから変更箇所を決める

なぜ価値があるか:toolbar が自動的にガラス上にグループ化され、sidebar が自動的にフローティングし、コントロールが自動的に新しい外観に変わるため、ほとんどのビジュアル更新は無料です。まずどこが崩れるか確認してから対処的に修正する方が、あらかじめ各インターフェースを書き直すよりはるかにコストが低いです。

どう始めるか:Xcode 26 に切り替えて現在のプロジェクトをコンパイルし、起動後に各ウィンドウをスクリーンショットで撮り、元のスクリーンショットと照らして「自動で変わったが変わり方がおかしい」箇所をマークします。

2. ハードコードされたコントロールの高さを監査し、すべて Auto Layout に変更する

なぜ価値があるか:mini/small/medium のコントロールがすべて高くなり、新しい extra large も加わったため、ハードコードされた heightAnchor.constraint(equalToConstant:) は今回の適配で問題が最も密集する箇所です。Inspector、popover、メニュー項目のレイアウトはすべて影響を受けます。

どう始めるか:プロジェクト内で heightAnchor.constraint(equalToConstantframe.size.height =bounds.height = などの書き方を grep し、コントロール関連のものをすべて差し替えます。高密度を維持しなければならない古いインターフェースには、親ビューに prefersCompactControlSizeMetrics = true を設定します。

3. メニュー項目に symbol icon を追加し、sidebar 内の NSVisualEffectView を整理する

なぜ価値があるか:新しいデザインはメニュー全体に symbol を使用することを推奨しており、スキャン速度が直接的に向上します。sidebar に残っている visual effect view は新しいガラスマテリアルを遮り、「Tahoe アプリらしく見えない」最も一般的な原因です。

どう始めるか:すべての menu item を列挙し、「対応する SF Symbol があるか」でチェックして補完します。view debugger で NSVisualEffectView を検索し、sidebar 内のものがすべて削除されていることを確認します。

4. Liquid Glass は最上位レベルのコントロールにのみ使用し、Container でグループ化する

なぜ価値があるか:ガラスは「最上位に浮かぶコントロール / ナビゲーション」という階層を示すために使用され、Apple は使用範囲を限定することを明確に推奨しています。広範囲に使用したり、孤立して配置したりすると、サンプリングの競合により色の不整合が生じ、パフォーマンスも低下します。

どう始めるか:現在の UI 内の「コンテンツの上に浮かぶツールバー / 編集コントロール」を見つけ、NSGlassEffectView でラップします。隣に2つ目のガラスがある場合は、外側に NSGlassEffectContainerView を被せ、spacing を適切な融合距離に調整します。

5. リッチコンテンツを sidebar の下まで伸ばす場合は NSBackgroundExtensionView で補完する

なぜ価値があるか:ポスターや地図などの作品は sidebar のための余白を持っておらず、無理に伸ばすと重要な情報が切れてしまいます。Background Extension View は自動的に延伸領域をミラーリングしてぼかし、safe area 内の実際のコンテンツは変更しません。

どう始めるか:まず splitViewItem.automaticallyAdjustsSafeAreaInsets = true をオンにします。ネガティブスペースを持たない素材には、NSBackgroundExtensionView で1層包み、contentView には実際に表示したい画像のみを配置し、延伸領域はシステムに任せます。


関連セッション

コメント

GitHub Issues · utterances