iOS Swift: 自定义交互过渡

iOS Swift: Custom interactive transition

我写了一个自定义滑动过渡,在模态演示中效果很好。但是在推送演示中,"to" 视图位置没有动画。

我已经尝试过使用 alpha 切换翻译的相同代码并且它有效。

from 视图完美运行,只是 to 视图在动画期间保持固定。

func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    let duration = transitionDuration(using: transitionContext)
    let container = transitionContext.containerView

    let toController = transitionContext.viewController(forKey: .to)
    toController?.beginAppearanceTransition(true, animated: true)

    guard
    let to = transitionContext.view(forKey: .to),
    let from = transitionContext.view(forKey: .from)
        else {
        print("To or from view are nil!")
        fatalError()
    }

    container.addSubview(to)

    let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
    var toStartingPoint: CGPoint
    var fromEndingPoint: CGPoint

    switch self.from {
    case .down:
        toStartingPoint = CGPoint(x: 0, y: -from.bounds.height)
        fromEndingPoint = CGPoint(x: 0, y: from.bounds.height)
    case .top:
        toStartingPoint = CGPoint(x: 0, y: from.bounds.height)
        fromEndingPoint = CGPoint(x: 0, y: -from.bounds.height)
    case .right:
        toStartingPoint = CGPoint(x: from.bounds.width, y: 0)
        fromEndingPoint = CGPoint(x: -from.bounds.width, y: 0)
    case .left:
        toStartingPoint = CGPoint(x: -from.bounds.width, y: 0)
        fromEndingPoint = CGPoint(x: from.bounds.width, y: 0)
    }

    to.transform = CGAffineTransform(translationX: toStartingPoint.x, y: toStartingPoint.y)

    animator.addAnimations({
        from.transform = CGAffineTransform(translationX: fromEndingPoint.x, y: fromEndingPoint.y)
    }, delayFactor: 0.0)

    animator.addAnimations({
        to.transform = .identity
    }, delayFactor: 0.0)


    animator.addCompletion { [weak self] position in
        switch position {
        case .start:
            self?.auxCancelCompletion?()
            transitionContext.completeTransition(false)
            self?.auxAnimationsCancel?()
        case .end:
            self?.auxEndCompletion?()
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            from.transform = .identity
            to.transform = .identity
        default:
            transitionContext.completeTransition(false)
            self?.auxAnimationsCancel?()
        }
    }

    if let auxAnimations = auxAnimations {
        animator.addAnimations(auxAnimations)
    }

    self.animator = animator
    self.context = transitionContext

    animator.addCompletion { [unowned self] _ in
        self.animator = nil
        self.context = nil
    }
    animator.isUserInteractionEnabled = true

    return animator
}

我认为这是委托的问题,但 navigationDelgate 设置正确,否则我想我看不到任何动画..

代表设置:

override func viewDidLoad() {
    super.viewDidLoad()
    transitionHelper = SwipeInteractiveTransitionHelper(withDelegate: self)
}


extension TodayViewController: UINavigationControllerDelegate {

    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return transitionHelper?.swipeTransition
    }

    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return transitionHelper?.swipeTransition
    }
}

这里是自定义推送协调器,其中 viewController 是下一个视图控制器,也是我附加委托的地方。

    case .pushCustom:
        guard let navigationController = currentViewController.navigationController else {
            fatalError("Can't push a view controller without a current navigation controller")
        }
        guard let current = currentViewController as? UINavigationControllerDelegate else {
            fatalError("Can't push a view controller without a current  navigation delegate")
        }
        navigationController.delegate = current
        navigationController.pushViewController(viewController, animated: true) { [weak self] in
            self?.currentViewController = SceneCoordinator.actualViewController(for: viewController)
            completion?()
        }

我就是这样做的。希望对你有所帮助。

VC:UIViewController
{
@IBAction func test(_ sender: Any){
navigationController?.delegate = self
let destine = storyboard?.instantiateViewController(withIdentifier: "target")
navigationController?.pushViewController(destine!, animated: true)
}

  func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?{
    return Traistion()
}

}


 class Traistion:  NSObject, UIViewControllerAnimatedTransitioning{


func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let animator  = transitionAnimator(using: transitionContext)
    animator.startAnimation()
}

var animator: UIViewPropertyAnimator!
var context: UIViewControllerContextTransitioning!

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

enum Direction {
    case down
    case top
    case left
    case right
}

var from : Direction = .left

var auxCancelCompletion :(()->())? = {
    return nil
}()

var auxAnimationsCancel :(()->())? = {
    return nil
}()

var auxEndCompletion :(()->())? = {
    return nil
}()


var auxAnimations : (()->())?
func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    let duration = transitionDuration(using: transitionContext)
    let container = transitionContext.containerView

    let toController = transitionContext.viewController(forKey: .to)
    toController?.beginAppearanceTransition(true, animated: true)

    guard
        let to = transitionContext.view(forKey: .to),
        let from = transitionContext.view(forKey: .from)
        else {
            print("To or from view are nil!")
            fatalError()
    }

    container.addSubview(to)

    let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
    var toStartingPoint: CGPoint
    var fromEndingPoint: CGPoint

    switch self.from {
    case .down:
        toStartingPoint = CGPoint(x: 0, y: -from.bounds.height)
        fromEndingPoint = CGPoint(x: 0, y: from.bounds.height)
    case .top:
        toStartingPoint = CGPoint(x: 0, y: from.bounds.height)
        fromEndingPoint = CGPoint(x: 0, y: -from.bounds.height)
    case .right:
        toStartingPoint = CGPoint(x: from.bounds.width, y: 0)
        fromEndingPoint = CGPoint(x: -from.bounds.width, y: 0)
    case .left:
        toStartingPoint = CGPoint(x: -from.bounds.width, y: 0)
        fromEndingPoint = CGPoint(x: from.bounds.width, y: 0)
    }

    to.transform = CGAffineTransform(translationX: toStartingPoint.x, y: toStartingPoint.y)

    animator.addAnimations({
        from.transform = CGAffineTransform(translationX: fromEndingPoint.x, y: fromEndingPoint.y)
    }, delayFactor: 0.0)

    animator.addAnimations({
        to.transform = .identity
    }, delayFactor: 0.0)


    animator.addCompletion { [weak self] position in
        switch position {
        case .start:
            self?.auxCancelCompletion?()
            transitionContext.completeTransition(false)
            self?.auxAnimationsCancel?()
        case .end:
            self?.auxEndCompletion?()
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            from.transform = .identity
            to.transform = .identity
        default:
            transitionContext.completeTransition(false)
            self?.auxAnimationsCancel?()
        }
    }

    if let auxAnimations = auxAnimations {
        animator.addAnimations(auxAnimations)
    }

    self.animator = animator
    self.context = transitionContext

    animator.addCompletion { [unowned self] _ in
       //self.animator = nil
       // self.context = nil
    }
    animator.isUserInteractionEnabled = true

    return animator
   }

 }

使用时interruptibleAnimator。它会至少调用这个函数两次,你应该提供相同但不同的动画师。所以你必须这样称呼它:

  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

 animator =    transitionAnimator(using: transitionContext) as! UIViewPropertyAnimator
 animator.startAnimation()
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    return  animator
}

var animator: UIViewPropertyAnimator!
var context: UIViewControllerContextTransitioning!

或像这样更简单:

 func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    if    animator == nil {animator =    transitionAnimator(using: transitionContext) as! UIViewPropertyAnimator
        animator.startAnimation()}
    return  animator
}

这里animator会被调用两次,而且是同一个。如果

 func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { 
   return transitionAnimator(using: transitionContext) } 

这不是正确的使用方法,因为动画师会有所不同。

https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1829434-interruptibleanimator

查看文档的最后一行。

解决了对目标视图的快照进行动画处理,而不是直接对目标视图进行动画处理。

let to = transitionContext.view(forKey: .to) 让 toViewSnapshot = to.snapshotView(afterScreenUpdates: true)

只需将 toViewSnapshot 用于动画