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

What's new in SwiftUI

元の動画を見る

ハイライト

今回の SwiftUI のアップデートは広範囲にわたりますが、もし一つだけ覚えておくとしたら、これです。過去 5 年間、リッチテキスト編集を必要とする SwiftUI アプリは UITextView をブリッジするしかなく、UIViewRepresentable のボイラープレートを大量に書く必要がありました。今や TextEditorAttributedString、選択範囲の管理、検索ナビゲーションをネイティブにサポートし、このブリッジ層をついに撤去できるようになりました。


主要内容

太字、斜体、色付きハイライトをサポートするメモアプリを書くこと——これが過去の SwiftUI でどれほど苦痛だったか。答えはこうです。SwiftUI の TextEditor を諦めて UIViewRepresentableUITextView をラップし、選択範囲の同期、キーボードツールバー、Undo スタックを自前で処理し、さらに NSAttributedString と SwiftUI の状態を結びつける必要がありました。一見シンプルな機能のために、200〜300 行のグルーコードを書くのが当たり前だったのです。WWDC25 の SwiftUI はこの道筋を一気にまっすぐにしました。TextEditorAttributedString バインディングを直接受け取れるようになり、選択範囲の管理もファーストクラスへと格上げされました。

Liquid Glass は同じアップデートにおけるもう一つの主軸です。その影響範囲はビジュアルシステム全体に及び、単一 API の境界をはるかに超えています。システム標準の TabBar、Toolbar、Button は新しい SDK の下で自動的に新しい外観を獲得します。しかし、これまで手書きしていたガラス風ブラー、カスタム背景色、ZStack による重ね合わせは、新しいマテリアルと衝突する可能性があります。Apple はカスタム View もこのマテリアル言語に参加できるよう glassEffect() 修飾子を提供し、加えてツールバーボタンをグループ化する ToolbarSpacer を新設しました。さらに @Animatable マクロが Animatable プロトコルを自動合成し、visionOS では manipulable() によって任意の 3D View が手によるグラブとスケーリングを得られるようになります——iOS、iPadOS、macOS、visionOS 全プラットフォームを同期して進めるアップデートだといえます。


詳細

ツールバーのグルーピングと Liquid Glass ボタン。新登場の ToolbarSpacer は、ツールバー内のボタンを複数の独立したグループに分割するためのものです。Liquid Glass と組み合わせると、各グループが独立したカプセル状の外観になります。

import SwiftUI

struct TripDetailView: View {
    var body: some View {
        NavigationStack {
            TripList()
                .toolbar {
                    ToolbarItemGroup(placement: .primaryAction) {
                        UpButton()
                        DownButton()
                    }

                    ToolbarSpacer(.fixed, placement: .primaryAction)

                    ToolbarItem(placement: .primaryAction) {
                        SettingsButton()
                    }
                }
        }
    }
}

ポイント:

  • ToolbarItemGroup は上下 2 つの矢印ボタンを 1 つのカプセルにまとめ、視覚的に同じグループとして扱われます。
  • ToolbarSpacer(.fixed, placement: .primaryAction) はその後ろに固定間隔を挿入します。Liquid Glass はこれを基準に、後続のボタンを独立したカプセルとしてレンダリングします。
  • 結果として SettingsButton は前 2 つのボタンから切り離され、すべての操作が同じピル形状に詰め込まれるのを避けられます。

カスタム View にガラスマテリアルを適用する。システムコントロールは Liquid Glass に自動対応しますが、自前で描いたフローティングボタンや通知カードは明示的に宣言する必要があります。

import SwiftUI

struct ToTopButton: View {
    var body: some View {
        Button("To Top", systemImage: "chevron.up") {
            scrollToTop()
        }
        .padding()
        .glassEffect()
    }

    func scrollToTop() {
        // Scroll to top of view
    }
}

ポイント:

  • .glassEffect() は新しい View 修飾子で、対象の View を Liquid Glass マテリアルとしてレンダリングします。
  • これは、これまで .background(.ultraThinMaterial) + .clipShape(Capsule()) といった手書きの組み合わせで実現していた処理を置き換えるものです。
  • padding() を先に適用することで、ガラス領域がボタンの内側余白まで含むようになり、文字にぴったり貼りつくのを避けられます。

@Animatable マクロでボイラープレートを撲滅する。これまでカスタムの Shape をアニメーションさせるには、animatableData を手書きし、複数のプロパティを AnimatablePair のネストに詰め込む必要がありました。今や、マクロ 1 つで十分です。

import SwiftUI

@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 {
        // Creates a `Path` arc using properties
        return Path()
    }
}

ポイント:

  • @AnimatableShape に付与すると、コンパイル時に animatableData が自動合成され、補間可能なすべてのプロパティが連結されます。
  • @AnimatableIgnored はアニメーションに参加しないプロパティをマークします——Bool のような離散値は補間できないため、必ず除外する必要があります。
  • 1 行のコード変更で、これまでの AnimatablePair<AnimatablePair<…>, …> 十数行のボイラープレートと等価になります。

visionOS: 3D View を掴めるようにし、テーブルの上に置けるようにするmanipulable() はジェスチャーによる View のグラブ・回転・スケーリングをユーザーに許可し、surfaceSnappingInfo は View が現実の平面にスナップしたかどうかを通知します。

import ARKit
import RealityKit
import SwiftUI

struct BackpackWaterBottle: View {
    @Environment(\.surfaceSnappingInfo) var snappingInfo: SurfaceSnappingInfo

    var body: some View {
        VStackLayout().depthAlignment(.center) {
            waterBottleView
                .manipulable()

            Pedestal()
                .opacity(
                    snappingInfo.classification == .table ? 1.0 : 0.0)
        }
    }

    var waterBottleView: some View {
        Model3D(named: "waterBottle")
    }
}

ポイント:

  • .manipulable() を 1 行付けるだけで、Model3D View にグラブ・回転・スケーリングといったシステムジェスチャーが付与されます。
  • @Environment(\.surfaceSnappingInfo) で現在のスナップ情報を読み取ります——分類には .table.wall.floor などがあります。
  • ユーザーが水筒をテーブルに置いたときにのみ、下部の Pedestal(台座)が表示されます。空中に浮かんでいるときは隠すことで、視覚的に自然な振る舞いになります。

重要ポイント

  • やること: プロジェクト内のすべての UIViewRepresentable<UITextView> リッチテキストブリッジコードを、ネイティブな TextEditor + AttributedString に置き換える。

    • なぜやる価値があるか: 各ブリッジ層は平均で 200〜300 行のグルーコードを抱えており、選択範囲の同期・キーボードツールバー・Undo スタックという 3 つのバグの温床を含んでいます。ネイティブ API がその部分を SwiftUI 側に取り戻すことで、長期的なメンテナンスコストが大幅に下がります。
    • どう始めるか: まずリポジトリ内のすべての UIViewRepresentable サブクラスを洗い出し、太字・斜体・色など基本属性しか使っていない最小ケースをパイロットとして選び、徐々に複雑なケースへ移行していきます。
  • やること: ツールバーのカスタム背景を棚卸しし、ToolbarSpacer + Liquid Glass のデフォルト外観へ移行する。

    • なぜやる価値があるか: 視覚的な整合性のために設定していた .toolbarBackground のカスタムカラーは、Liquid Glass の下では新マテリアルの屈折効果を壊してしまい、ビルド後に高確率で見栄えが悪くなります。
    • どう始めるか: Xcode の全文検索で toolbarBackgroundultraThinMaterial を洗い出し、カスタム指定を一つずつ取り除いてから、ToolbarSpacer(.fixed, ...) でボタンを再グルーピングし、ビジュアルリグレッションを行います。
  • やること: 手書きで animatableData を実装しているすべての Shape@Animatable マクロに置き換える。

    • なぜやる価値があるか: 手書きの AnimatablePair ネストはきわめて定型的なコードで、プロパティを 1 つ変えるだけで 3 か所を修正する必要があります。マクロはその源流からこうした低価値な労働を排除します。
    • どう始めるか: var animatableData キーワードで候補を洗い出し、それぞれに @Animatable を付け、離散プロパティには @AnimatableIgnored を補完し、UI テストを通してアニメーションカーブの一貫性を確認します。
  • やること: visionOS プロジェクトのすべての Model3D View に manipulable() を付けて評価する。

    • なぜやる価値があるか: 空間環境にいるユーザーの第一反応は手で物を掴むことです。これまでは DragGesture + RotateGesture3D の組み合わせを自前で書く必要がありましたが、これからは 1 行の修飾子で代替できます。
    • どう始めるか: まずデモシーンで .manipulable() を取り付け、続いて surfaceSnappingInfo でスナップ状態を読み取り、スナップ後に台座や影などの補助ビジュアルを表示するかどうかを決めます。

関連セッション

コメント

GitHub Issues · utterances