Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

UIKitによるCustomTransition・SwiftUIでの類似表現&HeroTran...

 UIKitによるCustomTransition・SwiftUIでの類似表現&HeroTransition関連に関するよもやま話

YUMEMI.grow Mobile #3での登壇資料になります。

以前CustomTransition関連の調査やサンプル実装をした経験から関心があったこともあり、このタイミングで自分自身の復習という意味も込めて簡単に実装方法や方針に関する基本事項を紹介したものになります。

実装手段を改めて比べてみると違いを知る事ができ、そしてその部分が面白くもあるとも感じている次第です。

「UIKitを利用した場合のCustomTransition関連実装のおさらい」を中心に据えて、SwiftUIでは同様の表現を実現するための方針や、AndroidやFlutterでの実現方針と比較すると大きく異なるが考え方は少し似ている点について紹介しています。

Fumiya Sakai

May 07, 2023
Tweet

More Decks by Fumiya Sakai

Other Decks in Technology

Transcript

  1. 自己紹介 ・Fumiya Sakai ・Mobile Application Engineer アカウント: ・Twitter: https://twitter.com/fumiyasac ・Facebook:

    https://www.facebook.com/fumiya.sakai.37 ・Github: https://github.com/fumiyasac ・Qiita: https://qiita.com/fumiyasac@github 発表者: ・Born on September 21, 1984 これまでの歩み: Web Designer 2008 ~ 2010 Web Engineer 2012 ~ 2016 App Engineer 2017 ~ Now iOS / Android / sometimes Flutter
  2. CustomTransitionを利用する事で得られる効果 画面遷移時表現としての位置付け以上の視覚的な効果も期待できる場合もある (参考)過去の登壇資料 ※書籍サンプルと一緒にAnimationを考察したもの : https://www.slideshare.net/fumiyasakai37/ui-172957909 Why CustomTransition? 画面遷移時に使われる効果やAnimationを指す モバイルアプリでは画面表示スペースが限ら

    れているため、ユーザーに何度も画面遷移を してもらう必要がある。 📝 モバイルアプリ特有の背景として… ✨ 効果的な活用でUserのストレスを軽減 ✨ 無意識に操作方法をに学習させる効果 機能と体験の調和を感じる例の1つ 😊 様々なiOS/Androidアプリ内で心地よい・気になる画面遷移があるはず (Adobe記事) アプリに最適なアニメーション遷移とスピードを考えてみよう! https://blog.adobe.com/jp/publish/2016/08/25/web-xd-ui-transition-easing Appleが提供するアプリ内でも美しい画面遷移が散りばめられている
  3. UIKit利用時における基本事項の整理と復習(1) CustomTransitionを利用する表現において最低限押さえるべき事項 UIViewControllerAnimatedTransitioning: NSObjectを継承したクラスをまずは準備し、次にこのプロトコルに準拠させる必要がある。 画面遷移Animationの実体となる部分 class DetailTransition: NSObject { //

    ※その他画面遷移をカスタマイズする為に必要な変数値を定義する // 👉 画面遷移処理を組み立てるに当たって必要な数値や要素を外部から渡せる様にしておくと良いです。 // 例. トランジションの方向を決定するための値や位置・サイズ情報等。 } // MARK: - UIViewControllerAnimatedTransitioning extension DetailTransition: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { … } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { … } } animateTransitionの処理内でクラス内に定義した変数を適用する。 ① Animation秒数 ② Animation処理 https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning
  4. UIKit利用時における基本事項の整理と復習(2) 連続性のあるAnimation処理の為に画面遷移元&遷移先の情報等を活用する // MARK: - UIViewControllerAnimatedTransitioning extension DetailTransition: UIViewControllerAnimatedTransitioning {

    // アニメーションの時間を定義する func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.28 } // アニメーションの実装を定義する // 画面遷移コンテキスト(UIViewControllerContextTransitioning)を利用する 👉 遷移元や遷移先のViewController等の関連情報が格納されている func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { // コンテキストを元にViewのインスタンスを取得する(存在しない場合は処理を終了) guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else { return } guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return } // アニメーションの実体となるContainerViewを作成する let container = transitionContext.containerView // 👉 CustomTransitionの実体となるContainerViewのAnimation定義をする … } } ① 両方の画面View要素を取得する ② ContainerViewへfromView/toViewを場合に応じて追加 例. 進む遷移: toView / 戻る遷移: fromView を追加 ③ 加えて必要な値等を適用しAnimation処理を実行する
  5. UIKit利用時における基本事項の整理と復習(3) Present/Dismiss&Push/Popの画面遷移を実行する際に最低限押さえるべき事項 UIViewControllerTransitioningDelegate: Present/Dismiss画面遷移時にCustomTransitionを適用するために必要なもの https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate UINavigationControllerDelegate: https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate Push/Pop画面遷移時にCustomTransitionを適用するために必要なもの func animationController(forPresented

    presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? 下記の部分に定義したCustomTransition用のクラス定義を適用する(Present/Dismissを分けられる様にしておく) 下記の部分に定義したCustomTransition用のクラス定義を適用する + Swipe処理時Interaction定義を適用する
  6. UIKit利用時における基本事項の整理と復習(4) Present/Dismiss時の画面遷移を実行する際に最低限押さえるべき事項 // MARK: - UIViewControllerTransitioningDelegate extension MainViewController: UIViewControllerTransitioningDelegate {

    // 進む場合のアニメーションの設定を行う func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { // 現在の画面サイズを引き渡して画面が縮むトランジションにする newsTransition.originalFrame = self.view.frame newsTransition.presenting = true return newsTransition } // 戻る場合のアニメーションの設定を行う func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { // 縮んだ状態から画面が戻るトランジションにする newsTransition.presenting = false return newsTransition } } 📝 Animation付きCrossFade ※ Animationの内容としてはContainerViewをアフィン変換を利用したものである。 クラス内プロパティで画面遷移の方向を決める(クラス自体を分割してもOK)
  7. UIKit利用時における基本事項の整理と復習(5) Push/Pop時の画面遷移を実行する際に最低限押さえるべき事項 // MARK: - UINavigationControllerDelegate extension MainViewController: UINavigationControllerDelegate {

    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { guard let targetInteractor = detailInteractor else { return nil } return targetInteractor.transitionInProgress ? targetInteractor : nil } } UIPercentDrivenInteractiveTransition: 左端をSwipeすることでPop(遷移先から戻る)処理をする際に必要なもの https://developer.apple.com/documentation/uikit/uipercentdriveninteractivetransition NSObjectを継承したクラスをまずは準備し、次にこのプロトコルに準拠させる必要がある。 ※ UIPercentDrivenInteractiveTransitionに準拠したクラス内で実施していること。 UIScreeEdgePanGestureRecognizerの処理と連動させる様にする。 ※ UIScreeEdgePanGestureRecognizerの処理における、.began /.changed / .cancelled / .ended と連携する UIPercentDrivenInteractiveTransitionにおける、update(progress) / cancel() / finish()を実行する
  8. // MARK: - UINavigationControllerDelegate extension MainViewController: UINavigationControllerDelegate { func navigationController(_

    navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { // カスタムトランジションのクラスに定義したプロパティへFrame情報とUIImage情報を渡す guard let frame = selectedFrame else { return nil } guard let image = selectedImage else { return nil } detailTransition.originFrame = frame detailTransition.originImage = image switch operation { case .push: self.detailInteractor = DetailInteractor(attachTo: toVC) detailTransition.presenting = true return detailTransition default: detailTransition.presenting = false return detailTransition } } } UIKit利用時における基本事項の整理と復習(6) Push/Pop時の画面遷移を実行する際に最低限押さえるべき事項 📝 写真が浮き上がる表現 ※ CustomTransition用のクラスを適用する ※ 左端をSwipeしてPop遷移を実施する処理クラス Animationに必要な要素(画像/Frame値)を渡す UIPercentDrivenInteractiveTransitionを継 承したクラスをPushでの画面遷移時に渡す
  9. UIKit利用時における基本事項の整理と復習(7) 今回紹介した画面表示時における画面遷移表現を実現するコード例 📝 Animation付きCrossFade 📝 写真が浮き上がる表現 Animation付きCrossFadeをする様なCustomTransitionの様な実装コード例 (Present / Dismiss)

    https://gist.github.com/fumiyasac/ab396d4938c94115eaa2ecfa71cbc382 一覧画面からサムネイル画像が浮き上がってくる様なCustomTransitionの様な実装コード例 (Push / Pop) https://gist.github.com/fumiyasac/549654724c711e96f9e1fa989931acf1
  10. MatchedGeometryEffectを利用した実装のイメージ図 UIKitでの実装方針や考え方とは大きく異なる点には注意が必要かもしれない点 一覧用View画面 詳細用View画面 @Namespace private var namespace CellView() .frame(height:

    360.0) .contentShape(Rectangle()) .onTapGesture { withAnimation(※Animation処理に関する指定) { isExpanded = true } } .overlay { if let isExpanded { DetailView(productID: $productID, animationID: namespace) } } 詳細用View画面に配置したAnimation対象と結び付ける ※表示処理時に一意なIDとnamespaceを引き渡す形とする .overlay Modifierを利用して詳細用Viewを重ねる LazyVGrid等を利用して一覧表示をする .matchedGeometryEffect 一覧表示と詳細表示画面の表示コントロールと一意なID @Stateを変更してView要素を再描画 @State private var productID: String = `` @State private var isExpanded: Bool = false /
  11. Shared Element Transitionを利用した実装のイメージ図 画面遷移元と遷移先のxmlに同じtransitionNameを指定しIntentを発行する方針 一覧用View画面 詳細用View画面 transitionName = robot transitionName

    = robot 画面遷移時のIntent発行タイミングで文字列を引き渡す ① .makeSceneTransitionAnimationを利用する点がポイント ② Animation対象の要素についてはPair.createで複数指定可 ※下記はActivityのxmlを指定している場合のコードです
  12. Flutterではどの様に実現するか? 前述したiOSのHeroTransitions(UIKit製OSS)での使い方とよく似ている (Flutter公式ドキュメント)Hero animations : https://docs.flutter.dev/ui/animations/hero-animations // (1) 画面遷移元 GestureDetector(

    onTap: () { // 画面遷移先へ進む処理 }, child: Hero( tag: 'imageID_00001', child: Image.network('API画像表示用URL'), ), ); // (2) 画面遷移先 GestureDetector( onTap: () { // 画面遷移元へ戻る処理 }, child: Center( child: Hero( tag: 'imageID_00001', child: Image.network('API画像表示用URL'), ), ), );
  13. Thank you for listening ! 今回はUIKit側の実装解説がメインでしたが、SwiftUI / Android / Flutter側については概要を紹介するだけですみません。

    この部分は自分でも研究するテーマとして今後とも追いかけていきたい部分でもあり、UI実装の面白い部分の1つだと思います。