ハイライト
iOS 18 では Zoom Transition と SwiftUI-UIKit アニメーション連携が追加され、フレームワークをまたぐ連続的なインタラクションがより簡単になりました。
主要内容
滑らかなトランジションアニメーションの実装には通常、大量のコードが必要です。UIKit では CAAnimation を、SwiftUI では withAnimation を書きます。ビュー階層が混在している場合、両方のアニメーション状態を手動で同期する必要もあります。
iOS 18 がこれを解決します。まず Zoom Transition:リストの cell をタップすると、全画面の詳細ページに拡大します。単純な視覚効果ではありません— トランジション全体がインタラクティブです。いつでもつかんでドラッグでき、途中で逆方向に操作することも可能。Navigation Push だけでなく、fullScreenCover や sheet でも使えます。
より根本的な変更は、SwiftUI Animation が UIView と NSView を直接駆動できるようになったこと。UIView.animate は不要で、SwiftUI の Animation 型を渡すだけ。混在ビュー階層では、1 つのアニメーション API ですべてのビューを管理できます。
Session では UIKit における Zoom Transition のライフサイクルコールバックのタイミングも解説。「Push 進行中に戻るキーを押す」ような複雑なシナリオでも、システムは Push をキャンセルせず、即座に完了して Pop に変換します。View Controller の観点では常に Appeared 状態に到達し、すべてのコールバック(viewWillAppear/willAppear/didAppear)が期待どおりの順序で実行されます。
詳細
Zoom Transition の基本
SwiftUI での Zoom Transition には 2 ステップが必要です:
NavigationLink {
BraceletEditor(bracelet)
.navigationTransitionStyle(
.zoom(
sourceID: bracelet.id,
in: braceletList
)
)
} label: {
BraceletPreview(bracelet)
}
.matchedTransitionSource(
id: bracelet.id,
in: braceletList
)
キーポイント:
navigationTransitionStyle(.zoom(...))で遷移先ビューが zoom トランジションを使うことを宣言(2:10).matchedTransitionSourceでソースビューをマーク。両方が同じidとnamespaceを共有- トランジション中、ソースビューが遷移先に morph し、画面上の同一 UI 要素の連続性を保持
UIKit での使い方も同様。Push 前に preferredTransition を設定:
func showEditor(for bracelet: Bracelet) {
let braceletEditor = BraceletEditor(bracelet)
braceletEditor.preferredTransition = .zoom { context in
let editor = context.zoomedViewController
as! BraceletEditor
return cell(for: editor.bracelet)
}
navigationController?.pushViewController(braceletEditor, animated: true)
}
キーポイント:
preferredTransitionは遷移先 View Controller に設定(3:02)- クロージャは zoom in と zoom out でそれぞれ 1 回呼ばれる
- ビューを直接キャプチャするのではなく、安定した識別子(モデルオブジェクトなど)を返す。UICollectionView の再利用問題を避けるため
contextから現在のデータを取得し、詳細ページ内で左右スワイプ後も正しいソースビューに戻れる
トランジションライフサイクルと互換性
中断可能な UIKit トランジションの重要な設計:中断された Push はキャンセルされず、即座に完了して Pop になる。
これは次を意味します:
- Push の
viewWillAppear→viewDidAppearは必ず呼ばれる - 同じ runloop サイクルで Pop が始まり、
viewWillDisappear→viewDidDisappearがトリガーされる - コードの観点では、Push が正常完了した直後に Pop が始まったように見える
既存コードとの互換性のためです。Push をキャンセルすると View Controller が「本当に表示された」ことがなく、「viewWillAppear で 1 回だけ実行」という前提の多くのコードが壊れます。
SwiftUI Animation による UIKit ビューの駆動
iOS 18 の最大の連携は、SwiftUI Animation 型を UIView アニメーションに直接使用できること:
UIView.animate(.spring(duration: 0.5)) {
bead.center = endOfBracelet
}
キーポイント:
- パラメータは従来の
UIView.AnimationOptionsではなく SwiftUI のAnimation型(8:39) - CustomAnimations を含むすべての SwiftUI アニメーション型をサポート
- 内部では CAAnimation を作成せず、layer の presentation values を直接操作
- presentation layer は引き続きアニメーション状態を正しく反映
UIViewRepresentable では、新しい context.animate でアニメーションを橋渡し:
struct BeadBoxWrapper: UIViewRepresentable {
@Binding var isOpen: Bool
func updateUIView(_ box: BeadBox, context: Context) {
context.animate {
box.lid.center.y = isOpen ? -100 : 100
}
}
}
struct BraceletEditor: View {
@State private var isBeadBoxOpen = false
var body: some View {
BeadBoxWrapper($isBeadBoxOpen.animated())
.onTapGesture {
isBeadBoxOpen.toggle()
}
}
}
キーポイント:
.animated()modifier で binding の変化にアニメーション情報を付与(9:56)context.animateは現在の Transaction 上のアニメーションをキャプチャし、UIView の変更に適用- Transaction にアニメーションがなければ即座に実行。手動判定不要
- SwiftUI と UIView のアニメーションが完全に同期
ジェスチャー駆動の連続アニメーション
ドラッグジェスチャー終了後に速度を保持するには、従来は初期 velocity の計算が必要:
switch gesture.state {
case .changed:
UIView.animate(.interactiveSpring) {
bead.center = gesture.translation
}
case .ended:
UIView.animate(.spring) {
bead.center = endOfBracelet
}
}
キーポイント:
- ジェスチャー中は
.interactiveSpring。各変化で新しいアニメーションを作成し、互いに retarget(11:39) - ジェスチャー終了後は通常の
.springを使い、以前の velocity を自動継承 - unit velocity の手動計算不要— SwiftUI アニメーションが自動処理
- SwiftUI アニメーションの利点:連続イベントでトリガーされたアニメーションは自動的にマージし速度を保持
重要ポイント
-
デフォルト Push アニメーションの代わりに Zoom Transition を使う:「リストの大きな画像から詳細ページへ」のシナリオがあれば、Zoom Transition を直接有効化。iOS 18 のシステムデザイン言語で、Photos や Messages も使用。modifier と source マーカーを追加するだけで、視覚的連続性が大幅に向上。
-
混在ビュー階層では SwiftUI Animation でモーションを統一:UIKit 部分で UIView.animate を手書きし、SwiftUI 部分で withAnimation するのではなく。新 API で 1 つのアニメーション型ですべてのビューを駆動。特に UIViewRepresentable 内の UIKit ビューは
context.animateで橋渡し— 外部のアニメーション設定が自動的に伝播。 -
ドラッグインタラクションでは interactiveSpring で速度計算を簡略化:「ドラッグして戻る」シナリオ(プルリフレッシュ、カードドラッグ)では、ジェスチャー変化に
.interactiveSpringを使い、ジェスチャー終了時の通常 spring アニメーションが速度を自動継承。velocity/distance 比の手動計算不要— コードが簡潔で、感触も自然。
関連セッション
- What’s new in SwiftUI — TabView、ウィンドウ管理、zoom transition などをカバーする SwiftUI フレームワークの年次更新概要
- What’s new in UIKit — トランジション、テキスト入力、ナビゲーションバーの更新を含む UIKit フレームワークの新機能
- Create custom visual effects with SwiftUI — スクロールエフェクトや色処理を含む SwiftUI カスタム視覚効果の構築方法
- Elevate your tab and sidebar experience in iPadOS — Tab Bar と Sidebar の柔軟な切り替えをサポートする iPadOS 18 の新ナビゲーションシステム
- Create custom hover effects in visionOS — 視線トリガーのカスタムホバーエフェクトの開発方法
コメント
GitHub Issues · utterances