Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save valeriomazzeo/336a7aee62305d701fbeb231d620a214 to your computer and use it in GitHub Desktop.
Save valeriomazzeo/336a7aee62305d701fbeb231d620a214 to your computer and use it in GitHub Desktop.
import UIKit
open class PercentDrivenInteractiveTransition: NSObject {
// MARK: Initialization
public required init(animator: UIViewControllerAnimatedTransitioning) {
self.animator = animator
}
// MARK: Setting and Getting Attributes
public let animator: UIViewControllerAnimatedTransitioning
public var completionSpeed: CGFloat = 1.0
public var duration: TimeInterval {
return self.animator.transitionDuration(using: self.transitionContext)
}
public fileprivate(set) var percentComplete: CGFloat = 0.0
// MARK: Private Helpers
fileprivate weak var transitionContext: UIViewControllerContextTransitioning? = nil
fileprivate var animationDisplayLink: CADisplayLink? = nil {
willSet {
guard self.animationDisplayLink != newValue else {
return
}
self.animationDisplayLink?.invalidate()
}
}
// MARK: Finalization
deinit {
self.animationDisplayLink = nil
}
}
// MARK: - Managing the Interactive Transition
extension PercentDrivenInteractiveTransition: UIViewControllerInteractiveTransitioning {
public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
guard self.transitionContext == nil else {
return
}
self.transitionContext = transitionContext
self.transitionContainerLayer?.speed = 0.0
self.animator.animateTransition(using: transitionContext)
}
open func update(_ percentComplete: CGFloat) {
self.percentComplete = min(1.0, max(0.0, percentComplete))
self.transitionContainerLayer?.timeOffset = TimeInterval(self.percentComplete) * self.duration
self.transitionContext?.updateInteractiveTransition(self.percentComplete)
}
open func cancel() {
self.transitionContext?.cancelInteractiveTransition()
self.completeTransition()
}
open func finish() {
self.transitionContext?.finishInteractiveTransition()
self.completeTransition()
}
}
// MARK: - Managing Animator Progressive Transition
extension PercentDrivenInteractiveTransition {
fileprivate var transitionContainerLayer: CALayer? {
return self.transitionContext?.containerView.layer
}
fileprivate func completeTransition() {
guard self.animationDisplayLink == nil else {
return
}
self.animationDisplayLink = CADisplayLink(target: self, selector: #selector(completeTransitionUpdate))
self.animationDisplayLink?.add(to: RunLoop.main, forMode: .commonModes)
}
@objc fileprivate func completeTransitionUpdate(sender: CADisplayLink) {
guard let transitionContext = self.transitionContext, let layer = self.transitionContainerLayer else {
return
}
var deltaTimeOffset = sender.duration * TimeInterval(self.completionSpeed)
deltaTimeOffset *= transitionContext.transitionWasCancelled ? -1.0 : 1.0
let timeOffset = layer.timeOffset + deltaTimeOffset
if timeOffset < 0.0 || timeOffset > self.duration {
self.completeTransitionDidFinish()
} else {
layer.timeOffset = timeOffset
}
}
private func completeTransitionDidFinish() {
self.animationDisplayLink = nil
guard let transitionContext = self.transitionContext, let layer = self.transitionContainerLayer else {
return
}
layer.speed = 1.0
if !transitionContext.transitionWasCancelled {
let pausedTime = layer.timeOffset
layer.timeOffset = 0.0
layer.beginTime = 0.0 // Need to reset to zero to avoid flickering
let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
layer.beginTime = timeSincePause
}
self.transitionContext = nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment