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; 
    } 
} 

的意見如何運動的表示是顯示在下面......兩個紅色區域是問題,後面會解釋插圖

enter image description here

呈現(推)翻譯工作正常 - 2移動到1和3移動到2.然而,解僱(流行)翻譯沒有,從而解僱VC看起來正確地移動(2移動到3),但呈現(前一個)VC到達或者在錯誤的地方或者尺寸大小不正確的地方...

與類一樣,翻譯導致2移動到3(正確),但是1移動到4該視圖的尺寸正確,但似乎偏離了預期位置的看似任意的距離。我從此嘗試了各種不同的解決方案。

在彈出部我試圖將下面的行(在代碼註釋): -

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

...,但VC現在最終被收縮(1個移動至5)。我希望有人能看到我正在犯的可能的愚蠢錯誤。我試着簡單地將push部分複製到pop部分(並反轉所有內容),但它不起作用!

僅供參考......那些需要知道如何來裝過渡到一個UINavigationController - 在UINavigationControllerDelegate添加到您的導航控制器,具有以下功能一起...

 
    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; 
    } 

下圖顯示了所有意見將共享相同的始發點(用於翻譯)。目標是給出一個左輪手槍桶將每個VC移動到視野中的錯覺。頂部中央視圖表示查看窗口,顯示堆棧中的第三個視圖。道歉對窮人的視覺效果......

回答

3

的問題是,在恢復視圖控制器的視圖屬性之一是沒有得到正確復位。我建議在動畫完成後重新設置它(如果您之後做了其他動畫,假設視圖未被轉換,您可能不想保留非標準transformanchorPoint)。因此,在動畫的completion塊中,重置視圖的position,anchorPointtransform

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 
    } 
} 

我做了一些其他雜物的變化,而我在這裏:

  • 淘汰分號;
  • 消除了其中一個持續時間屬性;
  • 修復completion關閉animate方法的參數名稱到finished而不是animated以更準確地反映它的真實目的是...你也可以使用_;
  • 根據動畫是否被取消設置completeTransition(因爲如果你曾經做過這種互動/可取消,你不想總是使用true);
  • 使用.pi而不是M_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; 
    } 
} 

這個特殊的過渡是非常簡單的,所以它不是太難以重置視圖框架的屬性,但如果你做了更復雜的任何事情,那麼canvas和快照方法運行得非常好,所以我傾向於在任何地方使用它。