使用Xcode-10.2.1,iOS-12.3,Swift-5.0.1,
使用自定义UIViewControllerAnimatedTransitioning
动画时,背景视图未知时我遇到一个奇怪的问题!
问题在于,每当自定义转换发生时,就会有一个彩色的剩余视图,该视图不应该存在!
在爆炸视图中,显然有一个未知的黄色视图可见。问题是为什么它首先出现在哪里,它从哪里来? (我认为是CircularCustomTranstion引起了问题-请继续阅读...)
以下是两个自定义转换视频,这些视频说明了未知黄色视图的问题。 (相应的代码可以在下面找到...):
背景应该是全黑的,但是有一个奇怪的未知黄色视图困扰着动画...
进一步的调查导致发现,只有当我以前使用CircularCustomTransition时,才会出现黄色的损坏视图(请参见下面的代码-originally taken from here)。
我发现,即使长时间使用此CircularCustomTransition的ViewController被解雇(并且内存不足),任何即将到来的Transition仍然会损坏。 (即,运行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!")
}
}
答案 0 :(得分:0)
我终于找到了解决方法:
如果将以下两行添加到CircularTransition中,则可以正常使用!!!
// SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION SOLUTION
// THE FOLLOWING TWO LINES HAVE BEEN ADDED !!!!
backgroundView.removeFromSuperview()
snapshot.removeFromSuperview()
(请在最下面显示的CircularTransition完整代码中找到另外两行...)
以下是将这两行添加到CircularTransition代码后的所有转换视频。您会看到所有黄色视图都按预期消失了!!
这是最终的TransitionTransition代码: (可以在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)
}
}