3

내비게이션 컨트롤러를 구현하여 회전 디스크 유형의 레이아웃을 통합했습니다. 각 VC는 전체적으로 순환하는 원을 기준으로 순서대로 볼 수 있습니다. 제어기는 아래에 정의 전이 클래스를 사용하도록 구성된다 : -스위프트 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

프리젠 테이션 (푸시) 번역은 잘 동작합니다. 1과 3으로 2 번 이동하여 2 번으로 이동합니다. 그러나 해체 (팝) 번역은 해제되지 않으므로 해산 된 VC가 외관상 올바르게 보이지 않게 이동합니다 (2 번은 3 번으로 이동). (이전) VC가 잘못된 위치 또는 틀린 크기의 프레임으로 도착합니다 ...

클래스가있는 상태에서 번역 결과 2는 3 (올바르게)으로 이동하지만 1은 4로 이동합니다 ,보기는 정확하게 크기가 조정되지만 계획된 위치에서 보이는 임의의 거리만큼 오프셋 된 것처럼 보입니다. 나는 그 이후로 다양한 솔루션을 시도했다. 내가 (코드에서 주석) 다음 줄을 추가하는 시도 팝업 섹션에서

: -

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

...하지만 VC 지금은 축소되고 끝 (5-1 이동). 나는 누군가 내가 바라는 바보 같은 실수를 볼 수 있기를 바란다. 간단히 말해서 팝 섹션 (그리고 모든 것을 뒤집는)에 푸시 섹션을 복제하려고 시도했지만 작동하지 않습니다! 참고로

...은 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를 움직이는 것처럼 보이게하는 것입니다. 상단 가운데보기는보기 창을 나타내며 스택의 세 번째보기를 나타냅니다. 가난한 영상에 대한 사과 ...

enter image description here

답변

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

나는, 몇 가지 다른 잡다한 변경을했다 :

  • 이 세미콜론을 제거;
  • 은 지속 기간 속성 중 하나를 제거했습니다.
  • 의 매개 변수 이름을 animate 방법의 매개 변수 이름을 animated이 아닌 finished으로 수정하여 실제 용도를 정확히 반영합니다 ..._도 사용할 수 있습니다.
  • 애니메이션을 취소했는지 여부에 따라 completeTransition을 설정하십시오 (대화식/취소 가능 설정을 한 경우 항상 true을 사용하고 싶지 않으므로).
  • M_PI 대신 .pi을 사용하십시오.
  • 필자는 opacity의 조정을 주석 처리했지만, 일반적으로 터치를 더욱 윤기 나는 효과를주고 각도를 조정하여보기가 겹치면 다른보기의 이상한 유물을 얻지 못하도록 보장합니다. 애니메이션이 시작될 때 또는 끝날 때처럼; 실제로 매개 변수를 계산 했으므로 화면 크기에 관계없이 겹치는 부분이 없으므로 필요하지 않으며 opacity 행을 주석 처리했지만 원하는 효과에 따라 사용하는 것이 좋습니다.

는 이전에 나는 과정을 조금 단순화하는 방법을 보여하지만, 그 결과 효과는 당신이 찾고 있지만, 관심이있는 경우 previous rendition of this answer을 볼 수있는 사람이 아닙니까 정확히이었다.

+0

코드 검토 _and_ 문제 해결, 멋진 작업! – jrturton

+0

이것은 내가 바라는 해답처럼 보입니다. 너무 가깝습니다 ... 슬프게도, 장면 전환은 회전하는 디스크에 고정되어 있다는 환상을 파괴하면서 다른 위치에서 시작된 것 같습니다. 애니메이션 속도가 느려졌습니다. 약간의 기괴한 이유, 왼쪽으로 이동하는보기는보기의 오른쪽에서 시작되며, 오른쪽의보기는 왼쪽으로 시작됩니다. 나는 반폭을 추가하려고 시도했지만 효과가없는 것 같습니다. – NickSaintJohn

+0

리볼버에서보기가 총알이고 총구가보기 창이라고 상상해보십시오. 같은 번역 원점에서 회전해야합니다. 나는 여전히 조정하려고 시도하고 있지만 어디에도 가지 않고있다. – NickSaintJohn

3

사용자 정의보기 컨트롤러 전환을 수행 할 때 일반적으로 발생하는 문제입니다. 나는 이것을 많이했기 때문에 이것을 안다. :)

당신은 팝 전환에서 문제를 찾고 있지만 실제 문제는 푸시이다. 전환 후 스택에서 첫 번째 컨트롤러의 뷰를 검사하면 변환 및 고정 점과 레이어 위치 등을 엉망으로 처리했기 때문에 비정상적인 프레임이 있음을 알 수 있습니다. 정말로, 당신은 전환을 끝내기 전에 모든 것을 정리할 필요가 있습니다. 그렇지 않으면 팝업에서 볼 때 나중에 물게됩니다.

사용자 지정 전환을 수행하는 훨씬 간단하고 안전한 방법은 "캔버스"보기를 추가 한 다음 캔버스에 보내고받는보기의 스냅 샷을 대신 추가하고 조작하는 것입니다. 즉, 전환이 끝나면 정리가 없다는 의미입니다. 캔바스보기 만 제거하면됩니다. 이 기술에 대해 작성했습니다. here.

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

는이 특별한 변화가없는, 그래서 매우 간단하다 : 그럼 대신에 캔버스보기에 주위의 스냅 샷을 이동하여 전환 코드를 업데이트

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

: 귀하의 경우, 나는 다음과 같은 편리한 메소드를 추가 뷰 프레임의 속성을 재설정하기는 너무 어렵지만 더 복잡한 작업을 수행하면 캔버스 및 스냅 샷 방식이 실제로 잘 작동하므로 어디서나 사용하는 경향이 있습니다.