ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
UIのアニメーションとトランジションの向上
ナビゲーションやプレゼンテーションにズームトランジションを取り入れると、アプリ画面が滑らかに切り替わる印象を生み出すことができます。また、SwiftUIアニメーションを使用してUIKitのビューに動きを与えることで、切れ目を感じさせないアニメーションを簡単に作成できます。このセッションで詳しい方法を確認しましょう。
関連する章
- 0:00 - Introduction
- 1:34 - New transitions!
- 2:07 - Zoom transitions in SwiftUI
- 3:02 - Zoom transitions in UIKit
- 4:15 - UIKit view controller life cycle and callbacks
- 7:02 - Additional tips for UIKit
- 8:10 - SwiftUI animation
- 9:46 - Animating representables
- 11:20 - Gesture-driven animations
リソース
関連ビデオ
WWDC23
-
ダウンロード
「UIのアニメーションと トランジションの向上」にようこそ ここでは SwiftUI UIKit AppKit アニメーションの連携が これまで以上に容易なりました このフレンドリーな連携機能を祝して 友情ブレスレットを作ってみました 今日は このフレンドリーな 連携機能を活かした 素晴らしい新機能や APIをご紹介します 優れた新機能を使って たとえば タップしたセルが拡大されて 画面いっぱいに表示されるような 滑らかなズームトランジションを アプリに追加したい方 またはSwiftUI Animationを使って UIKitやAppKitビューとの 滑らかなインタラクションを実現したい方は ぜひご覧ください なお このビデオのアニメーションはすべて 速度を半分に低下させています APIの紹介のために 友情ブレスレットを デザインを作るアプリを作りました それぞれが私の好きなAPIを表していて APIの単語の最初の文字を使っています SwiftUI.Animation.spring .repeatForever()を意味してます それでは 友情のように強いつながりを持つ 連携機能について確認していきましょう まず ナビゲーションや プレゼンテーションに使用できる 新しい 高度なトランジションを取り上げます 次に SwiftUI Animationと UIKit AppKitとの間の 新しい連携機能について掘り下げます これが新しいトランジションの 実現に役立ちます さらに 混在する階層で Representable型を使用して SwiftUI Animationの橋渡しを する方法を説明します 最後に 連続型ジェスチャが 含まれる場合には 特にUIViewと NSViewのアニメーション化が 効果を発揮することを紹介します それでは 新しいトランジションを見ていきましょう iOS 18では 新しいズームトランジションがあります この新しいトランジションでは タップしたセルが変化して ビューとして表示されます 見た目が新しいだけではありません 常にインタラクティブな操作が可能で トランジションが始まって 実行されている間も ドラッグして動かすことができます アプリの中で 大きなセルからの トランジションを行う場合 ズームトランジションを使うことで トランジションの間も 同じUI要素が継続して 画面上に表示されるので 継続性をより強く表現できます コードに組み込んでみましょう これは NavigationLinkの 既存のコードです ブレスレットのプレビューを表示し タップされると ブレスレットの エディタを表示します このコードではデフォルトの スライドトランジションが実行され スライドさせた方向と反対側から 新しいビューが表示されます しかし このような場合には ズームトランジションが最適です
ズームトランジションを使用するには 2つのことが必要になります 1つ目は 使用したいと明示することです それには 表示されるビューに navigationTransitionStyle モディファイアを追加して ズームトランジションを指定します 2つ目は このモディファイアを ソースビューに接続することです これにより システムで ズーム元のビューが認識されます どちらでも 同じ識別子と 名前空間を指定します これにより SwiftUIでどのプレビューから どのビューを表示するかが認識されます これでズームができます
UIKitでズームトランジションを導入する場合も 流れは同じです こちらのコードでは ブレスレットのプレビューがタップされると そのブレスレット用のエディタを作成し 現在のナビゲーション コントローラにプッシュします
ここにズームを導入するには まず プッシュされたビューコントローラで ズームの使用を指定します 次に ズームトランジションのソースとして 使用するビューを指定します できました
ズームトランジションに渡されたクロージャが ズームイン時に実行され ズームアウトで再度実行されます そのため安定した識別子が必要になります この場合 ブレスレットのモデルオブジェクトは 直接ビューをキャプチャするのではなく ビューをフェッチするのに使用できます これが重要となるのは コレクションビューなどで ソースビューが再利用される可能性が ある場合です
また エディタを離れずにスワイプで ブレスレットを切り替えることもできます つまり エディタ上のブレスレットを 変更できます この処理を行い ズームから 適切なブレスレットのプレビューに戻るには クロージャに渡されたコンテキストを使って エディタから現在の ブレスレットを取得します
ところで これらのAPIは SwiftUIとUIKitの両方の fullScreenCoverおよびシート プレゼンテーションAPIと連携できます それでは UIKitアプリについて 少し詳しく見ていきましょう この新しい滑らかなトランジションと ビューコントローラの ライフサイクルおよび外観に関する コールバックの連携について説明します SwiftUIを使用する方にも UIKitでのコールバックの方法が 参考になります
この例を通じて プッシュされるブレスレットエディタの ビューコントローラの状態について考えます まず 現在のシステムの動作が わかるように すべてのケースを確認します
赤い点がエディタの 現在の状態を表します コールバックによって状態が遷移し メソッドが呼び出されます 通常のケースでは pushが最後まで実行され ユーザーインタラクションがない場合 エディタはDisappeared状態で 開始され
Appearingを通って状態が遷移し その遷移中にviewWillAppear isAppearing didAppearが呼び出され 遷移が終了すると 最後はAppeared状態になります 同様に popが最後まで実行されると エディタはDisappearing状態を通って 状態遷移し 最終的にDisappeared状態に 戻ります 「戻る」ボタンのタップと インタラクティブなスワイプの どちらでpopが実行された場合でも このようになります
一旦元に戻して popがキャンセルされた場合の 動作についても確認します 少しだけドラッグして そのままにした場合 popの遷移が開始され エディタはDisappearing状態に移ります ユーザーが指を持ち上げて popがキャンセルされると アニメーションは最後まで実行されますが 最後にビューコントローラは そのままAppearing状態に移り 最終的にAppeared状態になり 実行ループを一周します これが既存の遷移のシナリオにおける コールバックのタイミングです
新しい遷移の滑らかな動作を テストするとどうなるか お見せします 最初に戻って エディタを Disappeared状態にします pushを開始するとエディタが Appearing状態になります それでは pushの最中に 「戻る」ボタンを押すか 後方スワイプして popを開始するとどうなるかを 考えてみましょう この場合 pushはキャンセルされず pushが即座に完了して エディタはそのまま Appeared状態に移行し 実行ループは止まらずに そのままpopの遷移が開始され Disappearing状態に移行します そしてここからは 通常のpopの遷移となり 完了まで進むか キャンセルになります
pushのキャンセルは popのキャンセルとは流れが異なりますが これは意図的なものです 理論上 pushに割り込みがあっても システムによって キャンセルされることはなく pushは必ず popに変換されます pushされたビューコントローラ側から 見ると 必ずAppeared状態になります つまり システムが必ず 通常どおり コールバックのサイクルを 完全に実行するということです ふう 外観に関するコールバックは頼れる友人だ ということを忘れないように 友情ブレスレットが もう1つ必要なようです
この新機能には既存のコードと 最大限の互換性があります ほかにも UIKitアプリに関して pushとpopの遷移を いつでも開始できるようにし この新しい環境でコードの正常な動作を 確保するためのヒントがいくつかあります いつ遷移が始まっても 対応できるようにしておきましょう 遷移中の処理と 遷移中でないときの 処理を分けないようにします ここでは 遷移が進行中の場合 タップハンドラによる pushの呼び出しが失敗します 遷移が実行中であるかどうかに関係なく pushを呼び出すようにします
一時的な遷移状態は 最小限に抑えます
状態が少ないほど 遷移の状態に依存するコードを 増やさずに済みます 対処が必要なことを減らすことになります
しかし 遷移中の状態を追跡することが 本当に必要な場合は viewDidAppearまたは viewDidDisappearでリセットします これらは遷移の終了時に 必ず呼び出されます ナビゲーションコントローラの デリゲートメソッドを使用する場合 willShowViewControllerと didShowViewControllerで 同様に処理できます 最後に SwiftUIをアプリに 組み込みます SwiftUIは命令型というより 関数型であり 一般に 変化し続ける環境に 対処するのに適しています このような新しい遷移への対応に有効なのが 優れた機能を持つ新しい低レベルAPIです SwiftUI Animationによる UIKitおよびAppKitの ビューのアニメーション化に役立ちます これらを使ってカスタムUIを作成する 方法を確認しましょう こちらはブレスレットエディタです 下部のボックス内のビーズをタップすると ブレスレットの最後尾に ビーズが追加されます
このUIの実装にUIKitまたは AppKitを使用していた場合 既存のアニメーションAPIを使って 呼び出しのパラメータで springの記述を行い クロージャでビューのプロパティを 更新します SwiftUIの場合も同様の構文があり SwiftUI Animationの種類を指定して アニメーションの記述を行い クロージャで状態を更新します ここで 両者の良いところ取りができたら 素晴らしいと思いませんか iOS 18ではそれが可能になっています SwiftUI Animationの種類を使用して UIKitとAppKitのビューの アニメーション化ができます SwiftUI Animationの種類を すべて使用することができます UIKitビューのアニメーション化に SwiftUIのCustomAnimationを 使うこともできます
CALayerで動作するコードの場合は この新しいAPIを使用する際に 考慮すべきことがいくつかあります 既存のUIKit APIでは CAAnimationが生成され それがビューのレイヤに追加されます これに対してSwiftUIのアニメーションでは CAAnimationは作成されず ビューのレイヤーの表示値が そのままアニメーション化されます これらの表示値は プレゼンテーションレイヤにも反映されます
UIKitとAppKitのビューの アニメーション化について説明しました それでは UIViewRepresentableなどの Representable型のコンテキストにおける アニメーション化の方法を見てみましょう ビーズの入るボックス用の 既存のUIViewがあり BeadBoxという名前です これをこのRepresentableラッパーで SwiftUIアプリに埋め込んでいます ボックスにはフタ(lid)があり "isOpen"のバインディングで 開け閉めできます 現在はボックスを開け閉めすると フタの表示/非表示が切り替わるだけです これをアニメーション化したいと思います
BeadBoxWrapperの実装に UIKitが使用されていたかどうかが 分からない場合 animatedモディファイアをバインディングに 追加するのが自然な方法です BeadBoxWrapperが SwiftUIで実装されていた場合は これでうまくいきます しかし lidでUIKitを使用して BeadBoxWrapperが実装されているため アニメーションの橋渡しが必要です
ここでは contextで 新しいanimateメソッドを使用しました これにより このアップデートに関連付けられた トランザクションのアニメーションを updateUIViewメソッドで行う UIViewの変更に適用できます トランザクション上に存在する SwiftUI Animationを取得し UIView.animateメソッドへの 橋渡しをして フタを上下にスライドさせます
できました
現在のトランザクションが アニメーション化されていない場合 animationとcompletionが ただちにインラインで呼び出されるため アップデートがアニメーション化 されているかどうかにかかわらず このコードは機能します 特に 1つのアニメーションが SwiftUIビューとUIViewをまたいで 実行される場合でも 完全に同期されて動作します
ここまで 単発型のアクションに対する アニメーションの実行方法を説明しました これらの同じAPIが連続型の ジェスチャに対して実行される場合に さらにパワフルになることを 確かめましょう 先ほどのビーズのボックスに戻って ボックスからビーズをドラッグして 取り出すときに パンのジェスチャを実行し ブレスレットの最後尾に向かって ふわっと投げたいと思います
こちらは パンのジェスチャに対して ビーズボックスから ビーズをドラッグする処理を行う UIKitのコードです パンジェスチャに変化があると ジェスチャの変換に基づいて ビーズの中心が更新されます ジェスチャが終了すると ビーズが最終的な位置に送られます これをアニメーション化するには ビーズの現在の速度をもとに springの初速度を 計算します また ビーズの現在位置から最終位置までの 移動距離で除算をすることにより 単位速度に変換する必要があります しかし この必要性がなくなれば もっと簡単になるでしょう そうです SwiftUI Animationには すでに ジェスチャの実行中にマージして ジェスチャの終了時の速度を 保持する機能があります ここに示したのは 対応するSwiftUIコードで これにより同じ効果が得られます ジェスチャ終了時の初速度の計算は 必要ありません
この手法はUIViewの アニメーション化にも適用することができ 同じSwiftUI Animationを 新しいUIViewの animateメソッドに渡すことができます
画面上をドラッグすると 指の動きに応じて ジェスチャにより 継続的にchangeイベントが発生し それらのイベントごとに 新しい.interactiveSpring アニメーションが作成され それぞれで 直前のものへの リターゲティングが行われます ジェスチャの終了時には 最後の非インタラクティブの springアニメーションが作成されます このspringではinteractiveSpringの 速度が使用され 連続速度でアニメーションが進みます 連続速度を使えたら 最高ですね アニメーションとトランジションについて 説明してきましたが 後はあなた次第です 大きなセルからズームする場合は ズームトランジションを取り入れましょう アプリ全体の視覚的な 連続性を演出できます ズームトランジションは 常に インタラクティブな操作が可能であるため いつ遷移が開始されても 対応できるコードにしておきましょう UIKitとAppKitのビューのアニメ化に SwiftUI Animationを使用しましょう 特に 連続速度の維持が重要なUIに 効果を発揮します コードを大幅に単純化することができ アニメーションの効果も向上します SwiftUI Animation全体の 詳細については 友人のKyleによるビデオ 「Explore SwiftUI animation」を ご覧ください Springのさらに詳しい情報については Jacobのビデオ 「Animate with springs」をご覧ください この情報を友人にも伝えましょう この知識を活かして 素敵なブレスレットを作って 広めるのもいいでしょう
-
-
2:10 - Zoom transition in SwiftUI
NavigationLink { BraceletEditor(bracelet) .navigationTransitionStyle( .zoom( sourceID: bracelet.id, in: braceletList ) ) } label: { BraceletPreview(bracelet) } .matchedTransitionSource( id: bracelet.id, in: braceletList )
-
3:02 - Zoom transition in UIKit
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) }
-
8:39 - Animate UIView with SwiftUI animation
UIView.animate(.spring(duration: 0.5)) { bead.center = endOfBracelet }
-
9:56 - Animating representables
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() } } }
-
11:39 - Gesture-driven animations
switch gesture.state { case .changed: UIView.animate(.interactiveSpring) { bead.center = gesture.translation } case .ended: UIView.animate(.spring) { bead.center = endOfBracelet } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。