Commit fcf3d822 by Daniel Dahan

initial pass at MotionController rework

parent 19664a2f
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
96AEB6A31EE4610F009A3BE0 /* MotionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */; }; 96AEB6A31EE4610F009A3BE0 /* MotionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */; };
96AEB6A41EE4610F009A3BE0 /* MotionStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */; }; 96AEB6A41EE4610F009A3BE0 /* MotionStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */; };
96AEB6A51EE4610F009A3BE0 /* MotionTargetState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */; }; 96AEB6A51EE4610F009A3BE0 /* MotionTargetState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */; };
96AEB6A61EE4610F009A3BE0 /* MotionTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6801EE4610F009A3BE0 /* MotionTypes.swift */; }; 96AEB6A61EE4610F009A3BE0 /* MotionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6801EE4610F009A3BE0 /* MotionViewController.swift */; };
96AEB6A71EE4610F009A3BE0 /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6821EE4610F009A3BE0 /* Lexer.swift */; }; 96AEB6A71EE4610F009A3BE0 /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6821EE4610F009A3BE0 /* Lexer.swift */; };
96AEB6A81EE4610F009A3BE0 /* Nodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6831EE4610F009A3BE0 /* Nodes.swift */; }; 96AEB6A81EE4610F009A3BE0 /* Nodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6831EE4610F009A3BE0 /* Nodes.swift */; };
96AEB6A91EE4610F009A3BE0 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6841EE4610F009A3BE0 /* Parser.swift */; }; 96AEB6A91EE4610F009A3BE0 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6841EE4610F009A3BE0 /* Parser.swift */; };
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
96AEB6AE1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68A1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift */; }; 96AEB6AE1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68A1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift */; };
96AEB6AF1EE4610F009A3BE0 /* MatchPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68B1EE4610F009A3BE0 /* MatchPreprocessor.swift */; }; 96AEB6AF1EE4610F009A3BE0 /* MatchPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68B1EE4610F009A3BE0 /* MatchPreprocessor.swift */; };
96AEB6B01EE4610F009A3BE0 /* SourcePreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68C1EE4610F009A3BE0 /* SourcePreprocessor.swift */; }; 96AEB6B01EE4610F009A3BE0 /* SourcePreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68C1EE4610F009A3BE0 /* SourcePreprocessor.swift */; };
96E49A401EEA08F8006D5A93 /* MotionTransitionObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E49A3F1EEA08F8006D5A93 /* MotionTransitionObserver.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
...@@ -80,7 +81,7 @@ ...@@ -80,7 +81,7 @@
96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionPlugin.swift; sourceTree = "<group>"; }; 96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionPlugin.swift; sourceTree = "<group>"; };
96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionStringConvertible.swift; sourceTree = "<group>"; }; 96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionStringConvertible.swift; sourceTree = "<group>"; };
96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTargetState.swift; sourceTree = "<group>"; }; 96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTargetState.swift; sourceTree = "<group>"; };
96AEB6801EE4610F009A3BE0 /* MotionTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTypes.swift; sourceTree = "<group>"; }; 96AEB6801EE4610F009A3BE0 /* MotionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionViewController.swift; sourceTree = "<group>"; };
96AEB6821EE4610F009A3BE0 /* Lexer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lexer.swift; sourceTree = "<group>"; }; 96AEB6821EE4610F009A3BE0 /* Lexer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lexer.swift; sourceTree = "<group>"; };
96AEB6831EE4610F009A3BE0 /* Nodes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Nodes.swift; sourceTree = "<group>"; }; 96AEB6831EE4610F009A3BE0 /* Nodes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Nodes.swift; sourceTree = "<group>"; };
96AEB6841EE4610F009A3BE0 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; }; 96AEB6841EE4610F009A3BE0 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
...@@ -95,6 +96,7 @@ ...@@ -95,6 +96,7 @@
96C98DDD1E424B9000B22906 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 96C98DDD1E424B9000B22906 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
96C98DE21E43809D00B22906 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; }; 96C98DE21E43809D00B22906 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
96C98DED1E438A5700B22906 /* Motion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Motion.h; sourceTree = "<group>"; }; 96C98DED1E438A5700B22906 /* Motion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Motion.h; sourceTree = "<group>"; };
96E49A3F1EEA08F8006D5A93 /* MotionTransitionObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTransitionObserver.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
...@@ -207,7 +209,8 @@ ...@@ -207,7 +209,8 @@
96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */, 96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */,
96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */, 96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */,
96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */, 96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */,
96AEB6801EE4610F009A3BE0 /* MotionTypes.swift */, 96AEB6801EE4610F009A3BE0 /* MotionViewController.swift */,
96E49A3F1EEA08F8006D5A93 /* MotionTransitionObserver.swift */,
96AEB6811EE4610F009A3BE0 /* Parser */, 96AEB6811EE4610F009A3BE0 /* Parser */,
96AEB6861EE4610F009A3BE0 /* Preprocessors */, 96AEB6861EE4610F009A3BE0 /* Preprocessors */,
); );
...@@ -308,9 +311,10 @@ ...@@ -308,9 +311,10 @@
96AEB6961EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift in Sources */, 96AEB6961EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift in Sources */,
96AEB6941EE4610F009A3BE0 /* Motion+Array.swift in Sources */, 96AEB6941EE4610F009A3BE0 /* Motion+Array.swift in Sources */,
96AEB6951EE4610F009A3BE0 /* Motion+CALayer.swift in Sources */, 96AEB6951EE4610F009A3BE0 /* Motion+CALayer.swift in Sources */,
96AEB6A61EE4610F009A3BE0 /* MotionTypes.swift in Sources */, 96AEB6A61EE4610F009A3BE0 /* MotionViewController.swift in Sources */,
96AEB68E1EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift in Sources */, 96AEB68E1EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift in Sources */,
96AEB6921EE4610F009A3BE0 /* MotionDebugView.swift in Sources */, 96AEB6921EE4610F009A3BE0 /* MotionDebugView.swift in Sources */,
96E49A401EEA08F8006D5A93 /* MotionTransitionObserver.swift in Sources */,
96AEB6A01EE4610F009A3BE0 /* MotionIndependentController.swift in Sources */, 96AEB6A01EE4610F009A3BE0 /* MotionIndependentController.swift in Sources */,
96AEB6AA1EE4610F009A3BE0 /* Regex.swift in Sources */, 96AEB6AA1EE4610F009A3BE0 /* Regex.swift in Sources */,
96AEB6901EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift in Sources */, 96AEB6901EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift in Sources */,
......
...@@ -58,11 +58,13 @@ public class Motion: MotionController { ...@@ -58,11 +58,13 @@ public class Motion: MotionController {
public internal(set) var isPresenting = true public internal(set) var isPresenting = true
/// Progress of the current transition, 0 if a transition is not happening. /// Progress of the current transition, 0 if a transition is not happening.
public override var progress: Double { public override var elapsedTime: TimeInterval {
didSet { didSet {
if isTransitioning { guard isTransitioning else {
transitionContext?.updateInteractiveTransition(CGFloat(progress)) return
} }
transitionContext?.updateInteractiveTransition(CGFloat(elapsedTime))
} }
} }
...@@ -155,7 +157,7 @@ public extension Motion { ...@@ -155,7 +157,7 @@ public extension Motion {
} }
} }
internal extension Motion { fileprivate extension Motion {
/// Starts the transition animation. /// Starts the transition animation.
func start() { func start() {
guard isTransitioning else { guard isTransitioning else {
...@@ -165,51 +167,25 @@ internal extension Motion { ...@@ -165,51 +167,25 @@ internal extension Motion {
prepareViewControllers() prepareViewControllers()
prepareSnapshotView() prepareSnapshotView()
prepareForTransition() prepareForTransition()
prepareMotionContext() prepareContext()
prepareToView() prepareToView()
prepareViewHierarchy() prepareViewHierarchy()
processContext() processContext()
prepareForAnimation() prepareForAnimation()
processForAnimation()
context.hide(view: toView)
#if os(tvOS)
animate()
#else
if isNavigationController {
// When animating within navigationController, we have to dispatch later into the main queue.
// otherwise snapshots will be pure white. Possibly a bug with UIKit
DispatchQueue.main.async { [weak self] in
self?.animate()
}
} else {
animate()
}
#endif
} }
}
internal extension Motion {
override func animate() { override func animate() {
context.unhide(view: toView) context.unhide(view: toView)
if let containerBackgroundColor = containerBackgroundColor { updateContainerBackgroundColor()
container.backgroundColor = containerBackgroundColor updateInsertOrder()
} else if !toOverFullScreen && !fromOverFullScreen {
container.backgroundColor = toView.backgroundColor
}
if fromOverFullScreen {
insertToViewFirst = true
}
for animator in animators {
if let animator = animator as? MotionHasInsertOrder {
animator.insertToViewFirst = insertToViewFirst
}
}
super.animate() super.animate()
fullScreenSnapshot!.removeFromSuperview()
fullScreenSnapshot?.removeFromSuperview()
} }
override func complete(finished: Bool) { override func complete(finished: Bool) {
...@@ -218,13 +194,13 @@ internal extension Motion { ...@@ -218,13 +194,13 @@ internal extension Motion {
} }
context.clean() context.clean()
if finished && isPresenting && toOverFullScreen { if finished && isPresenting && toOverFullScreen {
// finished presenting a overFullScreen VC // finished presenting a overFullScreen VC
context.unhide(rootView: toView) context.unhide(rootView: toView)
context.removeSnapshots(rootView: toView) context.removeSnapshots(rootView: toView)
context.storeViewAlpha(rootView: fromView) context.storeViewAlpha(rootView: fromView)
fromViewController!.motionStoredSnapshot = container fromViewController!.motionStoredSnapshot = container
fromView.removeFromSuperview() fromView.removeFromSuperview()
fromView.addSubview(container) fromView.addSubview(container)
...@@ -262,7 +238,7 @@ internal extension Motion { ...@@ -262,7 +238,7 @@ internal extension Motion {
let tContext = transitionContext let tContext = transitionContext
let fvc = fromViewController let fvc = fromViewController
let tvc = toViewController let tvc = toViewController
transitionContext = nil transitionContext = nil
fromViewController = nil fromViewController = nil
toViewController = nil toViewController = nil
...@@ -274,7 +250,7 @@ internal extension Motion { ...@@ -274,7 +250,7 @@ internal extension Motion {
defaultAnimation = .auto defaultAnimation = .auto
super.complete(finished: finished) super.complete(finished: finished)
if finished { if finished {
if let fvc = fvc, let tvc = tvc { if let fvc = fvc, let tvc = tvc {
processForMotionDelegate(viewController: fvc) { processForMotionDelegate(viewController: fvc) {
...@@ -344,7 +320,7 @@ fileprivate extension Motion { ...@@ -344,7 +320,7 @@ fileprivate extension Motion {
} }
/// Prepares the MotionContext instance. /// Prepares the MotionContext instance.
func prepareMotionContext() { func prepareContext() {
context.loadViewAlpha(rootView: toView) context.loadViewAlpha(rootView: toView)
context.loadViewAlpha(rootView: fromView) context.loadViewAlpha(rootView: fromView)
container.addSubview(toView) container.addSubview(toView)
...@@ -370,6 +346,52 @@ internal extension Motion { ...@@ -370,6 +346,52 @@ internal extension Motion {
super.prepareForTransition() super.prepareForTransition()
insert(preprocessor: DefaultAnimationPreprocessor(motion: self), before: DurationPreprocessor.self) insert(preprocessor: DefaultAnimationPreprocessor(motion: self), before: DurationPreprocessor.self)
} }
override func prepareForAnimation() {
super.prepareForAnimation()
context.hide(view: toView)
}
}
fileprivate extension Motion {
/// Processes the animations.
func processForAnimation() {
#if os(tvOS)
animate()
#else
if isNavigationController {
// When animating within navigationController, we have to dispatch later into the main queue.
// otherwise snapshots will be pure white. Possibly a bug with UIKit
DispatchQueue.main.async { [weak self] in
self?.animate()
}
} else {
animate()
}
#endif
}
}
fileprivate extension Motion {
/// Updates the container background color.
func updateContainerBackgroundColor() {
if let v = containerBackgroundColor {
container.backgroundColor = v
} else if !toOverFullScreen && !fromOverFullScreen {
container.backgroundColor = toView.backgroundColor
}
}
/// Updates the insertToViewFirst boolean for animators.
func updateInsertOrder() {
if fromOverFullScreen {
insertToViewFirst = true
}
for v in animators {
(v as? MotionHasInsertOrder)?.insertToViewFirst = insertToViewFirst
}
}
} }
internal extension Motion { internal extension Motion {
...@@ -418,99 +440,113 @@ internal extension Motion { ...@@ -418,99 +440,113 @@ internal extension Motion {
} }
} }
// MARK: UIKit Protocol Conformance
/*****************************
* UIKit protocol extensions *
*****************************/
extension Motion: UIViewControllerAnimatedTransitioning { extension Motion: UIViewControllerAnimatedTransitioning {
public func animateTransition(using context: UIViewControllerContextTransitioning) { /**
guard !isTransitioning else { return } The animation method that is used to coordinate the transition.
transitionContext = context - Parameter using transitionContext: A UIViewControllerContextTransitioning.
fromViewController = fromViewController ?? context.viewController(forKey: .from) */
toViewController = toViewController ?? context.viewController(forKey: .to) public func animateTransition(using context: UIViewControllerContextTransitioning) {
transitionContainer = context.containerView guard !isTransitioning else {
start() return
} }
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.375 // doesn't matter, real duration will be calculated later transitionContext = context
} fromViewController = fromViewController ?? context.viewController(forKey: .from)
toViewController = toViewController ?? context.viewController(forKey: .to)
public func animationEnded(_ transitionCompleted: Bool) { transitionContainer = context.containerView
isAnimating = !transitionCompleted
} start()
}
/**
Returns the transition duration time interval.
- Parameter using transitionContext: An optional UIViewControllerContextTransitioning.
- Returns: A TimeInterval that is the total animation time including delays.
*/
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0 // Will be updated dynamically.
}
public func animationEnded(_ transitionCompleted: Bool) {
isAnimating = !transitionCompleted
}
} }
extension Motion:UIViewControllerTransitioningDelegate { extension Motion {
var interactiveTransitioning: UIViewControllerInteractiveTransitioning? { /// A reference to the interactive transitioning.
return forceNonInteractive ? nil : self fileprivate var interactiveTransitioning: UIViewControllerInteractiveTransitioning? {
} return self
}
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { }
self.isPresenting = true
self.fromViewController = fromViewController ?? presenting extension Motion: UIViewControllerTransitioningDelegate {
self.toViewController = toViewController ?? presented public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self self.isPresenting = true
} self.fromViewController = fromViewController ?? presenting
self.toViewController = toViewController ?? presented
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self
self.isPresenting = false }
self.fromViewController = fromViewController ?? dismissed
return self public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
} self.isPresenting = false
self.fromViewController = fromViewController ?? dismissed
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return self
return interactiveTransitioning }
}
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactiveTransitioning
return interactiveTransitioning }
}
public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactiveTransitioning
}
} }
extension Motion: UIViewControllerInteractiveTransitioning { extension Motion: UIViewControllerInteractiveTransitioning {
public var wantsInteractiveStart: Bool { public var wantsInteractiveStart: Bool {
return true return true
} }
public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
animateTransition(using: transitionContext) animateTransition(using: transitionContext)
} }
} }
extension Motion: UINavigationControllerDelegate { extension Motion: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = operation == .push self.isPresenting = .push == operation
self.fromViewController = fromViewController ?? fromVC self.fromViewController = fromViewController ?? fromVC
self.toViewController = toViewController ?? toVC self.toViewController = toViewController ?? toVC
self.isNavigationController = true self.isNavigationController = true
return self return self
} }
public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactiveTransitioning return interactiveTransitioning
} }
} }
extension Motion: UITabBarControllerDelegate { extension Motion: UITabBarControllerDelegate {
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return !isAnimating return !isAnimating
} }
public func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { public func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactiveTransitioning return interactiveTransitioning
} }
public func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { public func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isAnimating = true isAnimating = true
let fromVCIndex = tabBarController.childViewControllers.index(of: fromVC)!
let toVCIndex = tabBarController.childViewControllers.index(of: toVC)! let fromVCIndex = tabBarController.childViewControllers.index(of: fromVC)!
self.isPresenting = toVCIndex > fromVCIndex let toVCIndex = tabBarController.childViewControllers.index(of: toVC)!
self.fromViewController = fromViewController ?? fromVC
self.toViewController = toViewController ?? toVC self.isPresenting = toVCIndex > fromVCIndex
self.isTabBarController = true self.fromViewController = fromViewController ?? fromVC
return self self.toViewController = toViewController ?? toVC
} self.isTabBarController = true
return self
}
} }
public typealias MotionDelayCancelBlock = (Bool) -> Void public typealias MotionDelayCancelBlock = (Bool) -> Void
......
...@@ -28,105 +28,166 @@ ...@@ -28,105 +28,166 @@
import UIKit import UIKit
/// Base class for managing a Motion transition
public class MotionController: NSObject { public class MotionController: NSObject {
// MARK: Properties /// A reference to the MotionContext.
/// context object holding transition informations public internal(set) var context: MotionContext!
public internal(set) var context: MotionContext!
/// whether or not we are handling transition interactively
public var interactive: Bool {
return displayLink == nil
}
/// progress of the current transition. 0 if no transition is happening
public internal(set) var progress: Double = 0 {
didSet {
if isTransitioning {
if let progressUpdateObservers = progressUpdateObservers {
for observer in progressUpdateObservers {
observer.motionDidUpdateProgress(progress: progress)
}
}
let elapsedTime = progress * totalDuration /// A boolean indicating whether the transition interactive or not.
if interactive { public var isInteractive: Bool {
for animator in animators { return nil == displayLink
animator.seek(to: elapsedTime) }
}
} else { /// Progress of the current transition. 0 if no transition is happening.
for plugin in plugins where plugin.requirePerFrameCallback { public internal(set) var elapsedTime: TimeInterval = 0 {
plugin.seek(to: elapsedTime) didSet {
} guard isTransitioning else {
return
}
updateTransitionObservers()
guard isInteractive else {
updatePlugins()
return
}
updateAnimators()
} }
}
} }
}
/// whether or not we are doing a transition /// A boolean indicating whether a transition is active.
public var isTransitioning: Bool { public var isTransitioning: Bool {
return transitionContainer != nil return nil != transitionContainer
} }
/// container we created to hold all animating views, will be a subview of the
/// transitionContainer when isTransitioning
public internal(set) var container: UIView!
/// this is the container supplied by UIKit /**
internal var transitionContainer: UIView! A view container used to hold all the animating views during a
transition.
*/
public internal(set) var container: UIView!
internal var completionCallback: ((Bool) -> Void)? /// UIKit's supplied transition container.
internal var transitionContainer: UIView!
internal var displayLink: CADisplayLink? /// An optional completion callback.
internal var progressUpdateObservers: [MotionProgressUpdateObserver]? internal var completionCallback: ((Bool) -> Void)?
/// Binds the render cycle to the transition animation.
internal var displayLink: CADisplayLink?
/// An Array of observers that are updated during a transition.
internal var transitionObservers: [MotionTransitionObserver]?
/// max duration needed by the default animator and plugins /// Max duration used by MotionAnimators and MotionPlugins.
public internal(set) var totalDuration: TimeInterval = 0.0 public internal(set) var totalDuration: TimeInterval = 0
/// current animation complete duration. /// The currently running animation duration.
/// (differs from totalDuration because this one could be the duration for finishing interactive transition) internal var currentAnimationDuration: TimeInterval = 0
internal var duration: TimeInterval = 0.0
internal var beginTime: TimeInterval? { /// The start time of the animation.
didSet { internal var beginTime: TimeInterval? {
if beginTime != nil { didSet {
if displayLink == nil { guard nil != beginTime else {
displayLink = CADisplayLink(target: self, selector: #selector(displayUpdate(_:))) displayLink?.isPaused = true
displayLink!.add(to: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue)) displayLink?.remove(from: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
} displayLink = nil
} else { return
displayLink?.isPaused = true }
displayLink?.remove(from: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
displayLink = nil guard nil == displayLink else {
} return
} }
}
func displayUpdate(_ link: CADisplayLink) { displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
if isTransitioning, duration > 0, let beginTime = beginTime { displayLink?.add(to: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
let elapsedTime = CACurrentMediaTime() - beginTime
if elapsedTime > duration {
progress = finishing ? 1 : 0
self.beginTime = nil
complete(finished: finishing)
} else {
var completed = elapsedTime / totalDuration
if !finishing {
completed = 1 - completed
} }
completed = max(0, min(1, completed))
progress = completed
}
} }
}
internal var finishing: Bool = true /// A boolean indicating if the transition has finished.
internal var isFinished = true
internal var processors: [MotionPreprocessor]! /// An Array of MotionPreprocessors used during a transition.
internal var animators: [MotionAnimator]! internal var processors: [MotionPreprocessor]!
internal var plugins: [MotionPlugin]!
internal var animatingViews: [(fromViews: [UIView], toViews: [UIView])]! /// An Array of MotionAnimators used during a transition.
internal var animators: [MotionAnimator]!
internal static var enabledPlugins: [MotionPlugin.Type] = [] /// An Array of MotionPlugins used during a transition.
internal var plugins: [MotionPlugin]!
internal override init() {} /// The matching from-views to to-views based on the motionIdentifier value.
internal var transitionPairs: [(fromViews: [UIView], toViews: [UIView])]!
/// Plugins that are enabled during the transition.
internal static var enabledPlugins = [MotionPlugin.Type]()
/// Initializer.
internal override init() {}
}
fileprivate extension MotionController {
/// Updates the transition observers.
func updateTransitionObservers() {
guard let observers = transitionObservers else {
return
}
for v in observers {
v.motion(transitionObserver: v, didUpdateWith: elapsedTime)
}
}
/// Updates the animators.
func updateAnimators() {
let v = elapsedTime * totalDuration
for a in animators {
a.seek(to: v)
}
}
/// Updates the plugins.
func updatePlugins() {
let v = elapsedTime * totalDuration
for p in plugins where p.requirePerFrameCallback {
p.seek(to: v)
}
}
}
fileprivate extension MotionController {
@objc
func handleDisplayLink(_ link: CADisplayLink) {
guard isTransitioning else {
return
}
guard 0 < currentAnimationDuration else {
return
}
guard let t = beginTime else {
return
}
let cTime = CACurrentMediaTime() - t
if cTime > currentAnimationDuration {
elapsedTime = isFinished ? 1 : 0
beginTime = nil
complete(isFinished: isFinished)
} else {
var eTime = cTime / totalDuration
if !isFinished {
eTime = 1 - eTime
}
elapsedTime = max(0, min(1, eTime))
}
}
} }
public extension MotionController { public extension MotionController {
...@@ -140,7 +201,7 @@ public extension MotionController { ...@@ -140,7 +201,7 @@ public extension MotionController {
public func update(progress: Double) { public func update(progress: Double) {
guard isTransitioning else { return } guard isTransitioning else { return }
self.beginTime = nil self.beginTime = nil
self.progress = max(-1, min(1, progress)) self.elapsedTime = max(-1, min(1, progress))
} }
/** /**
...@@ -151,14 +212,14 @@ public extension MotionController { ...@@ -151,14 +212,14 @@ public extension MotionController {
public func end(animate: Bool = true) { public func end(animate: Bool = true) {
guard isTransitioning else { return } guard isTransitioning else { return }
if !animate { if !animate {
self.complete(finished:true) self.complete(isFinished:true)
return return
} }
var maxTime: TimeInterval = 0 var maxTime: TimeInterval = 0
for animator in self.animators { for animator in self.animators {
maxTime = max(maxTime, animator.resume(at: self.progress * self.totalDuration, isReversed: false)) maxTime = max(maxTime, animator.resume(at: self.elapsedTime * self.totalDuration, isReversed: false))
} }
self.complete(after: maxTime, finishing: true) self.complete(after: maxTime, isFinished: true)
} }
/** /**
...@@ -169,18 +230,18 @@ public extension MotionController { ...@@ -169,18 +230,18 @@ public extension MotionController {
public func cancel(animate: Bool = true) { public func cancel(animate: Bool = true) {
guard isTransitioning else { return } guard isTransitioning else { return }
if !animate { if !animate {
self.complete(finished:false) self.complete(isFinished:false)
return return
} }
var maxTime: TimeInterval = 0 var maxTime: TimeInterval = 0
for animator in self.animators { for animator in self.animators {
var adjustedProgress = self.progress var adjustedProgress = self.elapsedTime
if adjustedProgress < 0 { if adjustedProgress < 0 {
adjustedProgress = -adjustedProgress adjustedProgress = -adjustedProgress
} }
maxTime = max(maxTime, animator.resume(at: adjustedProgress * self.totalDuration, isReversed: true)) maxTime = max(maxTime, animator.resume(at: adjustedProgress * self.totalDuration, isReversed: true))
} }
self.complete(after: maxTime, finishing: false) self.complete(after: maxTime, isFinished: false)
} }
/** /**
...@@ -219,11 +280,11 @@ public extension MotionController { ...@@ -219,11 +280,11 @@ public extension MotionController {
- Parameters: - Parameters:
- observer: the observer - observer: the observer
*/ */
func observeForProgressUpdate(observer: MotionProgressUpdateObserver) { func observeForProgressUpdate(observer: MotionTransitionObserver) {
if progressUpdateObservers == nil { if transitionObservers == nil {
progressUpdateObservers = [] transitionObservers = []
} }
progressUpdateObservers!.append(observer) transitionObservers!.append(observer)
} }
} }
...@@ -283,7 +344,7 @@ internal extension MotionController { ...@@ -283,7 +344,7 @@ internal extension MotionController {
func prepareForAnimation() { func prepareForAnimation() {
guard isTransitioning else { fatalError() } guard isTransitioning else { fatalError() }
animatingViews = [([UIView], [UIView])]() transitionPairs = [([UIView], [UIView])]()
for animator in animators { for animator in animators {
let currentFromViews = context.fromViews.filter { (view: UIView) -> Bool in let currentFromViews = context.fromViews.filter { (view: UIView) -> Bool in
return animator.canAnimate(view: view, isAppearing: false) return animator.canAnimate(view: view, isAppearing: false)
...@@ -291,7 +352,7 @@ internal extension MotionController { ...@@ -291,7 +352,7 @@ internal extension MotionController {
let currentToViews = context.toViews.filter { (view: UIView) -> Bool in let currentToViews = context.toViews.filter { (view: UIView) -> Bool in
return animator.canAnimate(view: view, isAppearing: true) return animator.canAnimate(view: view, isAppearing: true)
} }
animatingViews.append((currentFromViews, currentToViews)) transitionPairs.append((currentFromViews, currentToViews))
} }
} }
...@@ -299,7 +360,7 @@ internal extension MotionController { ...@@ -299,7 +360,7 @@ internal extension MotionController {
/// subclass should call `prepareForTransition` & `prepareForAnimation` before calling `animate` /// subclass should call `prepareForTransition` & `prepareForAnimation` before calling `animate`
func animate() { func animate() {
guard isTransitioning else { fatalError() } guard isTransitioning else { fatalError() }
for (currentFromViews, currentToViews) in animatingViews { for (currentFromViews, currentToViews) in transitionPairs {
// auto hide all animated views // auto hide all animated views
for view in currentFromViews { for view in currentFromViews {
context.hide(view: view) context.hide(view: view)
...@@ -312,8 +373,8 @@ internal extension MotionController { ...@@ -312,8 +373,8 @@ internal extension MotionController {
var totalDuration: TimeInterval = 0 var totalDuration: TimeInterval = 0
var animatorWantsInteractive = false var animatorWantsInteractive = false
for (i, animator) in animators.enumerated() { for (i, animator) in animators.enumerated() {
let duration = animator.animate(fromViews: animatingViews[i].0, let duration = animator.animate(fromViews: transitionPairs[i].0,
toViews: animatingViews[i].1) toViews: transitionPairs[i].1)
if duration == .infinity { if duration == .infinity {
animatorWantsInteractive = true animatorWantsInteractive = true
} else { } else {
...@@ -325,23 +386,23 @@ internal extension MotionController { ...@@ -325,23 +386,23 @@ internal extension MotionController {
if animatorWantsInteractive { if animatorWantsInteractive {
update(progress: 0) update(progress: 0)
} else { } else {
complete(after: totalDuration, finishing: true) complete(after: totalDuration, isFinished: true)
} }
} }
func complete(after: TimeInterval, finishing: Bool) { func complete(after: TimeInterval, isFinished: Bool) {
guard isTransitioning else { fatalError() } guard isTransitioning else { fatalError() }
if after <= 0.001 { if after <= 0.001 {
complete(finished: finishing) complete(isFinished: isFinished)
return return
} }
let elapsedTime = (finishing ? progress : 1 - progress) * totalDuration let v = (isFinished ? elapsedTime : 1 - elapsedTime) * totalDuration
self.finishing = finishing self.isFinished = isFinished
self.duration = after + elapsedTime self.currentAnimationDuration = after + v
self.beginTime = CACurrentMediaTime() - elapsedTime self.beginTime = CACurrentMediaTime() - v
} }
func complete(finished: Bool) { func complete(isFinished: Bool) {
guard isTransitioning else { fatalError() } guard isTransitioning else { fatalError() }
for animator in animators { for animator in animators {
animator.clean() animator.clean()
...@@ -351,8 +412,8 @@ internal extension MotionController { ...@@ -351,8 +412,8 @@ internal extension MotionController {
let completion = completionCallback let completion = completionCallback
animatingViews = nil transitionPairs = nil
progressUpdateObservers = nil transitionObservers = nil
transitionContainer = nil transitionContainer = nil
completionCallback = nil completionCallback = nil
container = nil container = nil
...@@ -361,10 +422,10 @@ internal extension MotionController { ...@@ -361,10 +422,10 @@ internal extension MotionController {
plugins = nil plugins = nil
context = nil context = nil
beginTime = nil beginTime = nil
progress = 0 elapsedTime = 0
totalDuration = 0 totalDuration = 0
completion?(finished) completion?(isFinished)
} }
} }
......
/*
* The MIT License (MIT)
*
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import Foundation
public protocol MotionTransitionObserver {
/**
Executed when the elapsed time changes during a transition.
- Parameter transitionObserver: A MotionTransitionObserver.
- Parameter didUpdateWith elapsedTime: A TimeInterval.
*/
func motion(transitionObserver: MotionTransitionObserver, didUpdateWith elapsedTime: TimeInterval)
}
...@@ -28,10 +28,6 @@ ...@@ -28,10 +28,6 @@
import UIKit import UIKit
public protocol MotionProgressUpdateObserver {
func motionDidUpdateProgress(progress: Double)
}
@objc(MotionViewControllerDelegate) @objc(MotionViewControllerDelegate)
public protocol MotionViewControllerDelegate { public protocol MotionViewControllerDelegate {
@objc @objc
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment