UIPercentDrivenInteractiveTransition 无法通过快速手势完成动画
UIPercentDrivenInteractiveTransition doesn't get to animation's completion on fast gesture
我创建了一个交互式过渡。我的 func animateTransition(transitionContext: UIViewControllerContextTransitioning)
很正常,我得到容器 UIView
,我添加两个 UIViewControllers
然后我在 UIView.animateWithDuration(duration, animations, completion)
中做动画变化。
我在 UIViewController
中添加了一个 UIScreenEdgePanGestureRecognizer
。它运作良好,除非我快速平底锅。
在最后一个场景中,应用程序没有响应,仍然在相同的 UIViewController
(转换似乎没有工作)但后台任务 运行。当我 运行 和 Debug View Hierarchy
时,我看到新的 UIViewController
而不是前一个,而前一个(至少它的 UIView
)站在它应该站在的地方在过渡结束时。
我做了一些打印和检查点,据此我可以说当问题发生时,动画完成(我的 animateTransition
方法中的那个) 未达到,所以我无法调用transitionContext.completeTransition
方法来完成或不完成转换。
我也看到平底锅有时从 UIGestureRecognizerState.Began
直接到 UIGestureRecognizerState.Ended
而没有经过 UIGestureRecognizerState.Changed
。
当它经过 UIGestureRecognizerState.Changed
时, translation
和 velocity
都会留下来每个 UIGestureRecognizerState.Changed
个州都一样。
编辑:
代码如下:
animateTransition
方法
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
let containerView = transitionContext.containerView()
let screens: (from: UIViewController, to: UIViewController) = (transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!, transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!)
let parentViewController = presenting ? screens.from : screens.to
let childViewController = presenting ? screens.to : screens.from
let parentView = parentViewController.view
let childView = childViewController.view
// positionning the "to" viewController's view for the animation
if presenting {
offStageChildViewController(childView)
}
containerView.addSubview(parentView)
containerView.addSubview(childView)
let duration = transitionDuration(transitionContext)
UIView.animateWithDuration(duration, animations: {
if self.presenting {
self.onStageViewController(childView)
self.offStageParentViewController(parentView)
} else {
self.onStageViewController(parentView)
self.offStageChildViewController(childView)
}}, completion: { finished in
if transitionContext.transitionWasCancelled() {
transitionContext.completeTransition(false)
} else {
transitionContext.completeTransition(true)
}
})
}
手势和手势处理程序:
weak var fromViewController: UIViewController! {
didSet {
let screenEdgePanRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: "presentingViewController:")
screenEdgePanRecognizer.edges = edge
fromViewController.view.addGestureRecognizer(screenEdgePanRecognizer)
}
}
func presentingViewController(pan: UIPanGestureRecognizer) {
let percentage = getPercentage(pan)
switch pan.state {
case UIGestureRecognizerState.Began:
interactive = true
presentViewController(pan)
case UIGestureRecognizerState.Changed:
updateInteractiveTransition(percentage)
case UIGestureRecognizerState.Ended:
interactive = false
if finishPresenting(pan, percentage: percentage) {
finishInteractiveTransition()
} else {
cancelInteractiveTransition()
}
default:
break
}
}
知道会发生什么吗?
编辑 2:
以下是未公开的方法:
override func getPercentage(pan: UIPanGestureRecognizer) -> CGFloat {
let translation = pan.translationInView(pan.view!)
return abs(translation.x / pan.view!.bounds.width)
}
override func onStageViewController(view: UIView) {
view.transform = CGAffineTransformIdentity
}
override func offStageParentViewController(view: UIView) {
view.transform = CGAffineTransformMakeTranslation(-view.bounds.width / 2, 0)
}
override func offStageChildViewController(view: UIView) {
view.transform = CGAffineTransformMakeTranslation(view.bounds.width, 0)
}
override func presentViewController(pan: UIPanGestureRecognizer) {
let location = pan.locationInView((fromViewController as! MainViewController).tableView)
let indexPath = (fromViewController as! MainViewController).tableView.indexPathForRowAtPoint(location)
if indexPath == nil {
pan.state = .Failed
return
}
fromViewController.performSegueWithIdentifier("chartSegue", sender: pan)
}
- 我删除了 "over" 添加行 => 没有修复它
- 我在
.Began
和 .Ended
中都添加了 updateInteractiveTransition
=> 没有修复它
- 我在
toViewController
的视图层上打开了 shouldRasterize 并一直打开它 => 没有修复它
But the question is why, when doing a fast interactive gesture, is it not responding quickly enough
只要我的手指离开足够长的时间,它实际上可以使用快速交互手势。例如,如果我在超过(比方说)1 厘米的范围内非常快速地平移,没关系。如果我在小于 1 厘米的小表面(再说一遍)上快速平移是不行的
Possible candidates include the views being animated are too complicated (or have complicated effects like shading)
我也想过一个复杂的观点,但我不认为我的观点真的很复杂。有一堆按钮和标签,一个自定义 UIControl
作为分段,一个图表(一旦控制器出现就加载)和一个 xib 在 viewController.[=41= 中加载]
好的,我刚刚用 MINIMUM 类 和对象创建了一个 project 来触发问题。因此,要触发它,您只需从右向左快速而短暂地滑动即可。
我注意到它第一次工作起来很容易,但是如果你第一次正常拖动视图控制器,那么触发它会变得更加困难(甚至不可能?)。在我的整个项目中,这并不重要。
当我诊断这个问题时,我注意到手势的变化和结束状态事件发生在animateTransition
甚至运行之前。所以动画还没开始就canceled/finished了!
我尝试使用 GCD 动画同步队列来确保 UIPercentDrivenInterativeTransition
的更新不会在 `animate:
之后发生
private let animationSynchronizationQueue = dispatch_queue_create("com.domain.app.animationsynchronization", DISPATCH_QUEUE_SERIAL)
然后我有了一个实用方法来使用这个队列:
func dispatchToMainFromSynchronizationQueue(block: dispatch_block_t) {
dispatch_async(animationSynchronizationQueue) {
dispatch_sync(dispatch_get_main_queue(), block)
}
}
然后我的手势处理程序确保更改和结束状态通过该队列路由:
func handlePan(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .Began:
dispatch_suspend(animationSynchronizationQueue)
fromViewController.performSegueWithIdentifier("segueID", sender: gesture)
case .Changed:
dispatchToMainFromSynchronizationQueue() {
self.updateInteractiveTransition(percentage)
}
case .Ended:
dispatchToMainFromSynchronizationQueue() {
if isOkToFinish {
self.finishInteractiveTransition()
} else {
self.cancelInteractiveTransition()
}
}
default:
break
}
}
因此,我让手势识别器的 .Began
状态暂停该队列,并且我让动画控制器在 animationTransition
中恢复该队列(确保队列仅在该方法运行之前再次启动手势继续尝试更新 UIPercentDrivenInteractiveTransition
对象。
有同样的问题,尝试使用serialQueue.suspend()/resume(),没有用。
这个问题是因为pan手势太快时,结束状态早于animateTransition开始,然后context.completeTransition得不到运行,整个动画就乱了。
当这种情况发生时,我的解决方案是强制 运行 context.completeTransition。
比如我有两个类:
class SwipeInteractor: UIPercentDrivenInteractiveTransition {
var interactionInProgress = false
...
}
class AnimationController: UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if !swipeInteractor.interactionInProgress {
DispatchQueue.main.asyncAfter(deadline: .now()+transitionDuration) {
if context.transitionWasCancelled {
toView?.removeFromSuperview()
} else {
fromView?.removeFromSuperview()
}
context.completeTransition(!context.transitionWasCancelled)
}
}
...
}
...
}
interactionInProgress 在手势开始时设置为 true,在手势结束时设置为 false。
我遇到了类似的问题,但是程序化动画触发器没有触发动画完成块。我的解决方案类似于 Sam 的解决方案,除了不是在稍有延迟后进行调度,而是在 UIPercentDrivenInteractiveTransition
实例上手动调用 finish
。
class SwipeInteractor: UIPercentDrivenInteractiveTransition {
var interactionInProgress = false
...
}
class AnimationController: UIViewControllerAnimatedTransitioning {
private var swipeInteractor: SwipeInteractor
..
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
...
if !swipeInteractor.interactionInProgress {
swipeInteractor.finish()
}
...
UIView.animateWithDuration(...)
}
}
我创建了一个交互式过渡。我的 func animateTransition(transitionContext: UIViewControllerContextTransitioning)
很正常,我得到容器 UIView
,我添加两个 UIViewControllers
然后我在 UIView.animateWithDuration(duration, animations, completion)
中做动画变化。
我在 UIViewController
中添加了一个 UIScreenEdgePanGestureRecognizer
。它运作良好,除非我快速平底锅。
在最后一个场景中,应用程序没有响应,仍然在相同的 UIViewController
(转换似乎没有工作)但后台任务 运行。当我 运行 和 Debug View Hierarchy
时,我看到新的 UIViewController
而不是前一个,而前一个(至少它的 UIView
)站在它应该站在的地方在过渡结束时。
我做了一些打印和检查点,据此我可以说当问题发生时,动画完成(我的 animateTransition
方法中的那个) 未达到,所以我无法调用transitionContext.completeTransition
方法来完成或不完成转换。
我也看到平底锅有时从 UIGestureRecognizerState.Began
直接到 UIGestureRecognizerState.Ended
而没有经过 UIGestureRecognizerState.Changed
。
当它经过 UIGestureRecognizerState.Changed
时, translation
和 velocity
都会留下来每个 UIGestureRecognizerState.Changed
个州都一样。
编辑:
代码如下:
animateTransition
方法
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
let containerView = transitionContext.containerView()
let screens: (from: UIViewController, to: UIViewController) = (transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!, transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!)
let parentViewController = presenting ? screens.from : screens.to
let childViewController = presenting ? screens.to : screens.from
let parentView = parentViewController.view
let childView = childViewController.view
// positionning the "to" viewController's view for the animation
if presenting {
offStageChildViewController(childView)
}
containerView.addSubview(parentView)
containerView.addSubview(childView)
let duration = transitionDuration(transitionContext)
UIView.animateWithDuration(duration, animations: {
if self.presenting {
self.onStageViewController(childView)
self.offStageParentViewController(parentView)
} else {
self.onStageViewController(parentView)
self.offStageChildViewController(childView)
}}, completion: { finished in
if transitionContext.transitionWasCancelled() {
transitionContext.completeTransition(false)
} else {
transitionContext.completeTransition(true)
}
})
}
手势和手势处理程序:
weak var fromViewController: UIViewController! {
didSet {
let screenEdgePanRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: "presentingViewController:")
screenEdgePanRecognizer.edges = edge
fromViewController.view.addGestureRecognizer(screenEdgePanRecognizer)
}
}
func presentingViewController(pan: UIPanGestureRecognizer) {
let percentage = getPercentage(pan)
switch pan.state {
case UIGestureRecognizerState.Began:
interactive = true
presentViewController(pan)
case UIGestureRecognizerState.Changed:
updateInteractiveTransition(percentage)
case UIGestureRecognizerState.Ended:
interactive = false
if finishPresenting(pan, percentage: percentage) {
finishInteractiveTransition()
} else {
cancelInteractiveTransition()
}
default:
break
}
}
知道会发生什么吗?
编辑 2:
以下是未公开的方法:
override func getPercentage(pan: UIPanGestureRecognizer) -> CGFloat {
let translation = pan.translationInView(pan.view!)
return abs(translation.x / pan.view!.bounds.width)
}
override func onStageViewController(view: UIView) {
view.transform = CGAffineTransformIdentity
}
override func offStageParentViewController(view: UIView) {
view.transform = CGAffineTransformMakeTranslation(-view.bounds.width / 2, 0)
}
override func offStageChildViewController(view: UIView) {
view.transform = CGAffineTransformMakeTranslation(view.bounds.width, 0)
}
override func presentViewController(pan: UIPanGestureRecognizer) {
let location = pan.locationInView((fromViewController as! MainViewController).tableView)
let indexPath = (fromViewController as! MainViewController).tableView.indexPathForRowAtPoint(location)
if indexPath == nil {
pan.state = .Failed
return
}
fromViewController.performSegueWithIdentifier("chartSegue", sender: pan)
}
- 我删除了 "over" 添加行 => 没有修复它
- 我在
.Began
和.Ended
中都添加了updateInteractiveTransition
=> 没有修复它 - 我在
toViewController
的视图层上打开了 shouldRasterize 并一直打开它 => 没有修复它
But the question is why, when doing a fast interactive gesture, is it not responding quickly enough
只要我的手指离开足够长的时间,它实际上可以使用快速交互手势。例如,如果我在超过(比方说)1 厘米的范围内非常快速地平移,没关系。如果我在小于 1 厘米的小表面(再说一遍)上快速平移是不行的
Possible candidates include the views being animated are too complicated (or have complicated effects like shading)
我也想过一个复杂的观点,但我不认为我的观点真的很复杂。有一堆按钮和标签,一个自定义 UIControl
作为分段,一个图表(一旦控制器出现就加载)和一个 xib 在 viewController.[=41= 中加载]
好的,我刚刚用 MINIMUM 类 和对象创建了一个 project 来触发问题。因此,要触发它,您只需从右向左快速而短暂地滑动即可。
我注意到它第一次工作起来很容易,但是如果你第一次正常拖动视图控制器,那么触发它会变得更加困难(甚至不可能?)。在我的整个项目中,这并不重要。
当我诊断这个问题时,我注意到手势的变化和结束状态事件发生在animateTransition
甚至运行之前。所以动画还没开始就canceled/finished了!
我尝试使用 GCD 动画同步队列来确保 UIPercentDrivenInterativeTransition
的更新不会在 `animate:
private let animationSynchronizationQueue = dispatch_queue_create("com.domain.app.animationsynchronization", DISPATCH_QUEUE_SERIAL)
然后我有了一个实用方法来使用这个队列:
func dispatchToMainFromSynchronizationQueue(block: dispatch_block_t) {
dispatch_async(animationSynchronizationQueue) {
dispatch_sync(dispatch_get_main_queue(), block)
}
}
然后我的手势处理程序确保更改和结束状态通过该队列路由:
func handlePan(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .Began:
dispatch_suspend(animationSynchronizationQueue)
fromViewController.performSegueWithIdentifier("segueID", sender: gesture)
case .Changed:
dispatchToMainFromSynchronizationQueue() {
self.updateInteractiveTransition(percentage)
}
case .Ended:
dispatchToMainFromSynchronizationQueue() {
if isOkToFinish {
self.finishInteractiveTransition()
} else {
self.cancelInteractiveTransition()
}
}
default:
break
}
}
因此,我让手势识别器的 .Began
状态暂停该队列,并且我让动画控制器在 animationTransition
中恢复该队列(确保队列仅在该方法运行之前再次启动手势继续尝试更新 UIPercentDrivenInteractiveTransition
对象。
有同样的问题,尝试使用serialQueue.suspend()/resume(),没有用。
这个问题是因为pan手势太快时,结束状态早于animateTransition开始,然后context.completeTransition得不到运行,整个动画就乱了。
当这种情况发生时,我的解决方案是强制 运行 context.completeTransition。
比如我有两个类:
class SwipeInteractor: UIPercentDrivenInteractiveTransition {
var interactionInProgress = false
...
}
class AnimationController: UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if !swipeInteractor.interactionInProgress {
DispatchQueue.main.asyncAfter(deadline: .now()+transitionDuration) {
if context.transitionWasCancelled {
toView?.removeFromSuperview()
} else {
fromView?.removeFromSuperview()
}
context.completeTransition(!context.transitionWasCancelled)
}
}
...
}
...
}
interactionInProgress 在手势开始时设置为 true,在手势结束时设置为 false。
我遇到了类似的问题,但是程序化动画触发器没有触发动画完成块。我的解决方案类似于 Sam 的解决方案,除了不是在稍有延迟后进行调度,而是在 UIPercentDrivenInteractiveTransition
实例上手动调用 finish
。
class SwipeInteractor: UIPercentDrivenInteractiveTransition {
var interactionInProgress = false
...
}
class AnimationController: UIViewControllerAnimatedTransitioning {
private var swipeInteractor: SwipeInteractor
..
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
...
if !swipeInteractor.interactionInProgress {
swipeInteractor.finish()
}
...
UIView.animateWithDuration(...)
}
}