Highlight
SwiftUI は 2025 年に Liquid Glass デザインシステム、macOS の List で 6 倍のロード高速化、
@Animatableマクロ、WebView、Chart3D、TextEditor のリッチテキストバインディング、そして SwiftUI のシーンを UIKit/AppKit アプリにブリッジする機能を獲得しました。
主な内容
List を書いたことのある開発者なら誰もが経験したことのある問題があります。macOS で 10 万件のアイテムを持つ List はリフレッシュのたびにカクつき、スクロールするとフレームが落ちます。カスタム Shape にアニメーションを付けようとすると、長い AnimatableData を書いて各プロパティを手動で展開する必要があります。既存の UIKit アプリで MenuBarExtra のような SwiftUI 専用のシーン型を使おうとすると、そもそも組み込めないことに気付きます。これらの痛点に、SwiftUI 2025 は正面から答えています。
Anna 氏と Peter 氏は、ハイキングプランニングアプリを題材にすべてのアップデートをつないで紹介しました。再コンパイルするだけで、アプリは自動的に Liquid Glass の外観を獲得し、サイドバーはガラス質感になり、ツールバーアイテムはナビゲーション遷移時に morph します。検索バーは iPhone では自動的に下部に移動し、操作しやすくなります。フレームワーク内部では List のロードが 6 倍、更新が 16 倍高速化され、scroll のスケジューリングが書き直されてハイフレームレート時のフレーム落ちが減りました。新しい SwiftUI Performance Instrument は、専用の lane で body の更新やプラットフォームビューの更新にかかる時間を特定できます。@Animatable マクロは SwiftUI が自動的にアニメーションプロパティを合成してくれます。Scene Bridging により、UIKit/AppKit アプリから直接 MenuBarExtra や ImmersiveSpace のような SwiftUI 専用シーンを利用できるようになりました。WebView、Chart3D、TextEditor の AttributedString バインディングは、長年欠けていたビュー機能を補完しています。
詳細
Liquid Glass のツールバー分割(02:27)。新しい ToolbarSpacer API を使うと、ツールバーアイテム間のグルーピングを明示的にコントロールできます。
NavigationStack {
TripList()
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
UpButton()
DownButton()
}
ToolbarSpacer(.fixed, placement: .primaryAction)
ToolbarItem(placement: .primaryAction) {
SettingsButton()
}
}
}
ポイント:
ToolbarItemGroupで Up/Down の 2 ボタンを 1 つのカプセルにまとめます。ToolbarSpacer(.fixed, placement: .primaryAction)でそれらと Settings の間に固定間隔を挿入し、SettingsButton を独立した Liquid Glass カプセルにします。- ナビゲーション遷移時には、このグループ構造に沿ってカプセルが morph します。
Search を独立した Tab として(04:12)。検索を role: .search の Tab として宣言すると、システムは通常の Tab と視覚的に分離し、タップしたときに直接検索フィールドへ morph させます。
TabView {
Tab("Summary", systemImage: "heart") {
NavigationStack { Text("Summary") }
}
Tab("Sharing", systemImage: "person.2") {
NavigationStack { Text("Sharing") }
}
Tab(role: .search) {
NavigationStack { Text("Search") }
}
}
.searchable(text: $text)
ポイント:
Tab(role: .search)でこれが検索の遷移先であることを示します。.searchableは引き続きTabViewに付与し、textをバインドします。- iPhone でこの Tab をタップすると、新しいレイヤーがポップアップするのではなく、検索フィールドが Tab の位置から morph してきます。
@Animatable マクロ(11:24)。これまでカスタム Shape でいくつかのプロパティをアニメーションさせるには、AnimatableData を手書きしてそれらを AnimatablePair のネストにパッキングする必要がありました。これからはマクロに任せられます。
@Animatable
struct LoadingArc: Shape {
var center: CGPoint
var radius: CGFloat
var startAngle: Angle
var endAngle: Angle
@AnimatableIgnored var drawPathClockwise: Bool
func path(in rect: CGRect) -> Path {
return Path()
}
}
ポイント:
@Animatableが自動的にanimatableDataを合成し、すべてのアニメーション可能プロパティをまとめます。@AnimatableIgnoredを付けることでdrawPathClockwiseのような Bool プロパティを補間対象から外せます。AnimatablePair<AnimatablePair<...>, ...>のようなネストしたボイラープレートはもう不要です。
WebView + WebPage(20:44)。WebKit にようやくネイティブの SwiftUI ビューが用意されました。最もシンプルなバージョンは URL を渡すだけですが、プログラム的にナビゲーションしたい場合は WebPage を使います。
struct InAppBrowser: View {
@State private var page = WebPage()
var body: some View {
WebView(page)
.ignoresSafeArea()
.onAppear {
page.load(URLRequest(url: sunshineMountainURL))
}
}
}
ポイント:
WebPageは Swift のために設計された Observable モデルで、WKWebViewの薄いラッパーではありません。WebView(page)はWebPageインスタンスを受け取り、そのページの描画を駆動します。page.load(_:)でナビゲーションをトリガーします。同じ API でページプロパティの読み取り、JavaScript の呼び出し、カスタム URL scheme もサポートします。
3D チャート(21:35)。Swift Charts に Chart3D が加わり、サーフェスを描画できるようになりました。
Chart3D {
SurfacePlot(x: "x", y: "y", z: "z") { x, y in
sin(x) * cos(y)
}
.foregroundStyle(Gradient(colors: [.orange, .pink]))
}
.chartXScale(domain: -3 ... 3)
.chartYScale(domain: -3 ... 3)
.chartZScale(domain: -3 ... 3)
ポイント:
Chart3Dは新しいコンテナで、SurfacePlotと組み合わせてサーフェスをレンダリングします。- クロージャ
(x, y) -> zで SwiftUI に直接数式を渡すので、事前にサンプリングする必要がありません。 - 3 つの
chart*Scaleで可視領域を限定します。
Drag and Drop の複数選択と削除(22:18)。Mac のドラッグ操作はずっと UIKit/AppKit ブリッジの難所でしたが、今年それが補完されました。
ScrollView {
LazyVGrid(columns: gridColumns) {
ForEach(model.photos) { photo in
view(photo: photo)
.draggable(containerItemID: photo.id)
}
}
}
.dragContainer(for: Photo.self, selection: selectedPhotos) { draggedIDs in
photos(ids: draggedIDs)
}
.dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))
.onDragSessionUpdated { session in
let ids = session.draggedItemIDs(for: Photo.ID.self)
if session.phase == .ended(.delete) {
trash(ids)
deletePhotos(ids)
}
}
.dragPreviewsFormation(.stack)
ポイント:
draggable(containerItemID:)は各セルをコンテナ内のドラッグ可能アイテムとしてマークし、ID だけを渡します。dragContainer(for:selection:)は実際に drop が発生したタイミングで呼ばれ、実体のPhotoデータを遅延ロードします。DragConfiguration(allowDelete: true)でゴミ箱へのドラッグによる削除を許可します。onDragSessionUpdatedでsession.phase == .ended(.delete)を監視し、実際のクリーンアップを行います。.dragPreviewsFormation(.stack)で複数の画像を一山に重ねてドラッグプレビューにします。
TextEditor のリッチテキスト(23:55)。TextEditor に AttributedString をバインドすると、ツールバーには太字・斜体・リストなどの書式コントロールが標準搭載されます。
struct CommentEditor: View {
@Binding var commentText: AttributedString
var body: some View {
TextEditor(text: $commentText)
}
}
ポイント:
- バインディング型を
StringからAttributedStringに変えるだけで、リッチテキスト機能がそのまま使えます。 - システムが標準のフォーマットコントロールを提供しますが、開発者側で段落スタイルをカスタマイズしたり、許可する属性集合を制限したりすることもできます。
主なインサイト
-
やること:
UIRequiresFullscreenを Info.plist から削除し、ウィンドウの自由なリサイズに対応する。- 価値: このキーは iPadOS 26 で deprecated になっており、残しておくと iPad のマルチタスクウィンドウでアプリの挙動が制限されます。
- 始め方: キーを削除した後にアプリを実行し、
NavigationSplitViewの列が狭幅で折りたたまれるときの挙動と、極端なサイズでカスタムレイアウトが破綻しないかを重点的に確認しましょう。
-
やること: SwiftUI Performance Instrument を Time Profiler の代わりに最初の性能調査に使う。
- 価値: 汎用の Time Profiler が見せるのは CPU のフレームグラフですが、新しい Instrument はどの view の
bodyが何度も呼ばれているか、どのプラットフォームビューの更新が最も遅いかを直接教えてくれます。 - 始め方: Xcode 26 で Instruments を開き、SwiftUI テンプレートを選び、最も複雑なリストやアニメーションのシーンを実行して、long body update lane から読み始めましょう。
- 価値: 汎用の Time Profiler が見せるのは CPU のフレームグラフですが、新しい Instrument はどの view の
-
やること: 既存のカスタム Shape の
animatableDataを@Animatableで置き換える。- 価値: ネストした
AnimatablePairは誰もが認めるボイラープレートで、マクロはそれをまるごと取り除き、可読性を一段上げてくれます。 - 始め方: Shape に
@Animatableを付け、アニメーションさせたくないプロパティに@AnimatableIgnoredを付け、手書きのanimatableDataを削除して、既存のアニメーションテストを再実行します。
- 価値: ネストした
-
やること: 既存の UIKit/AppKit アプリを Scene Bridging で改修し、
MenuBarExtra/ImmersiveSpace/RemoteImmersiveSpaceを開けるようにできるか評価する。- 価値: SwiftUI lifecycle に全面移行する予定のない既存プロジェクトにとって、これは Vision Pro の立体コンテンツや Mac のメニューバー機能をアプリに取り込むための最もコストの低いパスです。
- 始め方: UIKit/AppKit のエントリポイントで SwiftUI シーンを宣言し、まず
MenuBarExtraで最小の検証を行ってから、CompositorServices と組み合わせたRemoteImmersiveSpaceを検討しましょう。
-
やること: 内蔵 H5 ページの旧来のラッパーを
WebView+WebPageに置き換える。- 価値: 古い
WKWebViewブリッジは通常 JavaScript bridge でやり取りしており、型安全でなくデバッグしづらいものでした。WebPageは Observable モデルで、ナビゲーションやページプロパティを純粋な Swift で扱えます。 - 始め方: まず表示専用ページ(FAQ や利用規約など)を
WebView(url:)に切り替え、インタラクティブなページはWebView(page)+page.load(_:)に移行し、WebPageでナビゲーションイベントをリッスンするようにしましょう。
- 価値: 古い
関連 Session
- Build a SwiftUI app with the new design — 新しいデザインシステムで SwiftUI アプリをゼロから構築するベストプラクティス
- Meet WebKit for SwiftUI —
WebViewとWebPageの完全な API と発展的な使い方 - Optimize SwiftUI performance with Instruments — 新しい SwiftUI Instrument の各 lane の読み解き方
- Better together: SwiftUI and RealityKit — Entity の Observable、座標変換、PresentationComponent
- Meet SwiftUI spatial layout —
Alignment3DやspatialOverlayなどの空間レイアウトツール
コメント
GitHub Issues · utterances