WWDC Quick Look 💓 By SwiftGGTeam
What’s new in SwiftUI

What’s new in SwiftUI

元の動画を見る

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 アプリから直接 MenuBarExtraImmersiveSpace のような 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 + WebPage20: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) でゴミ箱へのドラッグによる削除を許可します。
  • onDragSessionUpdatedsession.phase == .ended(.delete) を監視し、実際のクリーンアップを行います。
  • .dragPreviewsFormation(.stack) で複数の画像を一山に重ねてドラッグプレビューにします。

TextEditor のリッチテキスト23:55)。TextEditorAttributedString をバインドすると、ツールバーには太字・斜体・リストなどの書式コントロールが標準搭載されます。

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 から読み始めましょう。
  • やること: 既存のカスタム 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

コメント

GitHub Issues · utterances