3

私は、回転ディスクタイプのレイアウトを組み込むためにナビゲーションコントローラを実装しました(各VCを円内にレイアウトし、全体として回転して順番に表示することを想像してください)。コントローラは以下のように、カスタム遷移クラスを使用するように構成されている: -Swift 3のカスタムトランジションが正しく翻訳されない

 
import UIKit 

class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning { 
    let isPresenting :Bool 
    let duration :TimeInterval = 0.5 
    let animationDuration: TimeInterval = 0.7 
    let delay: TimeInterval = 0 
    let damping: CGFloat = 1.4 
    let spring: CGFloat = 6.0 

    init(isPresenting: Bool) { 
     self.isPresenting = isPresenting 
     super.init() 
    } 

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 
     //Get references to the view hierarchy 
     let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! 
     let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! 
     let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController) 
     let containerView: UIView = transitionContext.containerView 

     if self.isPresenting { // Push 
      //1. Settings for the fromVC ............................ 
//   fromViewController.view.frame = sourceRect 
      fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3); 

      //2. Setup toVC view........................... 
      containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view) 
      toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3); 
      toViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI/180)); 

      //3. Perform the animation............................... 
      UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: { 
       fromViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI/180)); 
       toViewController.view.transform = CGAffineTransform(rotationAngle: 0); 
      }, completion: { 
       (animated: Bool) ->() in transitionContext.completeTransition(true) 
      }) 
     } else { // Pop 
      //1. Settings for the fromVC ............................ 
      fromViewController.view.frame = sourceRect 
      fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3); 

      //2. Setup toVC view........................... 
//   toViewController.view.frame = transitionContext.finalFrame(for: toViewController) 
      toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3); 
      toViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI/180)); 
      containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view) 

      //3. Perform the animation............................... 
      UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: { 
       fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI/180)); 
       toViewController.view.transform = CGAffineTransform(rotationAngle: 0); 
      }, completion: { 
       //When the animation is completed call completeTransition 
       (animated: Bool) ->() in transitionContext.completeTransition(true) 
      })    
     } 
    } 

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 
     return duration; 
    } 
} 

ビューが移動する方法の表現は、2つの赤色の領域...以下の図に表示され、後に説明するように、問題である

enter image description here

プレゼンテーション(プッシュ)翻訳はうまく動作する - 1と3への2つの移動は2に移動する。しかし、解消(ポップ)変換は行われないので、解散するVCは外見上正しく外に出る(2は3に移動する) (前の)VCが間違った場所に到着するか、フレームのサイズが正しくない場合...

クラスがそのままでは、2は3(正しく)に移動しますが、1は4に移動しますビューのサイズは正しく設定されていますが、意図した場所から見かけ上任意の距離だけずれているようです。私は以来、さまざまなソリューションを試してきました。私は(コードでコメント)次の行を追加しようとしたポップセクションで

: -

toViewController.view.frame = transitionContext.finalFrame(for: toViewController) 

...しかしVCは現在、縮小されてしまう(1~5移動)。誰かが私が作っている可能性のある愚かなエラーを見ることができることを願っています。私は単純にプッシュセクションをポップセクションに複製しようとしましたが(そしてすべてを逆にする)、うまくいきませんでした! FYI

... UINavigationControllerへの移行をフックアップする方法を知っておく必要がもの -

...次の関数と一緒に、あなたのナビゲーションコントローラに

 
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
     let transition: SwingTransition = SwingTransition.init(isPresenting: (operation == .push ? true : false)) 
     return transition; 
    } 
をUINavigationControllerDelegateを追加します。下の図は、どのようにすべてを示しビューは同じ起点を共有します(翻訳の場合)。目的は、各VCを動かすリボルバーバレルの錯覚を見せることです。上部中央のビューはビューウィンドウを表し、スタック内の3番目のビューを示します。貧しいビジュアルのための謝罪...

答えて

3

問題は、復元されたビューコントローラのビューのプロパティのいずれかが適切にリセットを取得していないことです。私は、アニメーションが完了したらそれをリセットすることをお勧めします(後で他のアニメーションを行う場合は、非標準のtransformanchorPointを保持したくないかもしれません。したがって、アニメーションのcompletionブロックでは、positionanchorPointtransformのビューをリセットします。

class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning { 
    let isPresenting: Bool 
    let duration: TimeInterval = 0.5 
    let delay: TimeInterval = 0 
    let damping: CGFloat = 1.4 
    let spring: CGFloat = 6 

    init(isPresenting: Bool) { 
     self.isPresenting = isPresenting 
     super.init() 
    } 

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 
     let from = transitionContext.viewController(forKey: .from)! 
     let to  = transitionContext.viewController(forKey: .to)! 
     let frame = transitionContext.initialFrame(for: from) 
     let height = frame.size.height 
     let width = frame.size.width 

     let angle: CGFloat = 15.0 * .pi/180.0 
     let rotationCenterOffset: CGFloat = width/2/tan(angle/2)/height + 1 // use fixed value, e.g. 3, if you want, or use this to ensure that the corners of the two views just touch, but don't overlap 

     let rightTransform = CATransform3DMakeRotation(angle, 0, 0, 1) 
     let leftTransform = CATransform3DMakeRotation(-angle, 0, 0, 1) 

     transitionContext.containerView.insertSubview(to.view, aboveSubview: from.view) 

     // save the anchor and position 

     let anchorPoint = from.view.layer.anchorPoint 
     let position = from.view.layer.position 

     // prepare `to` layer for rotation 

     to.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset) 
     to.view.layer.position = CGPoint(x: width/2, y: height * rotationCenterOffset) 
     to.view.layer.transform = self.isPresenting ? rightTransform : leftTransform 
     //to.view.layer.opacity = 0 

     // prepare `from` layer for rotation 

     from.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset) 
     from.view.layer.position = CGPoint(x: width/2, y: height * rotationCenterOffset) 

     // rotate 

     UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, animations: { 
      from.view.layer.transform = self.isPresenting ? leftTransform : rightTransform 
      to.view.layer.transform = CATransform3DIdentity 
      //to.view.layer.opacity = 1 
      //from.view.layer.opacity = 0 
     }, completion: { finished in 
      // restore the layers to their default configuration 

      for view in [to.view, from.view] { 
       view?.layer.transform = CATransform3DIdentity 
       view?.layer.anchorPoint = anchorPoint 
       view?.layer.position = position 
       //view?.layer.opacity = 1 
      } 

      transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 
     }) 
    } 

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 
     return duration 
    } 
} 

私はここにいた間、私は、いくつかの他の雑貨の変更をしました:

  • はセミコロンを排除。
  • は、期間プロパティの1つを削除しました。
  • クロージャのパラメータの名前をanimateメソッドに変更して、animatedではなく、finishedに修正して、実際の目的をより正確に反映させます。_も使用できます。
  • アニメーションがキャンセルされたかどうかに基づいてcompleteTransitionを設定します(このインタラクティブ/キャンセル可能なものを作成した場合、常にtrueを使用しないためです)。
  • M_PIではなく.piを使用してください。
  • 私はopacityの調整をコメントアウトしましたが、一般的には、効果をより洗練させ、角度を調整してビューが重なるようにすると、他のビューの奇妙なアーティファクトアニメーションが開始されるとき、またはアニメーションが終了するときのように、私は実際には画面の大きさに関係なく重複しないようにパラメータを計算したので、必要ではなく、opacity行をコメントアウトしましたが、目的の効果に応じて使用することを検討することもできます。

以前私は、プロセスを少し簡素化する方法を示したが、その効果は、あなたが探していたまさにありませんでしたが、もし興味があるならprevious rendition of this answerを参照してください。

+0

コードレビュー_and_問題解決、素敵な仕事! – jrturton

+0

これは、私が望んでいた答えのように見えます。それはとても近いです...悲しいことに、遷移は回転するディスクにビューが固定されているという錯覚を破壊し、異なる場所から発生しているようです。アニメーションを遅くし、いくつかの奇妙な理由、左に移動するビューはビューの右のどこかから始まり、右からのビューは左のどこかに由来します。私は半分の幅で追加しようとしましたが、効果がないようです。 – NickSaintJohn

+0

ビューはリボルバーの箇条書きで、銃身は表示窓ですと想像してください。それらは同じ翻訳元から回転する必要があります。私はまだ微調整しようとしていますが、どこにも行きませんでした。 – NickSaintJohn

3

問題は、カスタムビューコントローラの切り替えを行う際に発生する一般的な問題です。私はこれを多く知っているのでこれを知っています:)

あなたはポップトランジションの問題を探していますが、実際の問題はプッシュです。トランジション後にスタック内の最初のコントローラのビューを調べると、そのトランスフォームとアンカーポイントとレイヤの位置などを混乱させたため、通常とは異なるフレームが表示されます。本当に、トランジションを終了する前にすべてのものをきれいにする必要があります。それ以外の場合は、ポップで見ているように、後で噛み付きます。

カスタムトランジションを行うもっと簡単で安全な方法は、「キャンバス」ビューを追加し、そのキャンバスに代わりに発信ビューと受信ビューのスナップショットを追加して操作することです。これは、移行の最後にクリーンアップがないことを意味します。キャンバスビューを削除するだけです。私はこのテクニックについて書きましたhere。あなたのケースのために、私は以下の便利なメソッドを追加しました:

extension UIView {  
    func snapshot(view: UIView, afterUpdates: Bool) -> UIView? { 
     guard let snapshot = view.snapshotView(afterScreenUpdates: afterUpdates) else { return nil } 
     self.addSubview(snapshot) 
     snapshot.frame = convert(view.bounds, from: view) 
     return snapshot 
    } 
} 

は、代わりにキャンバスビューに周りのスナップショットを移動するための移行コードを更新:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 
     //Get references to the view hierarchy 
     let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! 
     let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! 
     let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController) 
     let containerView: UIView = transitionContext.containerView 

     // The canvas is used for all animation and discarded at the end 
     let canvas = UIView(frame: containerView.bounds) 
     containerView.addSubview(canvas) 

     let fromView = transitionContext.view(forKey: .from)! 
     let toView = transitionContext.view(forKey: .to)! 
     toView.frame = transitionContext.finalFrame(for: toViewController) 
     toView.layoutIfNeeded() 
     let toSnap = canvas.snapshot(view: toView, afterUpdates: true)! 

     if self.isPresenting { // Push 
      //1. Settings for the fromVC ............................ 
      //   fromViewController.view.frame = sourceRect 
      let fromSnap = canvas.snapshot(view: fromView, afterUpdates: false)! 
      fromView.removeFromSuperview() 
      fromSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      fromSnap.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3); 

      //2. Setup toVC view........................... 
      toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3); 
      toSnap.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI/180)); 

      //3. Perform the animation............................... 
      UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: { 
       fromSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI/180)); 
       toSnap.transform = CGAffineTransform(rotationAngle: 0); 
      }, completion: { 
       (animated: Bool) ->() in 
       containerView.insertSubview(toViewController.view, belowSubview:canvas) 
       canvas.removeFromSuperview() 
       transitionContext.completeTransition(true) 
      }) 
     } else { // Pop 
      //1. Settings for the fromVC ............................ 
      fromViewController.view.frame = sourceRect 
      fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3); 

      //2. Setup toVC view........................... 
      let toSnap = canvas.snapshot(view: toView, afterUpdates: true)! 

      toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3); 
      toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3); 
      toSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI/180)); 

      //3. Perform the animation............................... 
      UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: { 
       fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI/180)); 
       toSnap.transform = CGAffineTransform(rotationAngle: 0); 
      }, completion: { 
       //When the animation is completed call completeTransition 
       (animated: Bool) ->() in 
       containerView.insertSubview(toViewController.view, belowSubview: canvas) 
       canvas.removeFromSuperview() 
       transitionContext.completeTransition(true) 
      }) 
     } 
    } 

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 
     return duration; 
    } 
} 

それはないですので、この特定の遷移は非常に簡単ですビューフレームのプロパティをリセットするのは難しいですが、もっと複雑な操作を行うと、キャンバスとスナップショットのアプローチがうまく機能するため、どこでも使用できます。

関連する問題