Оставшееся представление вызвано неправильной анимацией UIViewControllerAnimatedTransitioning - PullRequest
0 голосов
/ 19 мая 2019

Использование Xcode-10.2.1, iOS-12.3, Swift-5.0.1,

У меня странная проблема с неизвестным фоновым изображением при использовании пользовательских UIViewControllerAnimatedTransitioning анимаций!

Проблема в том, что всякий раз, когда происходит пользовательский переход, появляется цветное левое представление, которого там не должно быть!

Во взрывном изображении явно виден неизвестный желтый вид.Вопрос в том, почему он вообще существует и откуда он?(Я полагал, что CircularCustomTranstion вызывает проблему - продолжайте читать ...)

enter image description here

Вот два видео с пользовательским переходом, которые иллюстрируют проблемус неизвестным желтым видом.(соответствующий код можно найти ниже ...):

Фоны должны быть полностью черными, но есть странный неизвестный желтый вид, мешающий анимации ...

enter image description here

enter image description here

Дальнейшие исследования приводят к выводу, что искаженное представление желтого цвета происходит только в том случае, если я ранее использовал CircularCustomTransition (см. Код ниже- изначально взято отсюда ).

Я обнаружил, что даже если контроллеры ViewController, использующие этот CircularCustomTransition, длительно отклоняются (и не хватает памяти), все предстоящие переходы все еще повреждены.(т.е. достаточно запустить CircularCustomTransition, и все последующие переходы покажут этот неприятный нежелательный фоновый вид)

Мой вопрос: почему использование приведенного ниже CircularCustomTransition повреждает все последующие фоны CustomTransition (в видео) ??

Может кто-нибудь помочь мне найти решение о том, как соответствующим образом изменить код CircularCustomTransition?

Вот видео, показывающее, как работает CircularCostomTransition (то есть работает, нок сожалению, любой последующий переход будет поврежден желтым представлением):

enter image description here

Вот код для 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!")
    }
}

1 Ответ

0 голосов
/ 19 мая 2019

Я наконец нашел решение:

Если вы добавите следующие две строки в CircularTransition, то это сработает !!!

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

(пожалуйста, найдите две дополнительные строки в полном коде CircularTransition, показанном в самом низу ...)

Вот все видео перехода после того, как две строки были добавлены в код CircularTransition. Вы видите, что все желтые виды исчезли, как и ожидалось !!

enter image description here

enter image description here

enter image description here

Вот окончательный код CircularTransition: (две дополнительные строки можно найти с комментарием РЕШЕНИЯ)

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