ハイライト
macOS Sequoia は AppKit の SwiftUI 統合をさらに進化:NSHostingMenu でメニュー定義を共有、NSView が SwiftUI Animation 型で中断可能なアニメーションをサポート。Text Entry Suggestions、システムカーソル、Save Panel のファイル形式ピッカーなど標準コンポーネントも追加。
主要内容
Mac 上の app は長年、矛盾に直面してきました:AppKit はシステムレベルの完全な制御を、SwiftUI は宣言型開発の効率を提供する一方、両者の境界はスムーズではありませんでした。メニューは NSMenu を手書き、动画はレガシー NSAnimationContext API、入力候補は app ごとに独自実装——これらの隙間が開発速度を落とし、保守コストを増やしていました。
Sequoia はこれらを一つずつ埋めます。NSHostingMenu で SwiftUI View からメニュー内容を定義し、AppKit と SwiftUI で同じメニュー定義を共有。NSAnimationContext.animate(with:) が SwiftUI Animation 型(カスタム含む)を受け付け、アニメーションは自然に中断・リダイレクト可能。NSTextField に suggestionsDelegate が追加され、標準プロトコルでカスタム入力候補を提供。システムカーソル(frame resize、column/row resize、zoom in/out)がついに SDK で公開——自前描画は不要です。
システム機能面では、Writing Tools が標準 NSTextView で自動動作、Genmoji がインライン画像としてテキストフローに入り、Image Playground は ImagePlaygroundViewController で数行で統合。Window Tiling は既存 app すべてに自動適用されますが、ウィンドウの最小/最大サイズ制約が妥当か確認が必要です。
詳細
Image Playground 統合(02:09)
extension DocumentCanvasViewController {
@IBAction
func importFromImagePlayground(_ sender: Any?) {
let playground = ImagePlaygroundViewController()
playground.delegate = self
playground.concepts = [.text("birthday card")]
playground.sourceImage = NSImage(named: "balloons")
presentAsSheet(playground)
}
}
extension DocumentCanvasViewController: ImagePlaygroundViewController.Delegate {
func imagePlaygroundViewController(
_ imagePlaygroundViewController: ImagePlaygroundViewController,
didCreateImageAt resultingImageURL: URL
) {
if let image = NSImage(contentsOf: resultingImageURL) {
imageView.image = image
}
dismiss(imagePlaygroundViewController)
}
}
キーポイント:
ImagePlaygroundViewControllerが体験全体の入口。delegate を設定してライフサイクルイベントを監視conceptsはテキスト概念、sourceImageはグラフィック参照——どちらも任意で、ユーザーの出発点になる- 生成完了時、コールバックはサンドボックス一時ディレクトリのファイル URL を返す——自分で読み込み永続化
presentAsSheet(_:)で表示し、delegate コールバックで dismiss
Window Tiling とウィンドウサイズ(05:50)
Window Tiling はウィンドウを画面端にドラッグすると半分または四分の一を自動充填。Option + ドラッグで最近接エッジへのスナップをプレビュー。並列 2 ウィンドウは同期リサイズ。新規ウィンドウは cascadingReferenceFrame で既存ウィンドウの非タイル frame からカスケードオフセットを計算。ターミナル系 app では resizeIncrements で文字幅・高さステップに制限:
window.resizeIncrements = NSSize(width: characterWidth, height: characterHeight)
NSHostingMenu:AppKit 向け SwiftUI メニュー(07:05)
struct ActionMenu: View {
var body: some View {
Toggle("Use Groups", isOn: $useGroups)
Picker("Sort By", selection: $sortOrder) {
ForEach(SortOrder.allCases) { Text($0.title) }
}.pickerStyle(.inline)
Button("Customize View…") { <#Action#> }
}
}
let menu = NSHostingMenu(rootView: ActionMenu())
let pullDown = NSPopUpButton(image: image, pullDownMenu: menu)
キーポイント:
- メニュー内容は標準 SwiftUI 宣言:
Toggleでスイッチ、Pickerで選択、Buttonでアクション NSHostingMenuは SwiftUI View を NSMenu にラップする新 NSMenu サブクラス- ラップ後の menu は NSMenu を受け付ける AppKit コンテキスト(
NSPopUpButtonのプルダウンなど)で使用可能
NSView の SwiftUI アニメーション(07:43)
NSAnimationContext.animate(with: .spring(duration: 0.3)) {
drawer.isExpanded.toggle()
}
@Invalidating プロパティラッパーと組み合わせ、プロパティ変更で自動再レイアウト:
class PaletteView: NSView {
@Invalidating(.layout)
var isExpanded: Bool = false
private func onHover(_ isHovered: Bool) {
NSAnimationContext.animate(with: .spring) {
isExpanded = isHovered
layoutSubtreeIfNeeded()
}
}
}
キーポイント:
NSAnimationContext.animate(with:)は CustomAnimation を含む任意の SwiftUI Animation 型を受け付け- アニメーションは自然に中断・リダイレクト可能——途中で方向変更してもカクつかない
@Invalidating(.layout)でプロパティ変更時に view の再レイアウトをマーク
テキストハイライト(10:31)
let attributes: [NSAttributedString.Key: Any] = [
.textHighlight: NSAttributedString.TextHighlightStyle.systemDefault,
.textHighlightColorScheme: NSAttributedString.TextHighlightColorScheme.pink,
]
リッチテキスト NSTextView で自動利用可能。Font メニューの Highlight サブメニューでカラースキームを選択。
Text Entry Suggestions(17:49)
class MuseumTextSuggestionsController: NSTextSuggestionsDelegate {
typealias SuggestionItemType = Museum
func textField(
_ textField: NSTextField,
provideUpdatedSuggestions responseHandler: @escaping ((ItemResponse) -> Void)
) {
let searchString = textField.stringValue
func museumItem(_ museum: Museum) -> Item {
var item = NSSuggestionItem(representedValue: museum, title: museum.name)
item.secondaryTitle = museum.address
return item
}
let favoriteMuseums = Museum.favorites.filter({
$0.matches(searchString)
})
let favorites = NSSuggestionItemSection(
title: NSLocalizedString("Favorites", comment: ""),
items: favoriteMuseums.map(museumItem(_:))
)
var response = NSSuggestionItemResponse(itemSections: [favorites])
response.phase = .intermediate
responseHandler(response)
Task {
let otherMuseums = await Museum.allMatching(searchString)
let nonFavorites = NSSuggestionItemSection(items: otherMuseums.map(museumItem(_:)))
var response = NSSuggestionItemResponse(itemSections: [favorites, nonFavorites])
response.phase = .final
responseHandler(response)
}
}
}
キーポイント:
textField.suggestionsDelegateを設定するだけで有効。NSTextField と NSSearchField などサブクラスもサポート- 2 段階返却可能:まず
.intermediate同期結果、次に非同期.final結果 NSSuggestionItemはsecondaryTitleで追加情報、NSSuggestionItemSectionでグループ化
重要ポイント
-
何をするか:NSTextField に Text Entry Suggestions を導入し、検索候補やオートコンプリートを提供。なぜ価値があるか:ユーザーは入力時の即時フィードバックを期待。同期+非同期の 2 段階返却をサポートするシステム標準 API で、自作ポップオーバーより一貫。始め方:
NSTextSuggestionsDelegateを実装しtextField.suggestionsDelegateを設定。まず.intermediateローカル結果、次に非同期.finalリモート結果。 -
何をするか:NSHostingMenu でメニュー定義を SwiftUI に移行し、AppKit/SwiftUI で共有。なぜ価値があるか:メニューロジックを 1 箇所で保守。Toggle、Picker、Button の宣言型記述は NSMenuItem 手動構築より明確。始め方:SwiftUI View をメニュー body として作成し
NSHostingMenu(rootView:)でラップ、既存 NSMenu 構築コードを置換。 -
何をするか:レガシー NSAnimationContext アニメーションを SwiftUI Animation に置換。なぜ価値があるか:Spring やカスタムアニメーションを 1 宣言で使用、中断・リダイレクトも自然でインタラクション向上。始め方:
NSAnimationContext.runAnimationGroupをNSAnimationContext.animate(with: .spring(duration:))に置換し、アニメーション対象プロパティに@Invalidating(.layout)を付与。 -
何をするか:Image Playground を画像入力ソースとして統合。なぜ価値があるか:写真ライブラリや Finder からの画像挿入を既にサポートしている app なら、Image Playground は摩擦ゼロの第 3 の経路。始め方:
ImagePlaygroundViewControllerを作成、delegate と任意の concepts/sourceImage を設定、sheet で表示、delegate でファイル URL を読み込み。 -
何をするか:自前描画カーソルをシステムカーソル API に置換。なぜ価値があるか:システムカーソルはアクセシビリティの大サイズとポインタ色カスタマイズを自動サポート。始め方:
NSCursor.frameResize(position:directions:)、NSCursor.columnResize(directions:)、NSCursor.zoomIn/.zoomOutでカスタムカーソル画像を置換。
関連セッション
- Enhance your UI animations and transitions — UIKit と AppKit での SwiftUI Animation 使用方法の詳解
- Get started with Writing Tools — Writing Tools の動作と app との連携
- Bring expression to your app with Genmoji — カスタム絵文字のインライン画像としての適用方法
- What’s new in SF Symbols 6 — SF Symbols 6 の新シンボル、アニメーション効果、カスタムシンボル
- Animate symbols in your app — 新しいシンボルアニメーション効果の使用方法
コメント
GitHub Issues · utterances