【问题标题】:Leftover view caused by wrong UIViewControllerAnimatedTransitioning animation错误的 UIViewControllerAnimatedTransitioning 动画导致的剩余视图
【发布时间】:2019-05-19 10:52:56
【问题描述】:

使用 Xcode-10.2.1、iOS-12.3、Swift-5.0.1,

我在使用自定义 UIViewControllerAnimatedTransitioning 动画时遇到了一个未知背景视图的奇怪问题!

问题在于,每当自定义转换发生时,都会有一个不应该存在的彩色剩余视图!

在爆炸视图中,显然有一个未知的黄色视图可见。问题是为什么它首先存在,它来自哪里? (我认为是 CircularCustomTranstion 导致了问题 - 继续阅读......)

这里有两个自定义过渡视频,说明了未知黄色视图的问题。 (对应的代码可以在下面找到……):

背景应该是全黑的,但是有一个奇怪的未知黄色视图困扰着动画......

进一步调查发现,只有在我之前使用 CircularCustomTransition 时才会出现损坏的黄色视图(参见下面的代码 - originally taken from here)。

我发现,即使使用此 CircularCustomTransition 的 ViewController 被长时间关闭(并且内存不足),任何即将到来的转换仍然被破坏。 (即,运行 CircularCustomTransition 就足够了,所有后续转换都会显示这个讨厌的不需要的背景视图)

我的问题:为什么使用以下 CircularCustomTransition 会破坏所有以下 CustomTransition 背景(在视频中)??

任何人都可以帮助我找到有关如何适当更改 CircularCustomTransition 代码的解决方案吗?

这是展示 CircularCostomTransition 的视频(即可以正常工作,但不幸的是,黄色视图会破坏任何后期的过渡):

这是 CircularCustomTransition 的代码(有什么问题?)

import UIKit

protocol CircleTransitionable {
    var triggerButton: UIButton { get }
    var contentTextView: UITextView { get }
    var mainView: UIView { get }
}

class CircularTransition: CustomAnimator {
    weak var context: UIViewControllerContextTransitioning?

    public override init(duration: TimeInterval = 0.25) {
        super.init(duration: duration)
    }

    // make this zero for now and see if it matters when it comes time to make it interactive
    override func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.0
    }

    override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: .from) as? CircleTransitionable,
            let toVC = transitionContext.viewController(forKey: .to) as? CircleTransitionable,
            let snapshot = fromVC.mainView.snapshotView(afterScreenUpdates: false) else {
                transitionContext.completeTransition(false)
                return
        }

        context = transitionContext

        let containerView = transitionContext.containerView

        // Background View With Correct Color
        let backgroundView = UIView()
        backgroundView.frame = toVC.mainView.frame
        backgroundView.backgroundColor = fromVC.mainView.backgroundColor
        containerView.addSubview(backgroundView)

        // Animate old view offscreen
        containerView.addSubview(snapshot)
        fromVC.mainView.removeFromSuperview()
        animateOldTextOffscreen(fromView: snapshot)

        // Growing Circular Mask
        containerView.addSubview(toVC.mainView)
        animate(toView: toVC.mainView, fromTriggerButton: fromVC.triggerButton)

        // Animate Text in with a Fade
        animateToTextView(toTextView: toVC.contentTextView, fromTriggerButton: fromVC.triggerButton)
    }

    func animateOldTextOffscreen(fromView: UIView) {
        UIView.animate(withDuration: 0.25, delay: 0.0, options: [.curveEaseIn], animations: {
            fromView.center = CGPoint(x: fromView.center.x - 1000, y: fromView.center.y + 1500)
            fromView.transform = CGAffineTransform(scaleX: 5.0, y: 5.0)
        }, completion: { (finished) in
            self.context?.completeTransition(finished)
        })
    }
    func animate(toView: UIView, fromTriggerButton triggerButton: UIButton) {
        // Starting Path
        let rect = CGRect(x: triggerButton.frame.origin.x,
                          y: triggerButton.frame.origin.y,
                          width: triggerButton.frame.width,
                          height: triggerButton.frame.width)
        let circleMaskPathInitial = UIBezierPath(ovalIn: rect)

        // Destination Path
        let fullHeight = toView.bounds.height
        let extremePoint = CGPoint(x: triggerButton.center.x,
                                   y: fullHeight)
        let radius = sqrt((extremePoint.x*extremePoint.x) +
            (extremePoint.y*extremePoint.y))
        let circleMaskPathFinal = UIBezierPath(ovalIn: triggerButton.frame.insetBy(dx: -radius,
                                                                                   dy: -radius))

        // Actual mask layer
        let maskLayer = CAShapeLayer()
        maskLayer.path = circleMaskPathFinal.cgPath
        toView.layer.mask = maskLayer

        // Mask Animation
        let maskLayerAnimation = CABasicAnimation(keyPath: "path")
        maskLayerAnimation.fromValue = circleMaskPathInitial.cgPath
        maskLayerAnimation.toValue = circleMaskPathFinal.cgPath
        maskLayerAnimation.delegate = self
        maskLayer.add(maskLayerAnimation, forKey: "path")

        context?.completeTransition(true)
    }

    func animateToTextView(toTextView: UIView, fromTriggerButton: UIButton) {
        // Start toView offscreen a little and animate it to normal
        let originalCenter = toTextView.center
        toTextView.alpha = 0.0
        toTextView.center = fromTriggerButton.center
        toTextView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

        UIView.animate(withDuration: 0.25, delay: 0.1, options: [.curveEaseOut], animations: {
            toTextView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
            toTextView.center = originalCenter
            toTextView.alpha = 1.0
        }, completion: { (finished) in
            self.context?.completeTransition(finished)
        })
    }
}

extension CircularTransition: CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        context?.completeTransition(true)
    }
}

为了完整起见,这里是上面视频中显示的两个动画的代码...

class CustomBounceUpAnimationController: CustomAnimator {

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

    override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
        let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
        let finalFrameForVC = transitionContext.finalFrame(for: toViewController)
        let containerView = transitionContext.containerView
        containerView.bringSubviewToFront(toViewController.view)

        let bounds = UIScreen.main.bounds
        toViewController.view.frame = finalFrameForVC.offsetBy(dx: 0.0, dy: bounds.size.height)
        // toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, -bounds.size.height)
        containerView.addSubview(toViewController.view)

        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .curveLinear, animations: {
            fromViewController.view.alpha = 0.5
            toViewController.view.frame = finalFrameForVC
        }) {
            finished in
            transitionContext.completeTransition(true)
            fromViewController.view.alpha = 1.0
        }

        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .curveLinear, animations: {
            fromViewController.view.alpha = 0.5
            toViewController.view.frame = finalFrameForVC
            }, completion: {
                finished in
                transitionContext.completeTransition(true)
                fromViewController.view.alpha = 1.0
        })
    }
}
class Custom3DAnimationController: CustomAnimator {

    var reverse: Bool = false

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

    override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
        let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
        if let toView = toViewController.view {
            containerView.subviews[0].backgroundColor = .clear
            containerView.bringSubviewToFront(toView)
            let fromView = fromViewController.view
            let direction: CGFloat = reverse ? -1 : 1
            let const: CGFloat = -0.005

            toView.layer.anchorPoint = CGPoint(x: direction == 1 ? 0 : 1, y: 0.5)
            fromView?.layer.anchorPoint = CGPoint(x: direction == 1 ? 1 : 0, y: 0.5)

            var viewFromTransform: CATransform3D = CATransform3DMakeRotation(direction * CGFloat(Double.pi/2), 0.0, 1.0, 0.0)
            var viewToTransform: CATransform3D = CATransform3DMakeRotation(-direction * CGFloat(Double.pi/2), 0.0, 1.0, 0.0)
            viewFromTransform.m34 = const
            viewToTransform.m34 = const

            containerView.transform = CGAffineTransform(translationX: direction * containerView.frame.size.width / 2.0, y: 0)
            toView.layer.transform = viewToTransform
            containerView.addSubview(toView)

            UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
                containerView.transform = CGAffineTransform(translationX: -direction * containerView.frame.size.width / 2.0, y: 0)
                fromView?.layer.transform = viewFromTransform
                toView.layer.transform = CATransform3DIdentity
            }, completion: {
                finished in
                containerView.transform = .identity
                fromView?.layer.transform = CATransform3DIdentity
                toView.layer.transform = CATransform3DIdentity
                fromView?.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
                toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)

                if (transitionContext.transitionWasCancelled) {
                    toView.removeFromSuperview()
                } else {
                    fromView?.removeFromSuperview()
                }
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            })
        }
    }
}
import UIKit

open class CustomAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    public enum TransitionType {
        case navigation
        case modal
    }

    let duration: TimeInterval

    public init(duration: TimeInterval = 0.25) {
        self.duration = duration        
        super.init()
    }

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

    open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        fatalError("You have to implement this method for yourself!")
    }
}

【问题讨论】:

    标签: animation uiviewcontroller uikit transition navigationcontroller


    【解决方案1】:

    我终于找到了解决办法:

    如果将以下两行添加到 CircularTransition,那么它可以工作!!!

    // SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION
    // THE FOLLOWING TWO LINES HAVE BEEN ADDED !!!!
    backgroundView.removeFromSuperview()
    snapshot.removeFromSuperview()
    

    (请在最底部显示的 CircularTransition 的完整代码中找到额外的两行...)

    这是在 CircularTransition 代码中添加了这两行之后的所有过渡视频。您会看到所有黄色视图现在都按预期消失了!

    这是最终的 CircularTransition 代码: (可以在 SOLUTION 注释中找到额外的两行)

    import UIKit
    
    protocol CircleTransitionable {
        var triggerButton: UIButton { get }
        var contentTextView: UITextView { get }
        var mainView: UIView { get }
    }
    
    class CircularTransition: CustomAnimator {
        weak var context: UIViewControllerContextTransitioning?
    
        public override init(duration: TimeInterval = 0.25) {
            super.init(duration: duration)
        }
    
        // make this zero for now and see if it matters when it comes time to make it interactive
        override func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.0
        }
    
        override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            guard let fromVC = transitionContext.viewController(forKey: .from) as? CircleTransitionable,
                let toVC = transitionContext.viewController(forKey: .to) as? CircleTransitionable,
                let snapshot = fromVC.mainView.snapshotView(afterScreenUpdates: false) else {
                    transitionContext.completeTransition(false)
                    return
            }
    
            context = transitionContext
    
            let containerView = transitionContext.containerView
    
            // Background View With Correct Color
            let backgroundView = UIView()
            backgroundView.frame = toVC.mainView.frame
            backgroundView.backgroundColor = fromVC.mainView.backgroundColor
            containerView.addSubview(backgroundView)
    
            // Animate old view offscreen
            containerView.addSubview(snapshot)
            fromVC.mainView.removeFromSuperview()
            animateOldTextOffscreen(fromView: snapshot)
    
            // SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION
            // THE FOLLOWING TWO LINES HAVE BEEN ADDED !!!!
            backgroundView.removeFromSuperview()
            snapshot.removeFromSuperview()
    
    
            // Growing Circular Mask
            containerView.addSubview(toVC.mainView)
            animate(toView: toVC.mainView, fromTriggerButton: fromVC.triggerButton)
    
            // Animate Text in with a Fade
            animateToTextView(toTextView: toVC.contentTextView, fromTriggerButton: fromVC.triggerButton)
        }
    
        func animateOldTextOffscreen(fromView: UIView) {
            UIView.animate(withDuration: 0.25, delay: 0.0, options: [.curveEaseIn], animations: {
                fromView.center = CGPoint(x: fromView.center.x - 1000, y: fromView.center.y + 1500)
                fromView.transform = CGAffineTransform(scaleX: 5.0, y: 5.0)
            }, completion: nil)
        }
        func animate(toView: UIView, fromTriggerButton triggerButton: UIButton) {
            // Starting Path
            let rect = CGRect(x: triggerButton.frame.origin.x,
                              y: triggerButton.frame.origin.y,
                              width: triggerButton.frame.width,
                              height: triggerButton.frame.width)
            let circleMaskPathInitial = UIBezierPath(ovalIn: rect)
    
            // Destination Path
            let fullHeight = toView.bounds.height
            let extremePoint = CGPoint(x: triggerButton.center.x,
                                       y: fullHeight)
            let radius = sqrt((extremePoint.x*extremePoint.x) +
                (extremePoint.y*extremePoint.y))
            let circleMaskPathFinal = UIBezierPath(ovalIn: triggerButton.frame.insetBy(dx: -radius,
                                                                                       dy: -radius))
    
            // Actual mask layer
            let maskLayer = CAShapeLayer()
            maskLayer.path = circleMaskPathFinal.cgPath
            toView.layer.mask = maskLayer
    
            // Mask Animation
            let maskLayerAnimation = CABasicAnimation(keyPath: "path")
            maskLayerAnimation.fromValue = circleMaskPathInitial.cgPath
            maskLayerAnimation.toValue = circleMaskPathFinal.cgPath
            maskLayerAnimation.delegate = self
            maskLayer.add(maskLayerAnimation, forKey: "path")
        }
    
        func animateToTextView(toTextView: UIView, fromTriggerButton: UIButton) {
            // Start toView offscreen a little and animate it to normal
            let originalCenter = toTextView.center
            toTextView.alpha = 0.0
            toTextView.center = fromTriggerButton.center
            toTextView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
    
            UIView.animate(withDuration: 0.25, delay: 0.1, options: [.curveEaseOut], animations: {
                toTextView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
                toTextView.center = originalCenter
                toTextView.alpha = 1.0
            }, completion: nil)
        }
    }
    
    extension CircularTransition: CAAnimationDelegate {
        func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
            context?.completeTransition(true)
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多