ハイライト
2027 年プラットフォームの SwiftUI には、直接ディスクアクセスとスナップショットベースの差分書き込みに対応した新しい Document API、並べ替え可能なコンテナ、Toolbar の表示優先度と自動最小化、任意のビューで使えるスワイプジェスチャ、そして
@Stateのマクロ化とContentBuilderによるコンパイル性能改善が追加されます。
主要内容
Liquid Glass とウインドウ状態への反応
2027 年版では、SwiftUI アプリはコードを変更しなくても Liquid Glass の更新された外観を自動的に受け取ります。(02:17) macOS 上のカスタム Liquid Glass 要素は interactive としてマークでき、マウスクリックへの反応がより滑らかになります。iPad アプリでは、ウインドウが非アクティブになるとアイコンと文字が自動的に暗くなり、ユーザーが現在のアクティブウインドウを見分けやすくなります。
開発者は appearsActive 環境値を使って、カスタムビューが非アクティブ時にどう表示されるかを細かく制御できます。
struct SidebarFooterView: View {
@Environment(\.appearsActive) private var appearsActive
var body: some View {
MyAccountView()
.opacity(appearsActive ? 1 : 0.5)
}
}
ポイント:
appearsActiveは 2027 年に追加された新しい環境値- ウインドウが非アクティブなら
false、アクティブならtrueを返す - カスタムのサイドバー、ツールバーなどに視覚的なフィードバックを与える用途に使える
iPad と Mac のメニューバーは、デフォルトでは重要な操作のアイコンだけを表示します。.labelStyle(.titleAndIcon) を使うと、特定のメニュー項目でアイコンと文字を同時に表示し、目立たせることができます。(03:34)
Toolbar の適応と表示制御
機能が増えるにつれ、Toolbar のボタンも増えていきます。iPhone や小さなウインドウでは、入りきらないボタンをシステムが自動的に隠します。2027 年版では、この問題に対応するための 3 つの新しい API が用意されます。
表示優先度: visibilityPriority(.high) を使うと、重要なボタンをスペース不足時にも優先して残せます。
StickerPageView()
.toolbar {
ToolbarItemGroup {
UndoButton()
RedoButton()
}
.visibilityPriority(.high)
ToolbarOverflowMenu {
ChoosePhotoButton()
ExportAsImageButton()
ClearAllStickersButton()
}
ToolbarItem(placement: .topBarPinnedTrailing) {
ShareButton()
}
}
ポイント:
visibilityPriority(.high)は Undo/Redo を重要な操作としてマークし、優先的に表示するToolbarOverflowMenuは使用頻度の低いボタンを固定的にオーバーフローメニューへ入れる.topBarPinnedTrailingは Share ボタンを常に右側に表示し、隠れないようにする
自動最小化: ユーザーが下にスクロールすると、ナビゲーションバーが自動的に折りたたまれ、コンテンツのための空間を広げます。(07:37)
ScrollView {
StickerListView()
}
.toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar)
新しい Document API
SwiftUI 既存の FileDocument と ReferenceFileDocument プロトコルは、2027 年版で大きく拡張されました。新しい API は、文書作成コンテキスト、ディスク読み書きの性能最適化、文書 URL への直接アクセスを提供します。(08:12)
文書の作成元: アプリ起動時に、複数の文書作成方法を提示できます。
@main
struct Stickers: App {
var body: some Scene {
DocumentGroupLaunchScene("ステッカーページを作成") {
NewDocumentButton("新規ステッカーページ", source: .blank)
NewDocumentButton("写真からステッカーページ…", source: .photo)
}
DocumentGroup { document in
StickerPageDocumentView(document)
} { configuration, context in
StickerPageDocument(configuration: configuration, context: context)
}
}
}
extension DocumentCreationSource {
static let blank = Self(id: "blank")
static let photo = Self(id: "photo")
}
ポイント:
DocumentGroupLaunchSceneは起動画面に表示する新規文書ボタンを定義するDocumentCreationSourceは空白、写真から作成など、異なる作成元を宣言する- 作成クロージャは
context引数を通じて作成元の情報を受け取り、イニシャライザがそれに応じて動作を決める
書き込み最適化: 新しい WritableDocument プロトコルには、書き込み可能な形式リスト、スナップショットメソッド、Writer の 3 つが必要です。(11:25)
@Observable
final class StickerDocument {
static let writableDocumentTypes: [UTType] = [.stickerDocument]
@MainActor
func snapshot(contentType: UTType) async throws -> sending PageSnapshot {
makeSnapshot()
}
func writer(configuration: sending WriteConfiguration) -> sending Writer {
Writer(contentType: configuration.contentType)
}
}
struct Writer<Snapshot>: DocumentWriter {
typealias Snapshot = PageSnapshot
let contentType: UTType
nonisolated func write(
snapshot: sending PageSnapshot, to destination: URL,
previous: sending PageSnapshot?, progress: consuming Subprogress
) async throws {
// 現在の snapshot と previous snapshot を比較し、変更された部分だけを書き込む。
// Subprogress を使って書き込みの進捗を報告する。
}
}
ポイント:
snapshot()はある時点の文書全体の状態を返し、sendingによってスレッド安全性を保証するWriterのwriteメソッドはnonisolatedかつ非同期で、ディスク書き込みはバックグラウンドで実行されるsnapshotとpreviousを比較することで差分書き込みを実現するSubprogressは保存進捗をシステムに報告するために使う
複数形式への書き出し: 同じ Writer が複数の出力形式をサポートできます。(14:35)
@Observable
final class StickerDocument: WritableDocument {
static let writableContentTypes: [UTType] = [.stickerDocument, .png]
}
// write メソッド内で contentType に応じて分岐する。
if contentType.conforms(to: .stickerDocument) {
// カスタムパッケージ形式を書き込む。
} else if contentType.conforms(to: .png) {
let context = CGContext(/* ... */)
context.draw(/* ... */)
}
読み込みプロトコル: ReadableDocument は WritableDocument と対になるもので、DocumentReader と組み合わせてディスク読み込みを行います。(13:27)
並べ替え可能なコンテナ
2027 年版では Reorderable API が導入され、List や Grid など任意のコンテナでドラッグによる並べ替えをサポートします。(15:58)
List {
ForEach(stickers) { sticker in
StickerListItemView(sticker: sticker)
}
.reorderable()
}
.reorderContainer(for: Sticker.self) { difference in
difference.apply(to: &stickers)
}
同じコードは、並べ替えロジックを変更せずにそのまま LazyVGrid にも使えます。
LazyVGrid {
ForEach(stickers) { sticker in
StickerListItemView(sticker: sticker)
}
.reorderable()
}
.reorderContainer(for: Sticker.self) { difference in
difference.apply(to: &stickers)
}
ポイント:
.reorderable()はForEachを修飾し、ドラッグ操作を有効にする.reorderContainer(for:)は外側のコンテナを修飾し、並べ替えジェスチャを調整するdifference.applyは swift-collections のOrderedDictionaryを使って並び順の変更を反映する- 初めて watchOS もサポートされる
任意のビューで使えるスワイプジェスチャ
スワイプジェスチャは List だけのものではなくなり、任意のビューに適用できるようになりました。(18:15)
ScrollView {
LazyVStack {
ForEach(stickers) { sticker in
StickerListItemView(sticker: sticker)
.swipeActions {
DeleteButton(sticker: sticker)
}
}
}
}
.swipeActionsContainer()
ポイント:
.swipeActions()は個別のリスト項目を修飾する.swipeActionsContainer()は外側のScrollViewを修飾し、すべての子項目のスワイプジェスチャを調整する
Confirmation Dialog と Alert の item バインディング
confirmationDialog と alert は、sheet と同じ item バインディングパターンをサポートするようになりました。(18:54)
struct StickerCanvasView: View {
var stickers: [Sticker]
@State private var stickerToDelete: Sticker?
var body: some View {
ZStack {
ForEach(stickers) { sticker in
PlacedStickerView(sticker: sticker)
.contextMenu {
Button("削除") { stickerToDelete = sticker }
}
}
}
.confirmationDialog("削除しますか?", item: $stickerToDelete) { sticker in
DeleteStickerButton(sticker)
}
}
}
ポイント:
item: $stickerToDeleteはオプショナル値をバインドする- 値が nil でないときにダイアログが自動的に表示され、nil になると自動的に消える
alertも同じ item バインディングパターンをサポートする
詳細
AsyncImage のキャッシュ改善
以前の AsyncImage は、読み込んだ画像をメモリ内に保持しませんでした。上にスクロールしてから戻ると、画像は再度読み込まれていました。2027 年版では標準 HTTP キャッシュがデフォルトで有効になり、コード変更は不要です。(20:57)
より細かく制御したい場合は、カスタムの URLRequest と URLSession を渡せます。
@Observable class StickerStore {
static let imageSession: URLSession = {
let config = URLSessionConfiguration.default
config.urlCache = URLCache(
memoryCapacity: 64 * 1024 * 1024,
diskCapacity: 256 * 1024 * 1024)
return URLSession(configuration: config)
}()
}
ForEach(pets) { pet in
AsyncImage(request: URLRequest(
url: pet.imageURL,
cachePolicy: .returnCacheDataElseLoad)
)
}
.asyncImageURLSession(StickerStore.imageSession)
ポイント:
URLRequestではキャッシュポリシー、リクエストヘッダーなどを設定できるasyncImageURLSession修飾子はグローバルなURLSessionを設定し、長期的な構成に向いている- デフォルトキャッシュは追加コードなしで自動的に有効になる
@State のマクロ化と遅延初期化
2027 年版で、@State はプロパティラッパーからマクロに変わります。最大の変化は、@State が付いた Observable クラスが一度だけ初期化されることです。(23:08)
@Observable class StickerStore { }
struct StickerStoreView: View {
@State private var store = StickerStore()
var body: some View {
// store は初回初期化時だけ作成されます。親ビューの更新では再作成されません。
}
}
この挙動は iOS 17、macOS 14、および対応するバージョンまでバックデプロイされています。
移行時の注意: @State 変数にデフォルト値があり、さらに init 内で代入している場合、コンパイルエラーになります。(23:48)
// エラー: 変数 'self.title' が初期化前に使用されています
struct StickerPageView: View {
@State private var page = StickerPage()
let title: String
init(title: String) {
self.page = StickerPage(title: title)
self.title = title
}
}
// 修正: デフォルト値を取り除く。
struct StickerPageView: View {
@State private var page: StickerPage
let title: String
init(title: String) {
self.page = StickerPage(title: title)
self.title = title
}
}
ContentBuilder によるコンパイル性能改善
深くネストした SwiftUI ビューは、コンパイラエラー「この式を妥当な時間内に型チェックできません」を引き起こしがちでした。(24:31)
原因は、Section、Group、ForEach などのコンテナがそれぞれ複数のイニシャライザオーバーロードを持ち、コンパイラが型を決定するためにすべての組み合わせを試す必要があったことです。2027 年版では、これらのよく使う builder が ContentBuilder に統一され、型チェックの分岐の大半が解消されます。(26:07)
@ContentBuilder
func stickerLibraryView() -> some View {
// ...
}
ポイント:
ContentBuilderはViewBuilderの進化形で、複数の builder 型を統一する- コンパイル性能の改善はすべての最小デプロイメントターゲットに適用される
- Xcode 27 でビルドすると有効になり、対象プラットフォームが 2027 年版でもそれ以前でも関係ない
重要な示唆
1. 新しい Document API で既存の文書アプリを再設計する
FileDocument ベースのアプリを保守しているなら、新しい WritableDocument/ReadableDocument プロトコルへ移行することで、差分書き込み、非同期保存進捗、複数形式への書き出しなどを得られます。入口は DocumentGroup と DocumentGroupLaunchScene です。
2. iPad/Mac アプリに適応型 Toolbar を追加する
visibilityPriority(.high) で中核操作をマークし、ToolbarOverflowMenu に二次的なボタンを入れ、.topBarPinnedTrailing で共有ボタンを固定します。.toolbarMinimizeBehavior(.onScrollDown) と組み合わせれば、スクロール時にナビゲーションバーを自動的に折りたたみ、読みやすさを高められます。
3. カスタムのドラッグ並べ替えを Reorderable API に置き換える
以前 onMove や独自の DragGesture でリストの並べ替えを実装していたなら、.reorderable() と .reorderContainer() に置き換えられます。コードは短くなり、アニメーションとインタラクションは自動で得られ、List、Grid、LazyVGrid など複数のコンテナに対応します。
4. 任意のスクロールビューにスワイプジェスチャを追加する
スワイプジェスチャはもう List に限られません。LazyVStack や ScrollView 内の任意の子ビューに .swipeActions() を追加できます。外側のコンテナに .swipeActionsContainer() を付けるだけで、ジェスチャが調整されます。
5. @State のマクロ化によるコンパイルエラーを確認する
Xcode 27 にアップグレードしたら、プロジェクト内の @State 変数が init 内で重複代入されていないか確認します。修正方法は、プロパティ宣言側のデフォルト値を削除し、init 内だけで代入することです。
関連 Session
- UIKit アプリをモダナイズする — UIKit と SwiftUI を併用するときの画面サイズ対応ガイド
- SwiftUI で強力なドラッグ&ドロップを構築する — Reorderable API とさらに多くのドラッグ場面を詳しく解説
- SwiftData の新機能 — Document API と組み合わせるデータ永続化の方法
- SwiftUI のグラフィックスとアニメーション — Document アプリの視覚効果と組み合わせられる新しいグラフィックス、アニメーション API
- Xcode の新機能 — Xcode 27 の SwiftUI Specialist Skill と What’s New In SwiftUI Skill の使い方
コメント
GitHub Issues · utterances