Skip to content

Commit

Permalink
Update library
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexNsbmr committed Sep 23, 2020
1 parent 4d7a4e3 commit 111808c
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 9 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# QQPercentDrivenInteractiveTransition

A description of this package.
`QQPercentDrivenInteractiveTransition` is a drop-in replacement for `UIPercentDrivenInteractiveTransition` for use in custom container view controllers.

Why do you need it? Because Apples own `UIPercentDrivenInteractiveTransition` calls undocumented methods on your custom `UIViewControllerContextTransitioning` objects.

Note that this class can be used with UIKits standard container view controllers such as `UINavigationController`, `UITabBarController` and also for presenting modal view controllers.

Original file line number Diff line number Diff line change
@@ -1,3 +1,135 @@
struct QQPercentDrivenInteractiveTransition {
var text = "Hello, World!"
//
// QQPercentDrivenInteractiveTransition.swift
// QoQa
//
// Created by Alexandre on 23.09.20.
// Copyright © 2020 QoQa Services SA. All rights reserved.
//

import UIKit

open class QQPercentDrivenInteractiveTransition: NSObject {

// MARK: - Init
public required init(animator: UIViewControllerAnimatedTransitioning) {
self.animator = animator
}

// MARK: - Setters and getters
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?

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 QQPercentDrivenInteractiveTransition: 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 QQPercentDrivenInteractiveTransition {

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: .main, forMode: .common)
}

@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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ import XCTest
@testable import QQPercentDrivenInteractiveTransition

final class QQPercentDrivenInteractiveTransitionTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(QQPercentDrivenInteractiveTransition().text, "Hello, World!")

func testInstantiation() {
class CustomAnimator: NSObject, UIViewControllerAnimatedTransitioning {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.18
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {}
}
let transition = QQPercentDrivenInteractiveTransition(animator: CustomAnimator())

XCTAssertEqual(transition.duration, 0.18)
XCTAssertEqual(transition.percentComplete, 0)
}

static var allTests = [
("testExample", testExample),
("testInstantiation", testInstantiation),
]
}

0 comments on commit 111808c

Please sign in to comment.