繁体   English   中英

Swift 3中的自定义转换无法正确转换

[英]Custom transition in Swift 3 does not translate correctly

我已经实现了一个导航控制器,以结合旋转盘类型的布局(想象每个VC布局成一个圆圈,整体旋转,顺序进入视图。导航控制器配置为使用自定义过渡类,如下所示: -

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移动到1和3移动到2.然而,解雇(弹出)翻译没有,从而解雇VC看似正确地移出视图(2移动到3),但呈现(上一篇)VC到达错误的地方,或者框架尺寸不正确......

对于类按原样,平移导致2移动到3(正确)但是1然后移动到4,视图的大小正确,但似乎偏离预期位置看似任意的距离。 此后我尝试了各种不同的解决方案。

在pop部分,我尝试添加以下行(在代码中注释): -

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

......但VC现在最终收缩(1移至5)。 我希望有人能看到我正在犯的可能是愚蠢的错误。 我试过简单地将推送部分复制到弹出部分(并反转所有内容),但它只是不起作用!

仅供参考...那些需要知道如何将过渡连接到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移动到视野中。 顶部中心视图表示查看窗口,显示堆栈中的第三个视图。 为可怜的视觉效果道歉......

在此输入图像描述

问题是恢复的视图控制器视图中的某个属性未正确重置。 我建议在动画完成时重置它(你可能不想保留非标准transformanchorPoint ,以防你以后做其他动画,假定视图没有变换)。 因此,在动画的completion块中,重置positionanchorPoint和视图的transform

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方法的completion闭包的参数名称修改为finished而不是animated以更准确地反映它的真正目的是什么......你也可以使用_ ;
  • 根据动画是否被取消来设置completeTransition (因为如果你使这个交互/可取消,你不想总是使用true );
  • 使用.pi而不是M_PI ;
  • 我评论了我对opacity调整,但我通常这样做是为了让效果更加润滑,并确保如果你调整角度使视图重叠,你就不会像动画那样得到其他视图的任何奇怪的文物开始或完成; 我实际上已经计算了参数,所以没有重叠,无论屏幕尺寸如何,所以这没有必要,我注释掉了opacity线,但您可以考虑使用它们,具体取决于所需的效果。

之前我展示了如何简化过程,但结果效果并不完全符合您的要求,但如果您有兴趣,请参阅此答案上一个演绎

当您执行自定义视图控制器转换时,您的问题是常见问题。 我知道这是因为我做了很多:)

您正在寻找弹出过渡中的问题,但实际问题在于推动。 如果在转换后检查堆栈中第一个控制器的视图,您将看到它有一个不寻常的框架,因为您已经弄乱了它的变换和锚点以及图层位置等等。 真的,你需要在结束过渡之前清理所有这些,否则它会在你以后咬你,就像你在流行音乐中看到的那样。

一种更简单,更安全的自定义转换方法是添加“画布”视图,然后在该画布上添加传出视图和传入视图的快照,并对其进行操作。 这意味着您在转换结束时没有清理 - 只需删除画布视图。 我在这里写过关于这种技术的文章。 对于您的情况,我添加了以下便捷方法:

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

这个特定的转换非常简单,因此重置视图框架的属性并不困难,但如果你做了更复杂的事情,那么画布和快照方法的效果非常好,所以我倾向于在任何地方使用它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM