在 UIPercentDrivenInteractiveTransition 中缓慢平移会导致故障
Posted
技术标签:
【中文标题】在 UIPercentDrivenInteractiveTransition 中缓慢平移会导致故障【英文标题】:Slowly panning in UIPercentDrivenInteractiveTransition results in glitch 【发布时间】:2018-01-12 10:26:10 【问题描述】:在我的应用程序中,我使用由平移手势触发的 UIPercentDrivenInteractiveTransition 来关闭 viewController。我希望我的 viewController 在平移时被拖到右侧。然而,当我慢慢平移时,我遇到了一个小故障:viewController 快速地从左向右跳了一下。 下面是 transition 的代码:
class FilterHideTransition: UIPercentDrivenInteractiveTransition
let viewController: FilterViewController
var enabled = false
private let panGesture = UIPanGestureRecognizer()
private let tapGesture = UITapGestureRecognizer()
init(viewController: FilterViewController)
self.viewController = viewController
super.init()
panGesture.addTarget(self, action: #selector(didPan(with:)))
panGesture.cancelsTouchesInView = false
panGesture.delegate = self
tapGesture.addTarget(self, action: #selector(didTap(with:)))
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self
viewController.view.addGestureRecognizer(panGesture)
viewController.view.addGestureRecognizer(tapGesture)
//MARK: - Actions
private extension FilterHideTransition
@objc func didPan(with recognizer: UIPanGestureRecognizer)
let translation = recognizer.translation(in: viewController.view)
let percentage = translation.x / viewController.view.frame.size.width
print(percentage)
switch recognizer.state
case .began:
enabled = true
viewController.dismiss(animated: true, completion: nil)
break
case .changed:
update(percentage)
break
case .ended:
completionSpeed = 0.3
if percentage > 0.5
finish()
else
cancel()
enabled = false
break
case .cancelled:
cancel()
enabled = false
break
default:
cancel()
enabled = false
break
@objc func didTap(with recognizer: UITapGestureRecognizer)
viewController.dismiss(animated: true, completion: nil)
func isTouch(touch: UITouch, in view: UIView) -> Bool
let touchPoint = touch.location(in: view)
return view.hitTest(touchPoint, with: nil) != nil
extension FilterHideTransition: UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
if gestureRecognizer == tapGesture
return !isTouch(touch: touch, in: viewController.panel)
else if gestureRecognizer == panGesture
return !isTouch(touch: touch, in: viewController.heightSlider) &&
!isTouch(touch: touch, in: viewController.widthSlider) &&
!isTouch(touch: touch, in: viewController.priceSlider)
else
return true
这是 animator 的代码:
class FilterHideAnimator: NSObject, UIViewControllerAnimatedTransitioning
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
return 0.25
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? OverlayTabBarController
else return
let startFrame = fromVC.view.frame
let endFrame = CGRect(x: startFrame.size.width, y: 0, width: startFrame.size.width, height: startFrame.size.height)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0.0,
options: .curveEaseIn,
animations:
fromVC.view.frame = endFrame
toVC.overlay.alpha = 0
,
completion:
_ in
if transitionContext.transitionWasCancelled
transitionContext.completeTransition(false)
else
transitionContext.completeTransition(true)
)
我的问题:如何防止这种故障发生?
【问题讨论】:
你能添加动画实现吗? 好的,来了! -> 添加 你有没有想过这个问题? UIPercentDrivenInteractiveTransition 刚刚坏了吗? @AlexMedearis 当我发现 UIViewPropertyAnimator 对我有用时,我不再关心了。这实际上与 UIPercentDrivenInteractiveTransition 一起工作得很好,所以绝对没有坏。我猜 UIView.Animate 在交互中使用时并不是那么好。 【参考方案1】:我测试了你的最小工作示例,同样的问题再次出现。我无法使用 UIView.animate
API 修复它,但如果您使用 UIViewPropertyAnimator
,问题不会出现 - 唯一的缺点是 UIViewPropertyAnimator
仅适用于 ios 10+。
iOS 10+ 解决方案
首先重构 HideAnimator
以实现 interruptibleAnimator(using:)
以返回执行转换动画器的 UIViewPropertyAnimator
对象(请注意,根据文档,我们应该为正在进行的转换返回相同的动画器对象):
class HideAnimator: NSObject, UIViewControllerAnimatedTransitioning
fileprivate var propertyAnimator: UIViewPropertyAnimator?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
return 0.25
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
// use animator to implement animateTransition
let animator = interruptibleAnimator(using: transitionContext)
animator.startAnimation()
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating
// as per documentation, we need to return existing animator
// for ongoing transition
if let propertyAnimator = propertyAnimator
return propertyAnimator
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
else fatalError()
let startFrame = fromVC.view.frame
let endFrame = CGRect(x: startFrame.size.width, y: 0, width: startFrame.size.width, height: startFrame.size.height)
let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), timingParameters: UICubicTimingParameters(animationCurve: .easeInOut))
animator.addAnimations
fromVC.view.frame = endFrame
animator.addCompletion (_) in
if transitionContext.transitionWasCancelled
transitionContext.completeTransition(false)
else
transitionContext.completeTransition(true)
// reset animator because the current transition ended
self.propertyAnimator = nil
self.propertyAnimator = animator
return animator
最后一件事让它工作,在didPan(with:)
删除以下行:
completionSpeed = 0.3
这将使用默认速度(即1.0
,或者您可以明确设置)。使用interruptibleAnimator(using:)
时,完成速度会根据动画师的fractionComplete
自动计算。
【讨论】:
感谢您的回答,但不幸的是,这两个建议都没有运气:( @Menno 你能提供一个最小的工作示例吗? 好的,今天晚些时候会确保有一个。 @Menno 您针对哪些 iOS 版本? iOS10+方案可以吗? Nosál 我的目标是 11,但 10 也可能有同样的问题。【参考方案2】:所以问题实际上是当您启动交互式过渡时,动画会尝试完整运行。如果您在手势更改状态下设置断点,您可以看到整个动画运行,当您恢复时,它会重新开始跟踪您的手指。我尝试了很多技巧,将交互式过渡的进度设置为 0,但似乎没有任何效果。
解决方案包括在过渡期间将过渡上下文的容器视图的层速度设置为 0,并在过渡准备完成时将其设置回 1。我将这段代码抽象为UIPercentDrivenInteractiveTransition
的一个简单子类。代码如下所示:
@implementation InteractiveTransition
id<UIViewControllerContextTransitioning> _transitionContext;
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext
_transitionContext = transitionContext;
[_transitionContext containerView].layer.speed = 0;
[super startInteractiveTransition:transitionContext];
- (void)finishInteractiveTransition
[_transitionContext containerView].layer.speed = 1;
[super finishInteractiveTransition];
- (void)cancelInteractiveTransition
[_transitionContext containerView].layer.speed = 1;
[super cancelInteractiveTransition];
@end
这将暂停动画,直到您准备好完成或取消交互式过渡。
【讨论】:
恕我直言,这看起来比上面的解决方案更干净。我刚刚测试过,我工作正常。谢谢!以上是关于在 UIPercentDrivenInteractiveTransition 中缓慢平移会导致故障的主要内容,如果未能解决你的问题,请参考以下文章
在 React 应用程序中在哪里转换数据 - 在 Express 中还是在前端使用 React?