Commit 4d694db7 by Daniel Dahan

Hero + Motion rework: adding new files and the Hero base framework

parent 9465da01
......@@ -7,65 +7,84 @@
objects = {
/* Begin PBXBuildFile section */
961409AA1E43CF1B00E7BA99 /* Motion+Obj-C.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961409A91E43CF1B00E7BA99 /* Motion+Obj-C.swift */; };
961409B61E43D17200E7BA99 /* Motion+Obj-C.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961409A91E43CF1B00E7BA99 /* Motion+Obj-C.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961409B81E43D21300E7BA99 /* Motion.h in Headers */ = {isa = PBXBuildFile; fileRef = 96C98DED1E438A5700B22906 /* Motion.h */; settings = {ATTRIBUTES = (Public, ); }; };
964C153D1EDCF6EA00F0869D /* Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964C153C1EDCF6EA00F0869D /* Motion.swift */; };
964C15471EDD001A00F0869D /* MotionTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964C15461EDD001A00F0869D /* MotionTransition.swift */; };
9657A6AC1EDA1601004461DE /* MotionObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9657A6AB1EDA1601004461DE /* MotionObserver.swift */; };
9657A6AE1EDA19D8004461DE /* MotionTransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9657A6AD1EDA19D8004461DE /* MotionTransitionAnimator.swift */; };
9657A6B31EDA63FC004461DE /* MotionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9657A6B21EDA63FC004461DE /* MotionContext.swift */; };
966C539E1EDD207800A82A57 /* Motion+UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C539D1EDD207800A82A57 /* Motion+UIView.swift */; };
966C53A01EDD20DA00A82A57 /* Motion+UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C539F1EDD20DA00A82A57 /* Motion+UIViewController.swift */; };
966C53AF1EDD2F8B00A82A57 /* Motion+CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53AE1EDD2F8B00A82A57 /* Motion+CALayer.swift */; };
966C53B11EDD2FE600A82A57 /* Motion+CABasicAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53B01EDD2FE600A82A57 /* Motion+CABasicAnimation.swift */; };
966C53B31EDD325B00A82A57 /* MotionSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53B21EDD325B00A82A57 /* MotionSnapshot.swift */; };
966C53B51EDD327D00A82A57 /* MotionCascadeDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53B41EDD327D00A82A57 /* MotionCascadeDirection.swift */; };
966C53B71EDD328F00A82A57 /* MotionCoordinateSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53B61EDD328F00A82A57 /* MotionCoordinateSpace.swift */; };
966C53B91EDD366800A82A57 /* TransitionPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53B81EDD366800A82A57 /* TransitionPreprocessor.swift */; };
966C53BD1EDD396800A82A57 /* MotionAnimationFillMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53BC1EDD396800A82A57 /* MotionAnimationFillMode.swift */; };
966C53BF1EDD399400A82A57 /* MotionAnimationTimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966C53BE1EDD399400A82A57 /* MotionAnimationTimingFunction.swift */; };
96AEB5F11EE1FA3C009A3BE0 /* MatchPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB5F01EE1FA3C009A3BE0 /* MatchPreprocessor.swift */; };
96AEB6081EE32A38009A3BE0 /* SourcePreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6071EE32A38009A3BE0 /* SourcePreprocessor.swift */; };
96AEB60A1EE339F6009A3BE0 /* DurationPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6091EE339F6009A3BE0 /* DurationPreprocessor.swift */; };
96AEB60C1EE34012009A3BE0 /* Motion+CG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB60B1EE34012009A3BE0 /* Motion+CG.swift */; };
96BFC1701E63C3460075DE1F /* MotionController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96C98DE31E4382B100B22906 /* MotionController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96C98DE41E4382B100B22906 /* MotionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C98DE31E4382B100B22906 /* MotionController.swift */; };
96C98DE61E43848500B22906 /* MotionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C98DE51E43848500B22906 /* MotionAnimation.swift */; };
96C98DEB1E4389BE00B22906 /* MotionAnimation.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96C98DE51E43848500B22906 /* MotionAnimation.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96E846F41EDDA62F0005F32F /* AnimationPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E846F31EDDA62F0005F32F /* AnimationPreprocessor.swift */; };
96E846F61EDDA7F20005F32F /* MotionTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E846F51EDDA7F20005F32F /* MotionTransitionState.swift */; };
96AEB68D1EE4610F009A3BE0 /* MotionAnimatorViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6651EE4610F009A3BE0 /* MotionAnimatorViewContext.swift */; };
96AEB68E1EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6661EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift */; };
96AEB68F1EE4610F009A3BE0 /* MotionDefaultAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6671EE4610F009A3BE0 /* MotionDefaultAnimator.swift */; };
96AEB6901EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6681EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift */; };
96AEB6911EE4610F009A3BE0 /* MotionDebugPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66A1EE4610F009A3BE0 /* MotionDebugPlugin.swift */; };
96AEB6921EE4610F009A3BE0 /* MotionDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66B1EE4610F009A3BE0 /* MotionDebugView.swift */; };
96AEB6931EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66C1EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift */; };
96AEB6941EE4610F009A3BE0 /* MotionTransition+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66E1EE4610F009A3BE0 /* MotionTransition+Array.swift */; };
96AEB6951EE4610F009A3BE0 /* Motion+CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66F1EE4610F009A3BE0 /* Motion+CALayer.swift */; };
96AEB6961EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6701EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift */; };
96AEB6971EE4610F009A3BE0 /* Motion+CG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6711EE4610F009A3BE0 /* Motion+CG.swift */; };
96AEB6981EE4610F009A3BE0 /* Motion+DispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6721EE4610F009A3BE0 /* Motion+DispatchQueue.swift */; };
96AEB6991EE4610F009A3BE0 /* Motion+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6731EE4610F009A3BE0 /* Motion+UIKit.swift */; };
96AEB69A1EE4610F009A3BE0 /* Motion+UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6741EE4610F009A3BE0 /* Motion+UIView.swift */; };
96AEB69B1EE4610F009A3BE0 /* Motion+UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6751EE4610F009A3BE0 /* Motion+UIViewController.swift */; };
96AEB69D1EE4610F009A3BE0 /* Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6771EE4610F009A3BE0 /* Motion.swift */; };
96AEB69E1EE4610F009A3BE0 /* MotionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6781EE4610F009A3BE0 /* MotionController.swift */; };
96AEB69F1EE4610F009A3BE0 /* MotionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6791EE4610F009A3BE0 /* MotionContext.swift */; };
96AEB6A01EE4610F009A3BE0 /* MotionIndependentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67A1EE4610F009A3BE0 /* MotionIndependentController.swift */; };
96AEB6A11EE4610F009A3BE0 /* MotionTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67B1EE4610F009A3BE0 /* MotionTransition.swift */; };
96AEB6A21EE4610F009A3BE0 /* MotionTransition+MotionStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67C1EE4610F009A3BE0 /* MotionTransition+MotionStringConvertible.swift */; };
96AEB6A31EE4610F009A3BE0 /* MotionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */; };
96AEB6A41EE4610F009A3BE0 /* MotionStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */; };
96AEB6A51EE4610F009A3BE0 /* MotionTargetState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */; };
96AEB6A61EE4610F009A3BE0 /* MotionTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6801EE4610F009A3BE0 /* MotionTypes.swift */; };
96AEB6A71EE4610F009A3BE0 /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6821EE4610F009A3BE0 /* Lexer.swift */; };
96AEB6A81EE4610F009A3BE0 /* Nodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6831EE4610F009A3BE0 /* Nodes.swift */; };
96AEB6A91EE4610F009A3BE0 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6841EE4610F009A3BE0 /* Parser.swift */; };
96AEB6AA1EE4610F009A3BE0 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6851EE4610F009A3BE0 /* Regex.swift */; };
96AEB6AB1EE4610F009A3BE0 /* BasePreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6871EE4610F009A3BE0 /* BasePreprocessor.swift */; };
96AEB6AC1EE4610F009A3BE0 /* CascadePreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6881EE4610F009A3BE0 /* CascadePreprocessor.swift */; };
96AEB6AD1EE4610F009A3BE0 /* DurationPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6891EE4610F009A3BE0 /* DurationPreprocessor.swift */; };
96AEB6AE1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68A1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift */; };
96AEB6AF1EE4610F009A3BE0 /* MatchPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68B1EE4610F009A3BE0 /* MatchPreprocessor.swift */; };
96AEB6B01EE4610F009A3BE0 /* SourcePreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB68C1EE4610F009A3BE0 /* SourcePreprocessor.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
961409A91E43CF1B00E7BA99 /* Motion+Obj-C.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+Obj-C.swift"; sourceTree = "<group>"; };
964C153C1EDCF6EA00F0869D /* Motion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Motion.swift; sourceTree = "<group>"; };
964C15461EDD001A00F0869D /* MotionTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTransition.swift; sourceTree = "<group>"; };
9657A6AB1EDA1601004461DE /* MotionObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionObserver.swift; sourceTree = "<group>"; };
9657A6AD1EDA19D8004461DE /* MotionTransitionAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTransitionAnimator.swift; sourceTree = "<group>"; };
9657A6B21EDA63FC004461DE /* MotionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionContext.swift; sourceTree = "<group>"; };
966C539D1EDD207800A82A57 /* Motion+UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+UIView.swift"; sourceTree = "<group>"; };
966C539F1EDD20DA00A82A57 /* Motion+UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+UIViewController.swift"; sourceTree = "<group>"; };
966C53AE1EDD2F8B00A82A57 /* Motion+CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CALayer.swift"; sourceTree = "<group>"; };
966C53B01EDD2FE600A82A57 /* Motion+CABasicAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CABasicAnimation.swift"; sourceTree = "<group>"; };
966C53B21EDD325B00A82A57 /* MotionSnapshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionSnapshot.swift; sourceTree = "<group>"; };
966C53B41EDD327D00A82A57 /* MotionCascadeDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionCascadeDirection.swift; sourceTree = "<group>"; };
966C53B61EDD328F00A82A57 /* MotionCoordinateSpace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionCoordinateSpace.swift; sourceTree = "<group>"; };
966C53B81EDD366800A82A57 /* TransitionPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionPreprocessor.swift; sourceTree = "<group>"; };
966C53BC1EDD396800A82A57 /* MotionAnimationFillMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimationFillMode.swift; sourceTree = "<group>"; };
966C53BE1EDD399400A82A57 /* MotionAnimationTimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimationTimingFunction.swift; sourceTree = "<group>"; };
96AEB5F01EE1FA3C009A3BE0 /* MatchPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatchPreprocessor.swift; sourceTree = "<group>"; };
96AEB6071EE32A38009A3BE0 /* SourcePreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourcePreprocessor.swift; sourceTree = "<group>"; };
96AEB6091EE339F6009A3BE0 /* DurationPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DurationPreprocessor.swift; sourceTree = "<group>"; };
96AEB60B1EE34012009A3BE0 /* Motion+CG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CG.swift"; sourceTree = "<group>"; };
96AEB6651EE4610F009A3BE0 /* MotionAnimatorViewContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimatorViewContext.swift; sourceTree = "<group>"; };
96AEB6661EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionCoreAnimationViewContext.swift; sourceTree = "<group>"; };
96AEB6671EE4610F009A3BE0 /* MotionDefaultAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionDefaultAnimator.swift; sourceTree = "<group>"; };
96AEB6681EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionViewPropertyViewContext.swift; sourceTree = "<group>"; };
96AEB66A1EE4610F009A3BE0 /* MotionDebugPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionDebugPlugin.swift; sourceTree = "<group>"; };
96AEB66B1EE4610F009A3BE0 /* MotionDebugView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionDebugView.swift; sourceTree = "<group>"; };
96AEB66C1EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultAnimationPreprocessor.swift; sourceTree = "<group>"; };
96AEB66E1EE4610F009A3BE0 /* MotionTransition+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MotionTransition+Array.swift"; sourceTree = "<group>"; };
96AEB66F1EE4610F009A3BE0 /* Motion+CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CALayer.swift"; sourceTree = "<group>"; };
96AEB6701EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CAMediaTimingFunction.swift"; sourceTree = "<group>"; };
96AEB6711EE4610F009A3BE0 /* Motion+CG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CG.swift"; sourceTree = "<group>"; };
96AEB6721EE4610F009A3BE0 /* Motion+DispatchQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+DispatchQueue.swift"; sourceTree = "<group>"; };
96AEB6731EE4610F009A3BE0 /* Motion+UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+UIKit.swift"; sourceTree = "<group>"; };
96AEB6741EE4610F009A3BE0 /* Motion+UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+UIView.swift"; sourceTree = "<group>"; };
96AEB6751EE4610F009A3BE0 /* Motion+UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+UIViewController.swift"; sourceTree = "<group>"; };
96AEB6771EE4610F009A3BE0 /* Motion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Motion.swift; sourceTree = "<group>"; };
96AEB6781EE4610F009A3BE0 /* MotionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionController.swift; sourceTree = "<group>"; };
96AEB6791EE4610F009A3BE0 /* MotionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionContext.swift; sourceTree = "<group>"; };
96AEB67A1EE4610F009A3BE0 /* MotionIndependentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionIndependentController.swift; sourceTree = "<group>"; };
96AEB67B1EE4610F009A3BE0 /* MotionTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTransition.swift; sourceTree = "<group>"; };
96AEB67C1EE4610F009A3BE0 /* MotionTransition+MotionStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MotionTransition+MotionStringConvertible.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>"; };
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>"; };
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>"; };
96AEB6841EE4610F009A3BE0 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
96AEB6851EE4610F009A3BE0 /* Regex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Regex.swift; sourceTree = "<group>"; };
96AEB6871EE4610F009A3BE0 /* BasePreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePreprocessor.swift; sourceTree = "<group>"; };
96AEB6881EE4610F009A3BE0 /* CascadePreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CascadePreprocessor.swift; sourceTree = "<group>"; };
96AEB6891EE4610F009A3BE0 /* DurationPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DurationPreprocessor.swift; sourceTree = "<group>"; };
96AEB68A1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IgnoreSubviewModifiersPreprocessor.swift; sourceTree = "<group>"; };
96AEB68B1EE4610F009A3BE0 /* MatchPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatchPreprocessor.swift; sourceTree = "<group>"; };
96AEB68C1EE4610F009A3BE0 /* SourcePreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourcePreprocessor.swift; sourceTree = "<group>"; };
96C98DD11E424AB000B22906 /* Motion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Motion.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
96C98DE31E4382B100B22906 /* MotionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionController.swift; sourceTree = "<group>"; };
96C98DE51E43848500B22906 /* MotionAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimation.swift; sourceTree = "<group>"; };
96C98DED1E438A5700B22906 /* Motion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Motion.h; sourceTree = "<group>"; };
96E846F31EDDA62F0005F32F /* AnimationPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationPreprocessor.swift; sourceTree = "<group>"; };
96E846F51EDDA7F20005F32F /* MotionTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTransitionState.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
......@@ -79,78 +98,105 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
96C98DC71E424AB000B22906 = {
96AEB6641EE4610F009A3BE0 /* Animator */ = {
isa = PBXGroup;
children = (
96C98DDC1E424B9000B22906 /* Sources */,
96C98DD21E424AB000B22906 /* Products */,
96AEB6651EE4610F009A3BE0 /* MotionAnimatorViewContext.swift */,
96AEB6661EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift */,
96AEB6671EE4610F009A3BE0 /* MotionDefaultAnimator.swift */,
96AEB6681EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift */,
);
path = Animator;
sourceTree = "<group>";
};
96C98DD21E424AB000B22906 /* Products */ = {
96AEB6691EE4610F009A3BE0 /* Debug Plugin */ = {
isa = PBXGroup;
children = (
96C98DD11E424AB000B22906 /* Motion.framework */,
96AEB66A1EE4610F009A3BE0 /* MotionDebugPlugin.swift */,
96AEB66B1EE4610F009A3BE0 /* MotionDebugView.swift */,
);
name = Products;
path = "Debug Plugin";
sourceTree = "<group>";
};
96C98DDC1E424B9000B22906 /* Sources */ = {
96AEB66D1EE4610F009A3BE0 /* Extensions */ = {
isa = PBXGroup;
children = (
96C98DE21E43809D00B22906 /* LICENSE */,
96C98DDD1E424B9000B22906 /* Info.plist */,
96C98DED1E438A5700B22906 /* Motion.h */,
964C153C1EDCF6EA00F0869D /* Motion.swift */,
96C98DE31E4382B100B22906 /* MotionController.swift */,
9657A6AB1EDA1601004461DE /* MotionObserver.swift */,
9657A6B21EDA63FC004461DE /* MotionContext.swift */,
96C98DE51E43848500B22906 /* MotionAnimation.swift */,
96E846F51EDDA7F20005F32F /* MotionTransitionState.swift */,
964C15461EDD001A00F0869D /* MotionTransition.swift */,
966C53B21EDD325B00A82A57 /* MotionSnapshot.swift */,
966C53B41EDD327D00A82A57 /* MotionCascadeDirection.swift */,
966C53B61EDD328F00A82A57 /* MotionCoordinateSpace.swift */,
966C53BC1EDD396800A82A57 /* MotionAnimationFillMode.swift */,
966C53BE1EDD399400A82A57 /* MotionAnimationTimingFunction.swift */,
96E846EB1EDD4FCF0005F32F /* Animator */,
96E846EC1EDD4FE40005F32F /* Extension */,
96E846EA1EDD4FB30005F32F /* Preprocessor */,
96AEB66E1EE4610F009A3BE0 /* MotionTransition+Array.swift */,
96AEB66F1EE4610F009A3BE0 /* Motion+CALayer.swift */,
96AEB6701EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift */,
96AEB6711EE4610F009A3BE0 /* Motion+CG.swift */,
96AEB6721EE4610F009A3BE0 /* Motion+DispatchQueue.swift */,
96AEB6731EE4610F009A3BE0 /* Motion+UIKit.swift */,
96AEB6741EE4610F009A3BE0 /* Motion+UIView.swift */,
96AEB6751EE4610F009A3BE0 /* Motion+UIViewController.swift */,
);
path = Sources;
path = Extensions;
sourceTree = "<group>";
};
96AEB6811EE4610F009A3BE0 /* Parser */ = {
isa = PBXGroup;
children = (
96AEB6821EE4610F009A3BE0 /* Lexer.swift */,
96AEB6831EE4610F009A3BE0 /* Nodes.swift */,
96AEB6841EE4610F009A3BE0 /* Parser.swift */,
96AEB6851EE4610F009A3BE0 /* Regex.swift */,
);
path = Parser;
sourceTree = "<group>";
};
96E846EA1EDD4FB30005F32F /* Preprocessor */ = {
96AEB6861EE4610F009A3BE0 /* Preprocessors */ = {
isa = PBXGroup;
children = (
966C53B81EDD366800A82A57 /* TransitionPreprocessor.swift */,
96E846F31EDDA62F0005F32F /* AnimationPreprocessor.swift */,
96AEB5F01EE1FA3C009A3BE0 /* MatchPreprocessor.swift */,
96AEB6071EE32A38009A3BE0 /* SourcePreprocessor.swift */,
96AEB6091EE339F6009A3BE0 /* DurationPreprocessor.swift */,
96AEB6871EE4610F009A3BE0 /* BasePreprocessor.swift */,
96AEB6881EE4610F009A3BE0 /* CascadePreprocessor.swift */,
96AEB6891EE4610F009A3BE0 /* DurationPreprocessor.swift */,
96AEB68A1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift */,
96AEB68B1EE4610F009A3BE0 /* MatchPreprocessor.swift */,
96AEB68C1EE4610F009A3BE0 /* SourcePreprocessor.swift */,
);
name = Preprocessor;
path = Preprocessors;
sourceTree = "<group>";
};
96E846EB1EDD4FCF0005F32F /* Animator */ = {
96C98DC71E424AB000B22906 = {
isa = PBXGroup;
children = (
96C98DDC1E424B9000B22906 /* Sources */,
96C98DD21E424AB000B22906 /* Products */,
);
sourceTree = "<group>";
};
96C98DD21E424AB000B22906 /* Products */ = {
isa = PBXGroup;
children = (
9657A6AD1EDA19D8004461DE /* MotionTransitionAnimator.swift */,
96C98DD11E424AB000B22906 /* Motion.framework */,
);
name = Animator;
name = Products;
sourceTree = "<group>";
};
96E846EC1EDD4FE40005F32F /* Extension */ = {
96C98DDC1E424B9000B22906 /* Sources */ = {
isa = PBXGroup;
children = (
961409A91E43CF1B00E7BA99 /* Motion+Obj-C.swift */,
966C539D1EDD207800A82A57 /* Motion+UIView.swift */,
966C539F1EDD20DA00A82A57 /* Motion+UIViewController.swift */,
966C53AE1EDD2F8B00A82A57 /* Motion+CALayer.swift */,
966C53B01EDD2FE600A82A57 /* Motion+CABasicAnimation.swift */,
96AEB60B1EE34012009A3BE0 /* Motion+CG.swift */,
96C98DE21E43809D00B22906 /* LICENSE */,
96C98DDD1E424B9000B22906 /* Info.plist */,
96C98DED1E438A5700B22906 /* Motion.h */,
96AEB6641EE4610F009A3BE0 /* Animator */,
96AEB6691EE4610F009A3BE0 /* Debug Plugin */,
96AEB66C1EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift */,
96AEB66D1EE4610F009A3BE0 /* Extensions */,
96AEB6771EE4610F009A3BE0 /* Motion.swift */,
96AEB6781EE4610F009A3BE0 /* MotionController.swift */,
96AEB6791EE4610F009A3BE0 /* MotionContext.swift */,
96AEB67A1EE4610F009A3BE0 /* MotionIndependentController.swift */,
96AEB67B1EE4610F009A3BE0 /* MotionTransition.swift */,
96AEB67C1EE4610F009A3BE0 /* MotionTransition+MotionStringConvertible.swift */,
96AEB67D1EE4610F009A3BE0 /* MotionPlugin.swift */,
96AEB67E1EE4610F009A3BE0 /* MotionStringConvertible.swift */,
96AEB67F1EE4610F009A3BE0 /* MotionTargetState.swift */,
96AEB6801EE4610F009A3BE0 /* MotionTypes.swift */,
96AEB6811EE4610F009A3BE0 /* Parser */,
96AEB6861EE4610F009A3BE0 /* Preprocessors */,
);
name = Extension;
path = Sources;
sourceTree = "<group>";
};
/* End PBXGroup section */
......@@ -161,9 +207,6 @@
buildActionMask = 2147483647;
files = (
961409B81E43D21300E7BA99 /* Motion.h in Headers */,
961409B61E43D17200E7BA99 /* Motion+Obj-C.swift in Headers */,
96BFC1701E63C3460075DE1F /* MotionController.swift in Headers */,
96C98DEB1E4389BE00B22906 /* MotionAnimation.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -199,7 +242,7 @@
TargetAttributes = {
96C98DD01E424AB000B22906 = {
CreatedOnToolsVersion = 8.2.1;
LastSwiftMigration = 0820;
LastSwiftMigration = 0830;
ProvisioningStyle = Automatic;
};
};
......@@ -236,30 +279,41 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
966C53BD1EDD396800A82A57 /* MotionAnimationFillMode.swift in Sources */,
966C53B91EDD366800A82A57 /* TransitionPreprocessor.swift in Sources */,
96E846F41EDDA62F0005F32F /* AnimationPreprocessor.swift in Sources */,
966C53B71EDD328F00A82A57 /* MotionCoordinateSpace.swift in Sources */,
966C539E1EDD207800A82A57 /* Motion+UIView.swift in Sources */,
96AEB5F11EE1FA3C009A3BE0 /* MatchPreprocessor.swift in Sources */,
966C53B31EDD325B00A82A57 /* MotionSnapshot.swift in Sources */,
96C98DE61E43848500B22906 /* MotionAnimation.swift in Sources */,
96AEB60A1EE339F6009A3BE0 /* DurationPreprocessor.swift in Sources */,
9657A6AE1EDA19D8004461DE /* MotionTransitionAnimator.swift in Sources */,
9657A6AC1EDA1601004461DE /* MotionObserver.swift in Sources */,
9657A6B31EDA63FC004461DE /* MotionContext.swift in Sources */,
966C53B51EDD327D00A82A57 /* MotionCascadeDirection.swift in Sources */,
964C15471EDD001A00F0869D /* MotionTransition.swift in Sources */,
96E846F61EDDA7F20005F32F /* MotionTransitionState.swift in Sources */,
966C53B11EDD2FE600A82A57 /* Motion+CABasicAnimation.swift in Sources */,
961409AA1E43CF1B00E7BA99 /* Motion+Obj-C.swift in Sources */,
964C153D1EDCF6EA00F0869D /* Motion.swift in Sources */,
966C53AF1EDD2F8B00A82A57 /* Motion+CALayer.swift in Sources */,
966C53A01EDD20DA00A82A57 /* Motion+UIViewController.swift in Sources */,
96AEB6081EE32A38009A3BE0 /* SourcePreprocessor.swift in Sources */,
96AEB60C1EE34012009A3BE0 /* Motion+CG.swift in Sources */,
96C98DE41E4382B100B22906 /* MotionController.swift in Sources */,
966C53BF1EDD399400A82A57 /* MotionAnimationTimingFunction.swift in Sources */,
96AEB6A31EE4610F009A3BE0 /* MotionPlugin.swift in Sources */,
96AEB69A1EE4610F009A3BE0 /* Motion+UIView.swift in Sources */,
96AEB6AD1EE4610F009A3BE0 /* DurationPreprocessor.swift in Sources */,
96AEB6931EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift in Sources */,
96AEB68F1EE4610F009A3BE0 /* MotionDefaultAnimator.swift in Sources */,
96AEB69D1EE4610F009A3BE0 /* Motion.swift in Sources */,
96AEB6A21EE4610F009A3BE0 /* MotionTransition+MotionStringConvertible.swift in Sources */,
96AEB6A51EE4610F009A3BE0 /* MotionTargetState.swift in Sources */,
96AEB6961EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift in Sources */,
96AEB6941EE4610F009A3BE0 /* MotionTransition+Array.swift in Sources */,
96AEB6951EE4610F009A3BE0 /* Motion+CALayer.swift in Sources */,
96AEB6981EE4610F009A3BE0 /* Motion+DispatchQueue.swift in Sources */,
96AEB6A61EE4610F009A3BE0 /* MotionTypes.swift in Sources */,
96AEB68E1EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift in Sources */,
96AEB6921EE4610F009A3BE0 /* MotionDebugView.swift in Sources */,
96AEB6A01EE4610F009A3BE0 /* MotionIndependentController.swift in Sources */,
96AEB6AA1EE4610F009A3BE0 /* Regex.swift in Sources */,
96AEB6901EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift in Sources */,
96AEB6991EE4610F009A3BE0 /* Motion+UIKit.swift in Sources */,
96AEB69B1EE4610F009A3BE0 /* Motion+UIViewController.swift in Sources */,
96AEB6A41EE4610F009A3BE0 /* MotionStringConvertible.swift in Sources */,
96AEB6A81EE4610F009A3BE0 /* Nodes.swift in Sources */,
96AEB6A91EE4610F009A3BE0 /* Parser.swift in Sources */,
96AEB69F1EE4610F009A3BE0 /* MotionContext.swift in Sources */,
96AEB6A71EE4610F009A3BE0 /* Lexer.swift in Sources */,
96AEB6AE1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift in Sources */,
96AEB68D1EE4610F009A3BE0 /* MotionAnimatorViewContext.swift in Sources */,
96AEB6B01EE4610F009A3BE0 /* SourcePreprocessor.swift in Sources */,
96AEB6911EE4610F009A3BE0 /* MotionDebugPlugin.swift in Sources */,
96AEB6AF1EE4610F009A3BE0 /* MatchPreprocessor.swift in Sources */,
96AEB69E1EE4610F009A3BE0 /* MotionController.swift in Sources */,
96AEB6A11EE4610F009A3BE0 /* MotionTransition.swift in Sources */,
96AEB6971EE4610F009A3BE0 /* Motion+CG.swift in Sources */,
96AEB6AB1EE4610F009A3BE0 /* BasePreprocessor.swift in Sources */,
96AEB6AC1EE4610F009A3BE0 /* CascadePreprocessor.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public class AnimationPreprocessor: TransitionPreprocessor {
/// A reference to the Motion instance.
fileprivate var motion: Motion
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Initializer that accepts a Motion instance.
- Parameter motion: A Motion instance.
*/
init(motion: Motion) {
self.motion = motion
}
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
/*
let inNavigationController = motion.isNavigationController
let inTabBarController = motion.isTabBarController
let toViewController = motion.toViewController
let fromViewController = motion.fromViewController
let presenting = motion.isPresenting
let fromOverFullScreen = motion.isFromViewFullScreen
let toOverFullScreen = motion.isToViewFullScreen
let fromView = motion.fromView
let toView = motion.toView
let animators = motion.animators
*/
}
}
/*
* 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 UIKit
internal class MotionAnimatorViewContext {
var animator: MotionAnimator?
var snapshot: UIView
var duration: TimeInterval = 0
var targetState: MotionTargetState
// computed
var currentTime: TimeInterval {
return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil)
}
var container: UIView? {
return animator?.context.container
}
class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
return false
}
func apply(state: MotionTargetState) {
}
func resume(timePassed: TimeInterval, reverse: Bool) {
}
func seek(timePassed: TimeInterval) {
}
func clean() {
animator = nil
}
func startAnimations(appearing: Bool) {
}
required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTargetState) {
self.animator = animator
self.snapshot = snapshot
self.targetState = targetState
}
}
/*
* 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 UIKit
internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
var state = [String: (Any?, Any?)]()
var timingFunction: CAMediaTimingFunction = .standard
// computed
var contentLayer: CALayer? {
return snapshot.layer.sublayers?.get(0)
}
var overlayLayer: CALayer?
override class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
return state.position != nil ||
state.size != nil ||
state.transform != nil ||
state.cornerRadius != nil ||
state.opacity != nil ||
state.overlay != nil ||
state.backgroundColor != nil ||
state.borderColor != nil ||
state.borderWidth != nil ||
state.shadowOpacity != nil ||
state.shadowRadius != nil ||
state.shadowOffset != nil ||
state.shadowColor != nil ||
state.shadowPath != nil ||
state.contentsRect != nil ||
state.forceAnimate
}
func getOverlayLayer() -> CALayer {
if overlayLayer == nil {
overlayLayer = CALayer()
overlayLayer!.frame = snapshot.bounds
overlayLayer!.opacity = 0
snapshot.layer.addSublayer(overlayLayer!)
}
return overlayLayer!
}
func overlayKeyFor(key: String) -> String? {
if key.hasPrefix("overlay.") {
var key = key
key.removeSubrange(key.startIndex..<key.index(key.startIndex, offsetBy: 8))
return key
}
return nil
}
func currentValue(key: String) -> Any? {
if let key = overlayKeyFor(key: key) {
return overlayLayer?.value(forKeyPath: key)
}
if snapshot.layer.animationKeys()?.isEmpty != false {
return snapshot.layer.value(forKeyPath:key)
}
return (snapshot.layer.presentation() ?? snapshot.layer).value(forKeyPath: key)
}
func getAnimation(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?, ignoreArc: Bool = false) -> CAPropertyAnimation {
let key = overlayKeyFor(key: key) ?? key
let anim: CAPropertyAnimation
if !ignoreArc, key == "position", let arcIntensity = targetState.arc,
let fromPos = (fromValue as? NSValue)?.cgPointValue,
let toPos = (toValue as? NSValue)?.cgPointValue,
abs(fromPos.x - toPos.x) >= 1, abs(fromPos.y - toPos.y) >= 1 {
let kanim = CAKeyframeAnimation(keyPath: key)
let path = CGMutablePath()
let maxControl = fromPos.y > toPos.y ? CGPoint(x: toPos.x, y: fromPos.y) : CGPoint(x: fromPos.x, y: toPos.y)
let minControl = (toPos - fromPos) / 2 + fromPos
path.move(to: fromPos)
path.addQuadCurve(to: toPos, control: minControl + (maxControl - minControl) * arcIntensity)
kanim.values = [fromValue!, toValue!]
kanim.path = path
kanim.duration = duration
kanim.timingFunctions = [timingFunction]
anim = kanim
} else if #available(iOS 9.0, *), key != "cornerRadius", let (stiffness, damping) = targetState.spring {
let sanim = CASpringAnimation(keyPath: key)
sanim.stiffness = stiffness
sanim.damping = damping
sanim.duration = sanim.settlingDuration * 0.9
sanim.fromValue = fromValue
sanim.toValue = toValue
anim = sanim
} else {
let banim = CABasicAnimation(keyPath: key)
banim.duration = duration
banim.fromValue = fromValue
banim.toValue = toValue
banim.timingFunction = timingFunction
anim = banim
}
anim.fillMode = kCAFillModeBoth
anim.isRemovedOnCompletion = false
anim.beginTime = beginTime
return anim
}
// return the completion duration of the animation (duration + initial delay, not counting the beginTime)
func animate(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?) -> TimeInterval {
let anim = getAnimation(key: key, beginTime:beginTime, fromValue: fromValue, toValue: toValue)
if let overlayKey = overlayKeyFor(key:key) {
getOverlayLayer().add(anim, forKey: overlayKey)
} else {
snapshot.layer.add(anim, forKey: key)
switch key {
case "cornerRadius", "contentsRect", "contentsScale":
contentLayer?.add(anim, forKey: key)
overlayLayer?.add(anim, forKey: key)
case "bounds.size":
let fromSize = (fromValue as? NSValue)!.cgSizeValue
let toSize = (toValue as? NSValue)!.cgSizeValue
// for the snapshotView(UIReplicantView): there is a
// subview(UIReplicantContentView) that is hosting the real snapshot image.
// because we are using CAAnimations and not UIView animations,
// The snapshotView will not layout during animations.
// we have to add two more animations to manually layout the content view.
let fromPosn = NSValue(cgPoint:fromSize.center)
let toPosn = NSValue(cgPoint:toSize.center)
let positionAnim = getAnimation(key: "position", beginTime:0, fromValue: fromPosn, toValue: toPosn, ignoreArc: true)
positionAnim.beginTime = anim.beginTime
positionAnim.timingFunction = anim.timingFunction
positionAnim.duration = anim.duration
contentLayer?.add(positionAnim, forKey: "position")
contentLayer?.add(anim, forKey: key)
overlayLayer?.add(positionAnim, forKey: "position")
overlayLayer?.add(anim, forKey: key)
default: break
}
}
return anim.duration + anim.beginTime - beginTime
}
func animate(delay: TimeInterval) {
if let tf = targetState.timingFunction {
timingFunction = tf
}
duration = targetState.duration!
let beginTime = currentTime + delay
var finalDuration: TimeInterval = duration
for (key, (fromValue, toValue)) in state {
let neededTime = animate(key: key, beginTime: beginTime, fromValue: fromValue, toValue: toValue)
finalDuration = max(finalDuration, neededTime + delay)
}
duration = finalDuration
}
/**
- Returns: a CALayer [keyPath:value] map for animation
*/
func viewState(targetState: MotionTargetState) -> [String: Any?] {
var targetState = targetState
var rtn = [String: Any?]()
if let size = targetState.size {
if targetState.useScaleBasedSizeChange ?? self.targetState.useScaleBasedSizeChange ?? false {
let currentSize = snapshot.bounds.size
targetState.append(.scale(x:size.width / currentSize.width,
y:size.height / currentSize.height))
} else {
rtn["bounds.size"] = NSValue(cgSize:size)
}
}
if let position = targetState.position {
rtn["position"] = NSValue(cgPoint:position)
}
if let opacity = targetState.opacity, !(snapshot is UIVisualEffectView) {
rtn["opacity"] = NSNumber(value: opacity)
}
if let cornerRadius = targetState.cornerRadius {
rtn["cornerRadius"] = NSNumber(value: cornerRadius.native)
}
if let backgroundColor = targetState.backgroundColor {
rtn["backgroundColor"] = backgroundColor
}
if let zPosition = targetState.zPosition {
rtn["zPosition"] = NSNumber(value: zPosition.native)
}
if let borderWidth = targetState.borderWidth {
rtn["borderWidth"] = NSNumber(value: borderWidth.native)
}
if let borderColor = targetState.borderColor {
rtn["borderColor"] = borderColor
}
if let masksToBounds = targetState.masksToBounds {
rtn["masksToBounds"] = masksToBounds
}
if targetState.displayShadow {
if let shadowColor = targetState.shadowColor {
rtn["shadowColor"] = shadowColor
}
if let shadowRadius = targetState.shadowRadius {
rtn["shadowRadius"] = NSNumber(value: shadowRadius.native)
}
if let shadowOpacity = targetState.shadowOpacity {
rtn["shadowOpacity"] = NSNumber(value: shadowOpacity)
}
if let shadowPath = targetState.shadowPath {
rtn["shadowPath"] = shadowPath
}
if let shadowOffset = targetState.shadowOffset {
rtn["shadowOffset"] = NSValue(cgSize: shadowOffset)
}
}
if let contentsRect = targetState.contentsRect {
rtn["contentsRect"] = NSValue(cgRect: contentsRect)
}
if let contentsScale = targetState.contentsScale {
rtn["contentsScale"] = NSNumber(value: contentsScale.native)
}
if let transform = targetState.transform {
rtn["transform"] = NSValue(caTransform3D: transform)
}
if let (color, opacity) = targetState.overlay {
rtn["overlay.backgroundColor"] = color
rtn["overlay.opacity"] = NSNumber(value: opacity.native)
}
return rtn
}
override func apply(state: MotionTargetState) {
let targetState = viewState(targetState: state)
for (key, targetValue) in targetState {
if self.state[key] == nil {
let current = currentValue(key: key)
self.state[key] = (current, current)
}
_ = animate(key: key, beginTime: 0, fromValue: targetValue, toValue: targetValue)
}
}
override func resume(timePassed: TimeInterval, reverse: Bool) {
for (key, (fromValue, toValue)) in state {
let realToValue = !reverse ? toValue : fromValue
let realFromValue = currentValue(key: key)
state[key] = (realFromValue, realToValue)
}
// we need to update the duration to reflect current state
targetState.duration = reverse ? timePassed - targetState.delay : duration - timePassed
let realDelay = max(0, targetState.delay - timePassed)
animate(delay: realDelay)
}
func seek(layer: CALayer, timePassed: TimeInterval) {
let timeOffset = timePassed - targetState.delay
for (key, anim) in layer.animations {
anim.speed = 0
anim.timeOffset = max(0, min(anim.duration - 0.01, timeOffset))
layer.removeAnimation(forKey: key)
layer.add(anim, forKey: key)
}
}
override func seek(timePassed: TimeInterval) {
seek(layer:snapshot.layer, timePassed:timePassed)
if let contentLayer = contentLayer {
seek(layer:contentLayer, timePassed:timePassed)
}
if let overlayLayer = overlayLayer {
seek(layer: overlayLayer, timePassed: timePassed)
}
}
override func clean() {
super.clean()
overlayLayer = nil
}
override func startAnimations(appearing: Bool) {
if let beginState = targetState.beginState?.state {
let appeared = viewState(targetState: beginState)
for (key, value) in appeared {
snapshot.layer.setValue(value, forKeyPath: key)
}
if let (color, opacity) = beginState.overlay {
let overlay = getOverlayLayer()
overlay.backgroundColor = color
overlay.opacity = Float(opacity)
}
}
let disappeared = viewState(targetState: targetState)
for (key, disappearedState) in disappeared {
let appearingState = currentValue(key: key)
let toValue = appearing ? appearingState : disappearedState
let fromValue = !appearing ? appearingState : disappearedState
state[key] = (fromValue, toValue)
}
animate(delay: targetState.delay)
}
}
/*
* 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 UIKit
internal extension UIView {
func optimizedDuration(fromPosition: CGPoint, toPosition: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval {
let fromPos = fromPosition
let toPos = toPosition ?? fromPos
let fromSize = (layer.presentation() ?? layer).bounds.size
let toSize = size ?? fromSize
let fromTransform = (layer.presentation() ?? layer).transform
let toTransform = transform ?? fromTransform
let realFromPos = CGPoint.zero.transform(fromTransform) + fromPos
let realToPos = CGPoint.zero.transform(toTransform) + toPos
let realFromSize = fromSize.transform(fromTransform)
let realToSize = toSize.transform(toTransform)
let movePoints = (realFromPos.distance(realToPos) + realFromSize.point.distance(realToSize.point))
// duration is 0.2 @ 0 to 0.375 @ 500
let duration = 0.208 + Double(movePoints.clamp(0, 500)) / 3000
return duration
}
}
protocol HasInsertOrder: class {
var insertToViewFirst: Bool { get set }
}
internal class MotionDefaultAnimator<ViewContext: MotionAnimatorViewContext>: MotionAnimator, HasInsertOrder {
weak public var context: MotionContext!
var viewContexts: [UIView: ViewContext] = [:]
internal var insertToViewFirst = false
public func seekTo(timePassed: TimeInterval) {
for viewContext in viewContexts.values {
viewContext.seek(timePassed: timePassed)
}
}
public func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
var duration: TimeInterval = 0
for (_, context) in viewContexts {
context.resume(timePassed: timePassed, reverse: reverse)
duration = max(duration, context.duration)
}
return duration
}
public func apply(state: MotionTargetState, to view: UIView) {
if let context = viewContexts[view] {
context.apply(state:state)
}
}
public func canAnimate(view: UIView, appearing: Bool) -> Bool {
guard let state = context[view] else { return false }
return ViewContext.canAnimate(view: view, state: state, appearing: appearing)
}
public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
if insertToViewFirst {
for v in toViews { animate(view: v, appearing: true) }
for v in fromViews { animate(view: v, appearing: false) }
} else {
for v in fromViews { animate(view: v, appearing: false) }
for v in toViews { animate(view: v, appearing: true) }
}
for viewContext in viewContexts.values {
duration = max(duration, viewContext.duration)
}
return duration
}
func animate(view: UIView, appearing: Bool) {
let snapshot = context.snapshotView(for: view)
let viewContext = ViewContext(animator:self, snapshot: snapshot, targetState: context[view]!)
viewContexts[view] = viewContext
viewContext.startAnimations(appearing: appearing)
}
public func clean() {
for vc in viewContexts.values {
vc.clean()
}
viewContexts.removeAll()
insertToViewFirst = false
}
}
/*
* 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 UIKit
@available(iOS 10, tvOS 10, *)
internal class MotionViewPropertyViewContext: MotionAnimatorViewContext {
var viewPropertyAnimator: UIViewPropertyAnimator?
override class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
return view is UIVisualEffectView && state.opacity != nil
}
override func resume(timePassed: TimeInterval, reverse: Bool) {
viewPropertyAnimator?.finishAnimation(at: reverse ? .start : .end)
}
override func seek(timePassed: TimeInterval) {
viewPropertyAnimator?.pauseAnimation()
viewPropertyAnimator?.fractionComplete = CGFloat(timePassed / duration)
}
override func clean() {
super.clean()
viewPropertyAnimator?.stopAnimation(true)
viewPropertyAnimator = nil
}
override func startAnimations(appearing: Bool) {
guard let visualEffectView = snapshot as? UIVisualEffectView else { return }
let appearedEffect = visualEffectView.effect
let disappearedEffect = targetState.opacity == 0 ? nil : visualEffectView.effect
visualEffectView.effect = appearing ? disappearedEffect : appearedEffect
duration = targetState.duration!
viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
visualEffectView.effect = appearing ? appearedEffect : disappearedEffect
}
viewPropertyAnimator!.startAnimation()
}
}
/*
* 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 UIKit
#if os(iOS)
public class MotionDebugPlugin: MotionPlugin {
public static var showOnTop: Bool = false
var debugView: MotionDebugView?
var zPositionMap = [UIView: CGFloat]()
var addedLayers: [CALayer] = []
var updating = false
override public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
if Motion.shared.forceNotInteractive { return 0 }
var hasArc = false
for v in context.fromViews + context.toViews where context[v]?.arc != nil && context[v]?.position != nil {
hasArc = true
break
}
let debugView = MotionDebugView(initialProcess: Motion.shared.presenting ? 0.0 : 1.0, showCurveButton:hasArc, showOnTop:MotionDebugPlugin.showOnTop)
debugView.frame = Motion.shared.container.bounds
debugView.delegate = self
UIApplication.shared.keyWindow!.addSubview(debugView)
debugView.layoutSubviews()
self.debugView = debugView
UIView.animate(withDuration: 0.4) {
debugView.showControls = true
}
return .infinity
}
public override func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
guard let debugView = debugView else { return 0.4 }
debugView.delegate = nil
UIView.animate(withDuration: 0.4) {
debugView.showControls = false
debugView.debugSlider.setValue(roundf(debugView.progress), animated: true)
}
on3D(wants3D: false)
return 0.4
}
public override func clean() {
debugView?.removeFromSuperview()
debugView = nil
}
}
extension MotionDebugPlugin:MotionDebugViewDelegate {
public func onDone() {
guard let debugView = debugView else { return }
let seekValue = Motion.shared.presenting ? debugView.progress : 1.0 - debugView.progress
if seekValue > 0.5 {
Motion.shared.end()
} else {
Motion.shared.cancel()
}
}
public func onProcessSliderChanged(progress: Float) {
let seekValue = Motion.shared.presenting ? progress : 1.0 - progress
Motion.shared.update(progress: Double(seekValue))
}
func onPerspectiveChanged(translation: CGPoint, rotation: CGFloat, scale: CGFloat) {
var t = CATransform3DIdentity
t.m34 = -1 / 4000
t = CATransform3DTranslate(t, translation.x, translation.y, 0)
t = CATransform3DScale(t, scale, scale, 1)
t = CATransform3DRotate(t, rotation, 0, 1, 0)
Motion.shared.container.layer.sublayerTransform = t
}
func animateZPosition(view: UIView, to: CGFloat) {
let a = CABasicAnimation(keyPath: "zPosition")
a.fromValue = view.layer.value(forKeyPath: "zPosition")
a.toValue = NSNumber(value: Double(to))
a.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
a.duration = 0.4
view.layer.add(a, forKey: "zPosition")
view.layer.zPosition = to
}
func onDisplayArcCurve(wantsCurve: Bool) {
for layer in addedLayers {
layer.removeFromSuperlayer()
addedLayers.removeAll()
}
if wantsCurve {
for layer in Motion.shared.container.layer.sublayers! {
for (_, anim) in layer.animations {
if let keyframeAnim = anim as? CAKeyframeAnimation, let path = keyframeAnim.path {
let s = CAShapeLayer()
s.zPosition = layer.zPosition + 10
s.path = path
s.strokeColor = UIColor.blue.cgColor
s.fillColor = UIColor.clear.cgColor
Motion.shared.container.layer.addSublayer(s)
addedLayers.append(s)
}
}
}
}
}
func on3D(wants3D: Bool) {
var t = CATransform3DIdentity
if wants3D {
var viewsWithZPosition = Set<UIView>()
for view in Motion.shared.container.subviews where view.layer.zPosition != 0 {
viewsWithZPosition.insert(view)
zPositionMap[view] = view.layer.zPosition
}
let viewsWithoutZPosition = Motion.shared.container.subviews.filter { return !viewsWithZPosition.contains($0) }
let viewsWithPositiveZPosition = viewsWithZPosition.filter { return $0.layer.zPosition > 0 }
for (i, v) in viewsWithoutZPosition.enumerated() {
animateZPosition(view:v, to:CGFloat(i * 10))
}
var maxZPosition: CGFloat = 0
for v in viewsWithPositiveZPosition {
maxZPosition = max(maxZPosition, v.layer.zPosition)
animateZPosition(view:v, to:v.layer.zPosition + CGFloat(viewsWithoutZPosition.count * 10))
}
t.m34 = -1 / 4000
t = CATransform3DTranslate(t, debugView!.translation.x, debugView!.translation.y, 0)
t = CATransform3DScale(t, debugView!.scale, debugView!.scale, 1)
t = CATransform3DRotate(t, debugView!.rotation, 0, 1, 0)
} else {
for v in Motion.shared.container.subviews {
animateZPosition(view:v, to:self.zPositionMap[v] ?? 0)
}
self.zPositionMap.removeAll()
}
let a = CABasicAnimation(keyPath: "sublayerTransform")
a.fromValue = Motion.shared.container.layer.value(forKeyPath: "sublayerTransform")
a.toValue = NSValue(caTransform3D: t)
a.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
a.duration = 0.4
UIView.animate(withDuration:0.4) {
self.context.container.backgroundColor = UIColor(white: 0.85, alpha: 1.0)
}
Motion.shared.container.layer.add(a, forKey: "debug")
Motion.shared.container.layer.sublayerTransform = t
}
}
#endif
/*
* 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 UIKit
#if os(iOS)
protocol MotionDebugViewDelegate: class {
func onProcessSliderChanged(progress: Float)
func onPerspectiveChanged(translation: CGPoint, rotation: CGFloat, scale: CGFloat)
func on3D(wants3D: Bool)
func onDisplayArcCurve(wantsCurve: Bool)
func onDone()
}
class MotionDebugView: UIView {
var backgroundView: UIView!
var debugSlider: UISlider!
var perspectiveButton: UIButton!
var doneButton: UIButton!
var arcCurveButton: UIButton?
weak var delegate: MotionDebugViewDelegate?
var panGR: UIPanGestureRecognizer!
var pinchGR: UIPinchGestureRecognizer!
var showControls: Bool = false {
didSet {
layoutSubviews()
}
}
var showOnTop: Bool = false
var rotation: CGFloat = π / 6
var scale: CGFloat = 0.6
var translation: CGPoint = .zero
var progress: Float {
return debugSlider.value
}
init(initialProcess: Float, showCurveButton: Bool, showOnTop: Bool) {
super.init(frame:.zero)
self.showOnTop = showOnTop
backgroundView = UIView(frame:.zero)
backgroundView.backgroundColor = UIColor(white: 1.0, alpha: 0.95)
backgroundView.layer.shadowColor = UIColor.darkGray.cgColor
backgroundView.layer.shadowOpacity = 0.3
backgroundView.layer.shadowRadius = 5
backgroundView.layer.shadowOffset = CGSize.zero
addSubview(backgroundView)
doneButton = UIButton(type: .system)
doneButton.setTitle("Done", for: .normal)
doneButton.addTarget(self, action: #selector(onDone), for: .touchUpInside)
backgroundView.addSubview(doneButton)
perspectiveButton = UIButton(type: .system)
perspectiveButton.setTitle("3D View", for: .normal)
perspectiveButton.addTarget(self, action: #selector(onPerspective), for: .touchUpInside)
backgroundView.addSubview(perspectiveButton)
if showCurveButton {
arcCurveButton = UIButton(type: .system)
arcCurveButton!.setTitle("Show Arcs", for: .normal)
arcCurveButton!.addTarget(self, action: #selector(onDisplayArcCurve), for: .touchUpInside)
backgroundView.addSubview(arcCurveButton!)
}
debugSlider = UISlider(frame: .zero)
debugSlider.layer.zPosition = 1000
debugSlider.minimumValue = 0
debugSlider.maximumValue = 1
debugSlider.addTarget(self, action: #selector(onSlide), for: .valueChanged)
debugSlider.isUserInteractionEnabled = true
debugSlider.value = initialProcess
backgroundView.addSubview(debugSlider)
panGR = UIPanGestureRecognizer(target: self, action: #selector(pan))
panGR.delegate = self
panGR.maximumNumberOfTouches = 1
addGestureRecognizer(panGR)
pinchGR = UIPinchGestureRecognizer(target: self, action: #selector(pinch))
pinchGR.delegate = self
addGestureRecognizer(pinchGR)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func layoutSubviews() {
super.layoutSubviews()
var backgroundFrame = bounds
backgroundFrame.size.height = 72
if showOnTop {
backgroundFrame.origin.y = showControls ? 0 : -80
} else {
backgroundFrame.origin.y = bounds.maxY - CGFloat(showControls ? 72.0 : -8.0)
}
backgroundView.frame = backgroundFrame
var sliderFrame = bounds.insetBy(dx: 10, dy: 0)
sliderFrame.size.height = 44
sliderFrame.origin.y = 28
debugSlider.frame = sliderFrame
perspectiveButton.sizeToFit()
perspectiveButton.frame.origin = CGPoint(x:bounds.maxX - perspectiveButton.bounds.width - 10, y: 4)
doneButton.sizeToFit()
doneButton.frame.origin = CGPoint(x:10, y: 4)
arcCurveButton?.sizeToFit()
arcCurveButton?.center = CGPoint(x: center.x, y: doneButton.center.y)
}
var startRotation: CGFloat = 0
@objc public func pan() {
if panGR.state == .began {
startRotation = rotation
}
rotation = startRotation + panGR.translation(in: nil).x / 150
if rotation > π {
rotation -= 2 * π
} else if rotation < -π {
rotation += 2 * π
}
delegate?.onPerspectiveChanged(translation:translation, rotation: rotation, scale:scale)
}
var startLocation: CGPoint = .zero
var startTranslation: CGPoint = .zero
var startScale: CGFloat = 1
@objc public func pinch() {
switch pinchGR.state {
case .began:
startLocation = pinchGR.location(in: nil)
startTranslation = translation
startScale = scale
fallthrough
case .changed:
if pinchGR.numberOfTouches >= 2 {
scale = min(1, max(0.2, startScale * pinchGR.scale))
translation = startTranslation + pinchGR.location(in: nil) - startLocation
delegate?.onPerspectiveChanged(translation:translation, rotation: rotation, scale:scale)
}
default:
break
}
}
@objc public func onDone() {
delegate?.onDone()
}
@objc public func onPerspective() {
perspectiveButton.isSelected = !perspectiveButton.isSelected
delegate?.on3D(wants3D: perspectiveButton.isSelected)
}
@objc public func onDisplayArcCurve() {
arcCurveButton!.isSelected = !arcCurveButton!.isSelected
delegate?.onDisplayArcCurve(wantsCurve: arcCurveButton!.isSelected)
}
@objc public func onSlide() {
delegate?.onProcessSliderChanged(progress: debugSlider.value)
}
}
extension MotionDebugView:UIGestureRecognizerDelegate {
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return perspectiveButton.isSelected
}
}
#endif
/*
* 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 UIKit
public enum MotionDefaultAnimationType {
public enum Direction: MotionStringConvertible {
case left, right, up, down
public static func from(node: ExprNode) -> Direction? {
switch node.name {
case "left": return .left
case "right": return .right
case "up": return .up
case "down": return .down
default: return nil
}
}
}
case auto
case push(direction: Direction)
case pull(direction: Direction)
case cover(direction: Direction)
case uncover(direction: Direction)
case slide(direction: Direction)
case zoomSlide(direction: Direction)
case pageIn(direction: Direction)
case pageOut(direction: Direction)
case fade
case zoom
case zoomOut
indirect case selectBy(presenting: MotionDefaultAnimationType, dismissing: MotionDefaultAnimationType)
public static func autoReverse(presenting: MotionDefaultAnimationType) -> MotionDefaultAnimationType {
return .selectBy(presenting: presenting, dismissing: presenting.reversed())
}
case none
func reversed() -> MotionDefaultAnimationType {
switch self {
case .push(direction: .up):
return .pull(direction: .down)
case .push(direction: .right):
return .pull(direction: .left)
case .push(direction: .down):
return .pull(direction: .up)
case .push(direction: .left):
return .pull(direction: .right)
case .pull(direction: .up):
return .push(direction: .down)
case .pull(direction: .right):
return .push(direction: .left)
case .pull(direction: .down):
return .push(direction: .up)
case .pull(direction: .left):
return .push(direction: .right)
case .cover(direction: .up):
return .uncover(direction: .down)
case .cover(direction: .right):
return .uncover(direction: .left)
case .cover(direction: .down):
return .uncover(direction: .up)
case .cover(direction: .left):
return .uncover(direction: .right)
case .uncover(direction: .up):
return .cover(direction: .down)
case .uncover(direction: .right):
return .cover(direction: .left)
case .uncover(direction: .down):
return .cover(direction: .up)
case .uncover(direction: .left):
return .cover(direction: .right)
case .slide(direction: .up):
return .slide(direction: .down)
case .slide(direction: .down):
return .slide(direction: .up)
case .slide(direction: .left):
return .slide(direction: .right)
case .slide(direction: .right):
return .slide(direction: .left)
case .zoomSlide(direction: .up):
return .zoomSlide(direction: .down)
case .zoomSlide(direction: .down):
return .zoomSlide(direction: .up)
case .zoomSlide(direction: .left):
return .zoomSlide(direction: .right)
case .zoomSlide(direction: .right):
return .zoomSlide(direction: .left)
case .pageIn(direction: .up):
return .pageOut(direction: .down)
case .pageIn(direction: .right):
return .pageOut(direction: .left)
case .pageIn(direction: .down):
return .pageOut(direction: .up)
case .pageIn(direction: .left):
return .pageOut(direction: .right)
case .pageOut(direction: .up):
return .pageIn(direction: .down)
case .pageOut(direction: .right):
return .pageIn(direction: .left)
case .pageOut(direction: .down):
return .pageIn(direction: .up)
case .pageOut(direction: .left):
return .pageIn(direction: .right)
case .zoom:
return .zoomOut
case .zoomOut:
return .zoom
default:
return self
}
}
public var label: String? {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
let valuesMirror = Mirror(reflecting: associated.value)
if !valuesMirror.children.isEmpty {
let parameters = valuesMirror.children.map { ".\($0.value)" }.joined(separator: ",")
return ".\(associated.label ?? "")(\(parameters))"
}
return ".\(associated.label ?? "")(.\(associated.value))"
}
return ".\(self)"
}
}
extension MotionDefaultAnimationType: MotionStringConvertible {
public static func from(node: ExprNode) -> MotionDefaultAnimationType? {
let name: String = node.name
let parameters: [ExprNode] = (node as? CallNode)?.arguments ?? []
switch name {
case "auto":
return .auto
case "push":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .push(direction: direction)
}
case "pull":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .pull(direction: direction)
}
case "cover":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .cover(direction: direction)
}
case "uncover":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .uncover(direction: direction)
}
case "slide":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .slide(direction: direction)
}
case "zoomSlide":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .zoomSlide(direction: direction)
}
case "pageIn":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .pageIn(direction: direction)
}
case "pageOut":
if let node = parameters.get(0), let direction = Direction.from(node: node) {
return .pageOut(direction: direction)
}
case "fade": return .fade
case "zoom": return .zoom
case "zoomOut": return .zoomOut
case "selectBy":
if let presentingNode = parameters.get(0),
let presenting = MotionDefaultAnimationType.from(node: presentingNode),
let dismissingNode = parameters.get(1),
let dismissing = MotionDefaultAnimationType.from(node: dismissingNode) {
return .selectBy(presenting: presenting, dismissing: dismissing)
}
case "none": return .none
default: break
}
return nil
}
}
class DefaultAnimationPreprocessor: BasePreprocessor {
weak var motion: Motion?
init(motion: Motion) {
self.motion = motion
}
func shift(direction: MotionDefaultAnimationType.Direction, appearing: Bool, size: CGSize? = nil, transpose: Bool = false) -> CGPoint {
let size = size ?? context.container.bounds.size
let rtn: CGPoint
switch direction {
case .left, .right:
rtn = CGPoint(x: (direction == .right) == appearing ? -size.width : size.width, y: 0)
case .up, .down:
rtn = CGPoint(x: 0, y: (direction == .down) == appearing ? -size.height : size.height)
}
if transpose {
return CGPoint(x: rtn.y, y: rtn.x)
}
return rtn
}
override func process(fromViews: [UIView], toViews: [UIView]) {
guard let motion = motion else { return }
var defaultAnimation = motion.defaultAnimation
let inNavigationController = motion.inNavigationController
let inTabBarController = motion.inTabBarController
let toViewController = motion.toViewController
let fromViewController = motion.fromViewController
let presenting = motion.presenting
let fromOverFullScreen = motion.fromOverFullScreen
let toOverFullScreen = motion.toOverFullScreen
let toView = motion.toView
let fromView = motion.fromView
let animators = motion.animators
if case .auto = defaultAnimation {
if inNavigationController, let navAnim = toViewController?.navigationController?.motionNavigationAnimationType {
defaultAnimation = navAnim
} else if inTabBarController, let tabAnim = toViewController?.tabBarController?.motionTabBarAnimationType {
defaultAnimation = tabAnim
} else if let modalAnim = (presenting ? toViewController : fromViewController)?.motionModalAnimationType {
defaultAnimation = modalAnim
}
}
if case .selectBy(let presentAnim, let dismissAnim) = defaultAnimation {
defaultAnimation = presenting ? presentAnim : dismissAnim
}
if case .auto = defaultAnimation {
if animators!.contains(where: { $0.canAnimate(view: toView, appearing: true) || $0.canAnimate(view: fromView, appearing: false) }) {
defaultAnimation = .none
} else if inNavigationController {
defaultAnimation = presenting ? .push(direction:.left) : .pull(direction:.right)
} else if inTabBarController {
defaultAnimation = presenting ? .slide(direction:.left) : .slide(direction:.right)
} else {
defaultAnimation = .fade
}
}
if case .none = defaultAnimation {
return
}
context[fromView] = [.timingFunction(.standard), .duration(0.35)]
context[toView] = [.timingFunction(.standard), .duration(0.35)]
let shadowState: [MotionTransition] = [.shadowOpacity(0.5),
.shadowColor(.black),
.shadowRadius(5),
.shadowOffset(.zero),
.masksToBounds(false)]
switch defaultAnimation {
case .push(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false) / 3),
.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .pull(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true) / 3),
.overlay(color: .black, opacity: 0.1)])
case .slide(let direction):
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false))])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true))])
case .zoomSlide(let direction):
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)), .scale(0.8)])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)), .scale(0.8)])
case .cover(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .uncover(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1)])
case .pageIn(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.scale(0.7),
.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .pageOut(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.scale(0.7),
.overlay(color: .black, opacity: 0.1)])
case .fade:
// TODO: clean up this. overFullScreen logic shouldn't be here
if !(fromOverFullScreen && !presenting) {
context[toView] = [.fade]
}
#if os(tvOS)
context[fromView] = [.fade]
#else
if (!presenting && toOverFullScreen) || !fromView.isOpaque || (fromView.backgroundColor?.alphaComponent ?? 1) < 1 {
context[fromView] = [.fade]
}
#endif
context[toView]!.append(.durationMatchLongest)
context[fromView]!.append(.durationMatchLongest)
case .zoom:
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.scale(1.3), .fade])
context[toView]!.append(contentsOf: [.scale(0.7)])
case .zoomOut:
context[toView]!.append(contentsOf: [.scale(1.3), .fade])
context[fromView]!.append(contentsOf: [.scale(0.7)])
default:
fatalError("Not implemented")
}
}
}
// The MIT License (MIT)
//
// 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 UIKit
public class DurationPreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
var duration: TimeInterval = 0
duration = applyOptimizedDuration(for: fromViews)
duration = max(duration, applyOptimizedDuration(for: toViews))
set(duration: duration, for: fromViews)
set(duration: duration, for: toViews)
}
}
extension DurationPreprocessor {
fileprivate func set(duration: TimeInterval, for views: [UIView]) {
for view in views where .infinity == context.viewToMotionTransitionState[view]?.duration {
context.viewToMotionTransitionState[view]?.duration = duration
}
}
fileprivate func applyOptimizedDuration(for views: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
for v in views {
guard var state = context.viewToMotionTransitionState[v] else {
continue
}
if state.duration == nil {
state.duration = optimizedDuration(for: v)
}
if state.duration! == .infinity {
duration = max(duration, optimizedDuration(for: v))
} else {
duration = max(duration, state.duration!)
}
}
return duration
}
fileprivate func optimizedDuration(for view: UIView) -> TimeInterval {
guard let state = context.viewToMotionTransitionState[view] else {
return 0
}
return view.optimizedDuration(fromPosition: context.container.convert(view.layer.position, from: view.superview), toPosition: state.position, size: state.size, transform: state.transform)
}
}
/*
* 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 UIKit
internal extension CALayer {
// return all animations running by this layer.
// the returned value is mutable
var animations: [(String, CAAnimation)] {
if let keys = animationKeys() {
return keys.map { return ($0, self.animation(forKey: $0)!.copy() as! CAAnimation) }
}
return []
}
}
/*
* 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 UIKit
internal extension CAMediaTimingFunction {
// default
static let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
static let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
static let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
static let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
// material
static let standard = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.2, 1.0)
static let deceleration = CAMediaTimingFunction(controlPoints: 0.0, 0.0, 0.2, 1)
static let acceleration = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 1, 1)
static let sharp = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.6, 1)
// easing.net
static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.275)
static func from(name: String) -> CAMediaTimingFunction? {
switch name {
case "linear":
return .linear
case "easeIn":
return .easeIn
case "easeOut":
return .easeOut
case "easeInOut":
return .easeInOut
case "standard":
return .standard
case "deceleration":
return .deceleration
case "acceleration":
return .acceleration
case "sharp":
return .sharp
default:
return nil
}
}
}
/*
* 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 MetalKit
let π = CGFloat.pi
internal struct KeySet<Key: Hashable, Value: Hashable> {
var dict: [Key:Set<Value>] = [:]
internal subscript(key: Key) -> Set<Value> {
mutating get {
if dict[key] == nil {
dict[key] = Set<Value>()
}
return dict[key]!
}
set {
dict[key] = newValue
}
}
}
internal extension CGSize {
internal var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
internal var point: CGPoint {
return CGPoint(x: width, y: height)
}
internal func transform(_ t: CGAffineTransform) -> CGSize {
return self.applying(t)
}
internal func transform(_ t: CATransform3D) -> CGSize {
return self.applying(CATransform3DGetAffineTransform(t))
}
}
internal extension CGRect {
internal var center: CGPoint {
return CGPoint(x: origin.x + size.width/2, y: origin.y + size.height/2)
}
internal var bounds: CGRect {
return CGRect(origin: CGPoint.zero, size: size)
}
init(center: CGPoint, size: CGSize) {
self.init(x: center.x - size.width/2, y: center.y - size.height/2, width: size.width, height: size.height)
}
}
extension CGFloat {
internal func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat {
return self < a ? a : (self > b ? b : self)
}
}
extension CGPoint {
internal func translate(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
return CGPoint(x: self.x+dx, y: self.y+dy)
}
internal func transform(_ t: CGAffineTransform) -> CGPoint {
return self.applying(t)
}
internal func transform(_ t: CATransform3D) -> CGPoint {
return self.applying(CATransform3DGetAffineTransform(t))
}
internal func distance(_ b: CGPoint) -> CGFloat {
return sqrt(pow(self.x - b.x, 2) + pow(self.y - b.y, 2))
}
}
internal func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
internal func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
internal func / (left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x/right, y: left.y/right)
}
internal func / (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x/right.x, y: left.y/right.y)
}
internal func * (left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x*right, y: left.y*right)
}
internal func * (left: CGPoint, right: CGSize) -> CGPoint {
return CGPoint(x: left.x*right.width, y: left.y*right.width)
}
internal func * (left: CGFloat, right: CGPoint) -> CGPoint {
return right * left
}
internal func * (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x*right.x, y: left.y*right.y)
}
internal prefix func - (point: CGPoint) -> CGPoint {
return CGPoint.zero - point
}
internal func abs(_ p: CGPoint) -> CGPoint {
return CGPoint(x: abs(p.x), y: abs(p.y))
}
internal func * (left: CGSize, right: CGFloat) -> CGSize {
return CGSize(width: left.width*right, height: left.height*right)
}
internal func * (left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width*right.width, height: left.height*right.width)
}
internal func / (left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width/right.width, height: left.height/right.height)
}
internal func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
var lhs = lhs
var rhs = rhs
return memcmp(&lhs, &rhs, MemoryLayout<CATransform3D>.size) == 0
}
internal func != (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
return !(lhs == rhs)
}
/*
* 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
func delay(_ time: Double, execute: @escaping () -> Void) {
if time > 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + time, execute: execute)
} else {
DispatchQueue.main.async(execute: execute)
}
}
/*
* 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 UIKit
fileprivate let parameterRegex = "(?:\\-?\\d+(\\.?\\d+)?)|\\w+"
fileprivate let modifiersRegex = "(\\w+)(?:\\(([^\\)]*)\\))?"
internal extension NSObject {
func copyWithArchiver() -> Any? {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self))!
}
}
internal extension UIImage {
class func imageWithView(view: UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0.0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
internal extension UIColor {
var components:(r:CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
getRed(&r, green: &g, blue: &b, alpha: &a)
return (r, g, b, a)
}
var alphaComponent: CGFloat {
return components.a
}
}
/*
* 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 UIKit
public extension UIView {
private struct AssociatedKeys {
static var motionID = "motionID"
static var motionModifiers = "motionModifers"
static var motionStoredAlpha = "motionStoredAlpha"
static var motionEnabled = "motionEnabled"
}
/**
**motionID** is the identifier for the view. When doing a transition between two view controllers,
Motion will search through all the subviews for both view controllers and matches views with the same **motionID**.
Whenever a pair is discovered,
Motion will automatically transit the views from source state to the destination state.
*/
@IBInspectable public var motionID: String? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.motionID) as? String }
set { objc_setAssociatedObject(self, &AssociatedKeys.motionID, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
/**
**isMotionEnabled** allows to specify whether a view and its subviews should be consider for animations.
If true, Motion will search through all the subviews for motionIds and modifiers. Defaults to true
*/
@IBInspectable public var isMotionEnabled: Bool {
get { return objc_getAssociatedObject(self, &AssociatedKeys.motionEnabled) as? Bool ?? true }
set { objc_setAssociatedObject(self, &AssociatedKeys.motionEnabled, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
/**
Use **motionModifiers** to specify animations alongside the main transition. Checkout `MotionTransition.swift` for available modifiers.
*/
public var motionModifiers: [MotionTransition]? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.motionModifiers) as? [MotionTransition] }
set { objc_setAssociatedObject(self, &AssociatedKeys.motionModifiers, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
/**
**motionModifierString** provides another way to set **motionModifiers**. It can be assigned through storyboard.
*/
@IBInspectable public var motionModifierString: String? {
get { fatalError("Reverse lookup is not supported") }
set { motionModifiers = newValue?.parse() }
}
internal func slowSnapshotView() -> UIView {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let imageView = UIImageView(image: image)
imageView.frame = bounds
let snapshotView = UIView(frame:bounds)
snapshotView.addSubview(imageView)
return snapshotView
}
internal var flattenedViewHierarchy: [UIView] {
guard isMotionEnabled else { return [] }
if #available(iOS 9.0, *) {
return isHidden && (superview is UICollectionView || superview is UIStackView || self is UITableViewCell) ? [] : ([self] + subviews.flatMap { $0.flattenedViewHierarchy })
}
return isHidden && (superview is UICollectionView || self is UITableViewCell) ? [] : ([self] + subviews.flatMap { $0.flattenedViewHierarchy })
}
/// Used for .overFullScreen presentation
internal var motionStoredAlpha: CGFloat? {
get {
if let doubleValue = (objc_getAssociatedObject(self, &AssociatedKeys.motionStoredAlpha) as? NSNumber)?.doubleValue {
return CGFloat(doubleValue)
}
return nil
}
set {
if let newValue = newValue {
objc_setAssociatedObject(self, &AssociatedKeys.motionStoredAlpha, NSNumber(value:newValue.native), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} else {
objc_setAssociatedObject(self, &AssociatedKeys.motionStoredAlpha, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
/*
* 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 UIKit
internal class MotionViewControllerConfig: NSObject {
var modalAnimation: MotionDefaultAnimationType = .auto
var navigationAnimation: MotionDefaultAnimationType = .auto
var tabBarAnimation: MotionDefaultAnimationType = .auto
var storedSnapshot: UIView?
var previousNavigationDelegate: UINavigationControllerDelegate?
var previousTabBarDelegate: UITabBarControllerDelegate?
}
public extension UIViewController {
private struct AssociatedKeys {
static var motionConfig = "motionConfig"
}
internal var motionConfig: MotionViewControllerConfig {
get {
if let config = objc_getAssociatedObject(self, &AssociatedKeys.motionConfig) as? MotionViewControllerConfig {
return config
}
let config = MotionViewControllerConfig()
self.motionConfig = config
return config
}
set { objc_setAssociatedObject(self, &AssociatedKeys.motionConfig, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
internal var previousNavigationDelegate: UINavigationControllerDelegate? {
get { return motionConfig.previousNavigationDelegate }
set { motionConfig.previousNavigationDelegate = newValue }
}
internal var previousTabBarDelegate: UITabBarControllerDelegate? {
get { return motionConfig.previousTabBarDelegate }
set { motionConfig.previousTabBarDelegate = newValue }
}
/// used for .overFullScreen presentation
internal var motionStoredSnapshot: UIView? {
get { return motionConfig.storedSnapshot }
set { motionConfig.storedSnapshot = newValue }
}
/// default motion animation type for presenting & dismissing modally
public var motionModalAnimationType: MotionDefaultAnimationType {
get { return motionConfig.modalAnimation }
set { motionConfig.modalAnimation = newValue }
}
@IBInspectable public var motionModalAnimationTypeString: String? {
get { return motionConfig.modalAnimation.label }
set { motionConfig.modalAnimation = newValue?.parseOne() ?? .auto }
}
@IBInspectable public var isMotionEnabled: Bool {
get {
return transitioningDelegate is Motion
}
set {
guard newValue != isMotionEnabled else { return }
if newValue {
transitioningDelegate = Motion.shared
if let navi = self as? UINavigationController {
previousNavigationDelegate = navi.delegate
navi.delegate = Motion.shared
}
if let tab = self as? UITabBarController {
previousTabBarDelegate = tab.delegate
tab.delegate = Motion.shared
}
} else {
transitioningDelegate = nil
if let navi = self as? UINavigationController, navi.delegate is Motion {
navi.delegate = previousNavigationDelegate
}
if let tab = self as? UITabBarController, tab.delegate is Motion {
tab.delegate = previousTabBarDelegate
}
}
}
}
}
extension UINavigationController {
/// default motion animation type for push and pop within the navigation controller
public var motionNavigationAnimationType: MotionDefaultAnimationType {
get { return motionConfig.navigationAnimation }
set { motionConfig.navigationAnimation = newValue }
}
@IBInspectable public var motionNavigationAnimationTypeString: String? {
get { return motionConfig.navigationAnimation.label }
set { motionConfig.navigationAnimation = newValue?.parseOne() ?? .auto }
}
}
extension UITabBarController {
/// default motion animation type for switching tabs within the tab bar controller
public var motionTabBarAnimationType: MotionDefaultAnimationType {
get { return motionConfig.tabBarAnimation }
set { motionConfig.tabBarAnimation = newValue }
}
@IBInspectable public var motionTabBarAnimationTypeString: String? {
get { return motionConfig.tabBarAnimation.label }
set { motionConfig.tabBarAnimation = newValue?.parseOne() ?? .auto }
}
}
extension UIViewController {
@available(*, deprecated: 0.1.4, message: "use motion_dismissViewController instead")
@IBAction public func ht_dismiss(_ sender: UIView) {
motion_dismissViewController()
}
@available(*, deprecated: 0.1.4, message: "use motion_replaceViewController(with:) instead")
public func motionReplaceViewController(with next: UIViewController) {
motion_replaceViewController(with: next)
}
/**
Dismiss the current view controller with animation. Will perform a navigationController.popViewController
if the current view controller is contained inside a navigationController
*/
@IBAction public func motion_dismissViewController() {
if let navigationController = navigationController, navigationController.viewControllers.first != self {
navigationController.popViewController(animated: true)
} else {
dismiss(animated: true, completion: nil)
}
}
/**
Unwind to the root view controller using Motion
*/
@IBAction public func motion_unwindToRootViewController() {
motion_unwindToViewController { $0.presentingViewController == nil }
}
/**
Unwind to a specific view controller using Motion
*/
public func motion_unwindToViewController(_ toViewController: UIViewController) {
motion_unwindToViewController { $0 == toViewController }
}
/**
Unwind to a view controller that responds to the given selector using Motion
*/
public func motion_unwindToViewController(withSelector: Selector) {
motion_unwindToViewController { $0.responds(to: withSelector) }
}
/**
Unwind to a view controller with given class using Motion
*/
public func motion_unwindToViewController(withClass: AnyClass) {
motion_unwindToViewController { $0.isKind(of: withClass) }
}
/**
Unwind to a view controller that the matchBlock returns true on.
*/
public func motion_unwindToViewController(withMatchBlock: (UIViewController) -> Bool) {
var target: UIViewController? = nil
var current: UIViewController? = self
while target == nil && current != nil {
if let childViewControllers = (current as? UINavigationController)?.childViewControllers ?? current!.navigationController?.childViewControllers {
for vc in childViewControllers.reversed() {
if vc != self, withMatchBlock(vc) {
target = vc
break
}
}
}
if target == nil {
current = current!.presentingViewController
if let vc = current, withMatchBlock(vc) == true {
target = vc
}
}
}
if let target = target {
if target.presentedViewController != nil {
_ = target.navigationController?.popToViewController(target, animated: false)
let fromVC = self.navigationController ?? self
let toVC = target.navigationController ?? target
if target.presentedViewController != fromVC {
// UIKit's UIViewController.dismiss will jump to target.presentedViewController then perform the dismiss.
// We overcome this behavior by inserting a snapshot into target.presentedViewController
// And also force Motion to use the current VC as the fromViewController
Motion.shared.fromViewController = fromVC
let snapshotView = fromVC.view.snapshotView(afterScreenUpdates: true)!
toVC.presentedViewController!.view.addSubview(snapshotView)
}
toVC.dismiss(animated: true, completion: nil)
} else {
_ = target.navigationController?.popToViewController(target, animated: true)
}
} else {
// unwind target not found
}
}
/**
Replace the current view controller with another VC on the navigation/modal stack.
*/
public func motion_replaceViewController(with next: UIViewController) {
if Motion.shared.transitioning {
print("motion_replaceViewController cancelled because Motion was doing a transition. Use Motion.shared.cancel(animated:false) or Motion.shared.end(animated:false) to stop the transition first before calling motion_replaceViewController.")
return
}
if let navigationController = navigationController {
var vcs = navigationController.childViewControllers
if !vcs.isEmpty {
vcs.removeLast()
vcs.append(next)
}
if navigationController.isMotionEnabled {
Motion.shared.forceNotInteractive = true
}
navigationController.setViewControllers(vcs, animated: true)
} else if let container = view.superview {
let parentVC = presentingViewController
Motion.shared.transition(from: self, to: next, in: container) { finished in
if finished {
UIApplication.shared.keyWindow?.addSubview(next.view)
if let parentVC = parentVC {
self.dismiss(animated: false) {
parentVC.present(next, animated: false, completion:nil)
}
} else {
UIApplication.shared.keyWindow?.rootViewController = next
}
}
}
}
}
}
/*
* 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 UIKit
internal extension Array {
func get(_ index: Int) -> Element? {
if index < count {
return self[index]
}
return nil
}
}
internal extension Array where Element: ExprNode {
func getCGFloat(_ index: Int) -> CGFloat? {
if let s = get(index) as? NumberNode {
return CGFloat(s.value)
}
return nil
}
func getDouble(_ index: Int) -> Double? {
if let s = get(index) as? NumberNode {
return Double(s.value)
}
return nil
}
func getFloat(_ index: Int) -> Float? {
if let s = get(index) as? NumberNode {
return s.value
}
return nil
}
func getBool(_ index: Int) -> Bool? {
if let s = get(index) as? VariableNode, let f = Bool(s.name) {
return f
}
return nil
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public class MatchPreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let identifier = tv.motionIdentifier else {
return
}
guard let fv = context.sourceIdentifierToView[identifier] else {
continue
}
var tvState = context.viewToMotionTransitionState[tv] ?? MotionTransitionState()
var fvState = context.viewToMotionTransitionState[fv] ?? MotionTransitionState()
if let v = tvState.startStateIfMatched {
tvState.append(.startWith(animations: v))
}
if let v = fvState.startStateIfMatched {
fvState.append(.startWith(animations: v))
}
fvState.motionIdentifier = identifier
fvState.arc = tvState.arc
fvState.duration = tvState.duration
fvState.timingFunction = tvState.timingFunction
fvState.delay = tvState.delay
fvState.spring = tvState.spring
tvState.motionIdentifier = identifier
tvState.opacity = 0
if !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1 {
fvState.opacity = 0
} else {
fvState.opacity = nil
if !fv.layer.masksToBounds && fvState.displayShadow {
tvState.displayShadow = false
}
}
context.viewToMotionTransitionState[tv] = tvState
context.viewToMotionTransitionState[fv] = fvState
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public enum MotionAnimationKeyPath: String {
case backgroundColor
case barTintColor
case borderColor
case borderWidth
case cornerRadius
case transform
case rotation = "transform.rotation"
case rotationX = "transform.rotation.x"
case rotationY = "transform.rotation.y"
case rotationZ = "transform.rotation.z"
case scale = "transform.scale"
case scaleX = "transform.scale.x"
case scaleY = "transform.scale.y"
case scaleZ = "transform.scale.z"
case translation = "transform.translation"
case translationX = "transform.translation.x"
case translationY = "transform.translation.y"
case translationZ = "transform.translation.z"
case position
case opacity
case zPosition
case width = "bounds.size.width"
case height = "bounds.size.height"
case size = "bounds.size"
case shadowPath
case shadowColor
case shadowOffset
case shadowOpacity
case shadowRadius
}
extension CABasicAnimation {
/**
A convenience initializer that takes a given MotionAnimationKeyPath.
- Parameter keyPath: An MotionAnimationKeyPath.
*/
public convenience init(keyPath: MotionAnimationKeyPath) {
self.init(keyPath: keyPath.rawValue)
}
}
public struct MotionBasicAnimation {
/**
Creates a CABasicAnimation for the backgroundColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func background(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .backgroundColor)
animation.toValue = color.cgColor
return animation
}
/**
Creates a CABasicAnimation for the barTintColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func barTint(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .barTintColor)
animation.toValue = color.cgColor
return animation
}
/**
Creates a CABasicAnimation for the borderColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func border(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .borderColor)
animation.toValue = color.cgColor
return animation
}
/**
Creates a CABasicAnimation for the borderWidth key path.
- Parameter width: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func border(width: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .borderWidth)
animation.toValue = width
return animation
}
/**
Creates a CABasicAnimation for the cornerRadius key path.
- Parameter radius: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func corner(radius: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .cornerRadius)
animation.toValue = radius
return animation
}
/**
Creates a CABasicAnimation for the transform key path.
- Parameter transform: A CATransform3D object.
- Returns: A CABasicAnimation.
*/
public static func transform(transform: CATransform3D) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .transform)
animation.toValue = NSValue(caTransform3D: transform)
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotation(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.x key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationX(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.y key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationY(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.z key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationZ(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spin(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.x key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spinX(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.y key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spinY(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.z key path.
- Parameter rotations: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func spinZ(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scale(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scale)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale.x key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scaleX(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleX)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale.y key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scaleY(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleY)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.scale.z key path.
- Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func scaleZ(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleZ)
animation.toValue = NSNumber(value: Double(scale))
return animation
}
/**
Creates a CABasicAnimation for the transform.translation key path.
- Parameter point: A CGPoint.
- Returns: A CABasicAnimation.
*/
public static func translate(to point: CGPoint) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translation)
animation.toValue = NSValue(cgPoint: point)
return animation
}
/**
Creates a CABasicAnimation for the transform.translation.x key path.
- Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func translateX(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationX)
animation.toValue = NSNumber(value: Double(translation))
return animation
}
/**
Creates a CABasicAnimation for the transform.translation.y key path.
- Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func translateY(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationY)
animation.toValue = NSNumber(value: Double(translation))
return animation
}
/**
Creates a CABasicAnimation for the transform.translation.z key path.
- Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func translateZ(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationZ)
animation.toValue = NSNumber(value: Double(translation))
return animation
}
/**
Creates a CABasicAnimation for the position key path.
- Parameter x: A CGFloat.
- Parameter y: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func position(x: CGFloat, y: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: CGPoint(x: x, y: y))
return animation
}
/**
Creates a CABasicAnimation for the position key path.
- Parameter to point: A CGPoint.
- Returns: A CABasicAnimation.
*/
public static func position(to point: CGPoint) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: point)
return animation
}
/**
Creates a CABasicAnimation for the opacity key path.
- Parameter to opacity: A Double.
- Returns: A CABasicAnimation.
*/
public static func fade(to opacity: Double) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .opacity)
animation.toValue = NSNumber(floatLiteral: opacity)
return animation
}
/**
Creates a CABasicaAnimation for the zPosition key path.
- Parameter _ position: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func zPosition(_ position: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .zPosition)
animation.toValue = NSNumber(value: Double(position))
return animation
}
/**
Creates a CABasicaAnimation for the width key path.
- Parameter width: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func width(_ width: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .width)
animation.toValue = NSNumber(floatLiteral: Double(width))
return animation
}
/**
Creates a CABasicaAnimation for the height key path.
- Parameter height: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func height(_ height: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .height)
animation.toValue = NSNumber(floatLiteral: Double(height))
return animation
}
/**
Creates a CABasicaAnimation for the height key path.
- Parameter size: A CGSize.
- Returns: A CABasicAnimation.
*/
public static func size(_ size: CGSize) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .size)
animation.toValue = NSValue(cgSize: size)
return animation
}
/**
Creates a CABasicAnimation for the shadowPath key path.
- Parameter path: A CGPath.
- Returns: A CABasicAnimation.
*/
public static func shadow(path: CGPath) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowPath)
animation.toValue = path
return animation
}
/**
Creates a CABasicAnimation for the shadowColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func shadow(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowColor)
animation.toValue = color.cgColor
return animation
}
/**
Creates a CABasicAnimation for the shadowOffset key path.
- Parameter offset: CGSize.
- Returns: A CABasicAnimation.
*/
public static func shadow(offset: CGSize) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowOffset)
animation.toValue = NSValue(cgSize: offset)
return animation
}
/**
Creates a CABasicAnimation for the shadowOpacity key path.
- Parameter opacity: Float.
- Returns: A CABasicAnimation.
*/
public static func shadow(opacity: Float) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowOpacity)
animation.toValue = NSNumber(floatLiteral: Double(opacity))
return animation
}
/**
Creates a CABasicAnimation for the shadowRadius key path.
- Parameter radius: CGFloat.
- Returns: A CABasicAnimation.
*/
public static func shadow(radius: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowRadius)
animation.toValue = NSNumber(floatLiteral: Double(radius))
return animation
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@available(iOS 10, *)
extension CALayer: CAAnimationDelegate {}
extension CALayer {
/**
A function that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animation: A CAAnimation instance.
*/
public func animate(_ animations: CAAnimation...) {
animate(animations)
}
/**
A function that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animation: A CAAnimation instance.
*/
public func animate(_ animations: [CAAnimation]) {
for animation in animations {
if nil == animation.delegate {
animation.delegate = self
}
if let a = animation as? CABasicAnimation {
a.fromValue = (presentation() ?? self).value(forKeyPath: a.keyPath!)
}
if let a = animation as? CAPropertyAnimation {
add(a, forKey: a.keyPath!)
} else if let a = animation as? CAAnimationGroup {
add(a, forKey: nil)
} else if let a = animation as? CATransition {
add(a, forKey: kCATransition)
}
}
}
public func animationDidStart(_ anim: CAAnimation) {}
/**
A delegation function that is executed when the backing layer stops
running an animation.
- Parameter animation: The CAAnimation instance that stopped running.
- Parameter flag: A boolean that indicates if the animation stopped
because it was completed or interrupted. True if completed, false
if interrupted.
*/
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
guard let a = anim as? CAPropertyAnimation else {
if let a = (anim as? CAAnimationGroup)?.animations {
for x in a {
animationDidStop(x, finished: true)
}
}
return
}
guard let b = a as? CABasicAnimation else {
return
}
guard let v = b.toValue else {
return
}
guard let k = b.keyPath else {
return
}
setValue(v, forKeyPath: k)
removeAnimation(forKey: k)
}
/**
A function that accepts a list of MotionAnimation values and executes them.
- Parameter animations: A list of MotionAnimation values.
*/
public func motion(_ animations: MotionAnimation...) {
motion(animations)
}
/**
A function that accepts an Array of MotionAnimation values and executes them.
- Parameter animations: An Array of MotionAnimation values.
- Parameter completion: An optional completion block.
*/
public func motion(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
motion(delay: 0, duration: 0.35, timingFunction: .easeInEaseOut, animations: animations, completion: completion)
}
/**
A function that executes an Array of MotionAnimation values.
- Parameter delay: The animation delay TimeInterval.
- Parameter duration: The animation duration TimeInterval.
- Parameter timingFunction: The animation MotionAnimationTimingFunction.
- Parameter animations: An Array of MotionAnimations.
- Parameter completion: An optional completion block.
*/
fileprivate func motion(delay: TimeInterval, duration: TimeInterval, timingFunction: MotionAnimationTimingFunction, animations: [MotionAnimation], completion: (() -> Void)? = nil) {
var t = delay
for v in animations {
switch v {
case let .delay(time):
t = time
default:break
}
}
Motion.delay(t) { [weak self] in
guard let s = self else {
return
}
var a = [CABasicAnimation]()
var tf = timingFunction
var d = duration
var w: CGFloat = s.bounds.width
var h: CGFloat = s.bounds.height
for v in animations {
switch v {
case let .width(width):
w = width
case let .height(height):
h = height
case let .size(width, height):
w = width
h = height
default:break
}
}
var px: CGFloat = s.position.x
var py: CGFloat = s.position.y
for v in animations {
switch v {
case let .x(x):
px = x + w / 2
case let .y(y):
py = y + h / 2
case let .point(x, y):
px = x + w / 2
py = y + h / 2
default:break
}
}
for v in animations {
switch v {
case let .timingFunction(timingFunction):
tf = timingFunction
case let .duration(duration):
d = duration
case let .custom(animation):
a.append(animation)
case let .backgroundColor(color):
a.append(MotionBasicAnimation.background(color: color))
case let .barTintColor(color):
a.append(MotionBasicAnimation.barTint(color: color))
case let .borderColor(color):
a.append(MotionBasicAnimation.border(color: color))
case let .borderWidth(width):
a.append(MotionBasicAnimation.border(width: width))
case let .cornerRadius(radius):
a.append(MotionBasicAnimation.corner(radius: radius))
case let .transform(transform):
a.append(MotionBasicAnimation.transform(transform: transform))
case let .rotationAngle(angle):
let rotate = MotionBasicAnimation.rotation(angle: angle)
a.append(rotate)
case let .rotationAngleX(angle):
a.append(MotionBasicAnimation.rotationX(angle: angle))
case let .rotationAngleY(angle):
a.append(MotionBasicAnimation.rotationY(angle: angle))
case let .rotationAngleZ(angle):
a.append(MotionBasicAnimation.rotationZ(angle: angle))
case let .spin(rotations):
a.append(MotionBasicAnimation.spin(rotations: rotations))
case let .spinX(rotations):
a.append(MotionBasicAnimation.spinX(rotations: rotations))
case let .spinY(rotations):
a.append(MotionBasicAnimation.spinY(rotations: rotations))
case let .spinZ(rotations):
a.append(MotionBasicAnimation.spinZ(rotations: rotations))
case let .scale(to):
a.append(MotionBasicAnimation.scale(to: to))
case let .scaleX(to):
a.append(MotionBasicAnimation.scaleX(to: to))
case let .scaleY(to):
a.append(MotionBasicAnimation.scaleY(to: to))
case let .scaleZ(to):
a.append(MotionBasicAnimation.scaleZ(to: to))
case let .translate(x, y):
a.append(MotionBasicAnimation.translate(to: CGPoint(x: x, y: y)))
case let .translateX(to):
a.append(MotionBasicAnimation.translateX(to: to))
case let .translateY(to):
a.append(MotionBasicAnimation.translateY(to: to))
case let .translateZ(to):
a.append(MotionBasicAnimation.translateZ(to: to))
case .x(_), .y(_), .point(_, _):
let position = MotionBasicAnimation.position(to: CGPoint(x: px, y: py))
a.append(position)
case let .position(x, y):
a.append(MotionBasicAnimation.position(to: CGPoint(x: x, y: y)))
case let .fade(opacity):
let fade = MotionBasicAnimation.fade(to: opacity)
fade.fromValue = s.value(forKey: MotionAnimationKeyPath.opacity.rawValue) ?? NSNumber(floatLiteral: 1)
a.append(fade)
case let .zPosition(position):
let zPosition = MotionBasicAnimation.zPosition(position)
zPosition.fromValue = s.value(forKey: MotionAnimationKeyPath.zPosition.rawValue) ?? NSNumber(value: 0)
a.append(zPosition)
case .width(_), .height(_), .size(_, _):
a.append(MotionBasicAnimation.size(CGSize(width: w, height: h)))
case let .shadowPath(path):
let shadowPath = MotionBasicAnimation.shadow(path: path)
shadowPath.fromValue = s.shadowPath
a.append(shadowPath)
case let .shadowColor(color):
a.append(MotionBasicAnimation.shadow(color: color))
case let .shadowOffset(offset):
let shadowOffset = MotionBasicAnimation.shadow(offset: offset)
shadowOffset.fromValue = s.shadowOffset
a.append(shadowOffset)
case let .shadowOpacity(opacity):
let shadowOpacity = MotionBasicAnimation.shadow(opacity: opacity)
shadowOpacity.fromValue = s.shadowOpacity
a.append(shadowOpacity)
case let .shadowRadius(radius):
let shadowRadius = MotionBasicAnimation.shadow(radius: radius)
shadowRadius.fromValue = s.shadowRadius
a.append(shadowRadius)
case let .depth(offset, opacity, radius):
if let path = s.shadowPath {
let shadowPath = MotionBasicAnimation.shadow(path: path)
shadowPath.fromValue = s.shadowPath
a.append(shadowPath)
}
let shadowOffset = MotionBasicAnimation.shadow(offset: offset)
shadowOffset.fromValue = s.shadowOffset
a.append(shadowOffset)
let shadowOpacity = MotionBasicAnimation.shadow(opacity: opacity)
shadowOpacity.fromValue = s.shadowOpacity
a.append(shadowOpacity)
let shadowRadius = MotionBasicAnimation.shadow(radius: radius)
shadowRadius.fromValue = s.shadowRadius
a.append(shadowRadius)
default:break
}
}
let g = Motion.animate(group: a, duration: d)
g.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
g.isRemovedOnCompletion = false
g.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
s.animate(g)
guard let execute = completion else {
return
}
Motion.delay(d, execute: execute)
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import MetalKit
internal struct KeySet<Key: Hashable, Value: Hashable> {
/// A reference to the dictionary storing the key / Set pairs.
fileprivate var dictionary = [Key: Set<Value>]()
/**
Subscript for matching keys and returning the corresponding set.
- Parameter key: A Key type.
- Returns: A Set<Value> type.
*/
subscript(key: Key) -> Set<Value> {
mutating get {
if nil == dictionary[key] {
dictionary[key] = Set<Value>()
}
return dictionary[key]!
}
set(value) {
dictionary[key] = value
}
}
}
internal extension CGSize {
internal var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
internal var point: CGPoint {
return CGPoint(x: width, y: height)
}
internal func transform(_ t: CGAffineTransform) -> CGSize {
return applying(t)
}
internal func transform(_ t: CATransform3D) -> CGSize {
return applying(CATransform3DGetAffineTransform(t))
}
}
internal extension CGRect {
var center: CGPoint {
return CGPoint(x: origin.x + size.width / 2, y: origin.y + size.height / 2)
}
var bounds: CGRect {
return CGRect(origin: CGPoint.zero, size: size)
}
init(center: CGPoint, size: CGSize) {
self.init(x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height)
}
}
internal extension CGFloat {
func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat {
return self < a ? a : self > b ? b : self
}
}
internal extension CGPoint {
func translate(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
return CGPoint(x: x + dx, y: y + dy)
}
func transform(_ t: CGAffineTransform) -> CGPoint {
return applying(t)
}
func transform(_ t: CATransform3D) -> CGPoint {
return applying(CATransform3DGetAffineTransform(t))
}
func distance(_ b: CGPoint) -> CGFloat {
return sqrt(pow(x - b.x, 2) + pow(y - b.y, 2))
}
}
internal func +(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
internal func -(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
internal func /(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x / right, y: left.y / right)
}
internal func /(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x / right.x, y: left.y / right.y)
}
internal func *(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x * right, y: left.y * right)
}
internal func *(left: CGPoint, right: CGSize) -> CGPoint {
return CGPoint(x: left.x * right.width, y: left.y * right.width)
}
internal func *(left: CGFloat, right: CGPoint) -> CGPoint {
return right * left
}
internal func *(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x * right.x, y: left.y * right.y)
}
internal prefix func -(point: CGPoint) -> CGPoint {
return CGPoint.zero - point
}
internal func abs(_ p: CGPoint) -> CGPoint {
return CGPoint(x: abs(p.x), y: abs(p.y))
}
internal func *(left: CGSize, right: CGFloat) -> CGSize {
return CGSize(width: left.width * right, height: left.height * right)
}
internal func *(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width * right.width, height: left.height * right.width)
}
internal func /(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width / right.width, height: left.height / right.height)
}
internal func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
var lhs = lhs
var rhs = rhs
return memcmp(&lhs, &rhs, MemoryLayout<CATransform3D>.size) == 0
}
internal func != (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
return !(lhs == rhs)
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
internal struct AssociatedObject {
/**
Gets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object.
- Parameter key: Memory key pointer.
- Parameter initializer: Object initializer.
- Returns: The associated reference for the initializer object.
*/
public static func get<T: Any>(base: Any, key: UnsafePointer<UInt8>, initializer: () -> T) -> T {
if let v = objc_getAssociatedObject(base, key) as? T {
return v
}
let v = initializer()
objc_setAssociatedObject(base, key, v, .OBJC_ASSOCIATION_RETAIN)
return v
}
/**
Sets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object.
- Parameter key: Memory key pointer.
- Parameter value: The object instance to set for the associated object.
- Returns: The associated reference for the initializer object.
*/
public static func set<T: Any>(base: Any, key: UnsafePointer<UInt8>, value: T) {
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
fileprivate var MotionInstanceKey: UInt8 = 0
fileprivate struct MotionInstance {
/// A boolean indicating whether Motion is enabled.
fileprivate var isEnabled: Bool
/// An optional reference to the motion identifier.
fileprivate var identifier: String?
/// An optional reference to the motion animations.
fileprivate var animations: [MotionAnimation]?
/// An optional reference to the motion transition animations.
fileprivate var transitions: [MotionTransition]?
/// An alpha value.
fileprivate var alpha: CGFloat?
}
extension UIView {
/// MotionInstance reference.
fileprivate var motionInstance: MotionInstance {
get {
return AssociatedObject.get(base: self, key: &MotionInstanceKey) {
return MotionInstance(isEnabled: true, identifier: nil, animations: nil, transitions: nil, alpha: 1)
}
}
set(value) {
AssociatedObject.set(base: self, key: &MotionInstanceKey, value: value)
}
}
/// A boolean that indicates whether motion is enabled.
@IBInspectable
public var isMotionEnabled: Bool {
get {
return motionInstance.isEnabled
}
set(value) {
motionInstance.isEnabled = value
}
}
/// An identifier value used to connect views across UIViewControllers.
@IBInspectable
open var motionIdentifier: String? {
get {
return motionInstance.identifier
}
set(value) {
motionInstance.identifier = value
}
}
/// The animations to run.
open var motionAnimations: [MotionAnimation]? {
get {
return motionInstance.animations
}
set(value) {
motionInstance.animations = value
}
}
/// The animations to run while in transition.
open var motionTransitions: [MotionTransition]? {
get {
return motionInstance.transitions
}
set(value) {
motionInstance.transitions = value
}
}
/// The animations to run while in transition.
@IBInspectable
open var motionAlpha: CGFloat? {
get {
return motionInstance.alpha
}
set(value) {
motionInstance.alpha = value
}
}
}
extension UIView {
/// Retrieves a single Array of UIViews that are in the view hierarchy.
internal var flattenedViewHierarchy: [UIView] {
guard isMotionEnabled else {
return []
}
if #available(iOS 9.0, *) {
return isHidden && (superview is UICollectionView || superview is UIStackView || self is UITableViewCell) ? [] : ([self] + subviews.flatMap { $0.flattenedViewHierarchy })
}
return isHidden && (superview is UICollectionView || self is UITableViewCell) ? [] : ([self] + subviews.flatMap { $0.flattenedViewHierarchy })
}
}
extension UIView {
/**
Snapshots the view instance for animations during transitions.
- Parameter afterUpdates: A boolean indicating whether to snapshot the view
after a render update, or as is.
- Parameter shouldHide: A boolean indicating whether the view should be hidden
after the snapshot is taken.
- Returns: A UIView instance that is a snapshot of the given UIView.
*/
public func transitionSnapshot(afterUpdates: Bool, shouldHide: Bool = true) -> UIView {
isHidden = false
let oldCornerRadius = layer.cornerRadius
layer.cornerRadius = 0
var oldBackgroundColor: UIColor?
if shouldHide {
oldBackgroundColor = backgroundColor
backgroundColor = .clear
}
let oldTransform = motionTransform
motionTransform = CATransform3DIdentity
let v = snapshotView(afterScreenUpdates: afterUpdates)!
layer.cornerRadius = oldCornerRadius
if shouldHide {
backgroundColor = oldBackgroundColor
}
motionTransform = oldTransform
let contentView = v.subviews.first!
contentView.layer.cornerRadius = layer.cornerRadius
contentView.layer.masksToBounds = true
v.motionIdentifier = motionIdentifier
v.layer.position = motionPosition
v.bounds = bounds
v.layer.cornerRadius = layer.cornerRadius
v.layer.zPosition = layer.zPosition
v.layer.opacity = layer.opacity
v.isOpaque = isOpaque
v.layer.anchorPoint = layer.anchorPoint
v.layer.masksToBounds = layer.masksToBounds
v.layer.borderColor = layer.borderColor
v.layer.borderWidth = layer.borderWidth
v.layer.shadowRadius = layer.shadowRadius
v.layer.shadowOpacity = layer.shadowOpacity
v.layer.shadowColor = layer.shadowColor
v.layer.shadowOffset = layer.shadowOffset
v.contentMode = contentMode
v.motionTransform = motionTransform
v.backgroundColor = backgroundColor
isHidden = shouldHide
return v
}
}
extension UIView {
internal func optimizedDuration(fromPosition: CGPoint, toPosition: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval {
let toPos = toPosition ?? fromPosition
let fromSize = (layer.presentation() ?? layer).bounds.size
let toSize = size ?? fromSize
let fromTransform = (layer.presentation() ?? layer).transform
let toTransform = transform ?? fromTransform
let realFromPos = CGPoint.zero.transform(fromTransform) + fromPosition
let realToPos = CGPoint.zero.transform(toTransform) + toPos
let realFromSize = fromSize.transform(fromTransform)
let realToSize = toSize.transform(toTransform)
let movePoints = realFromPos.distance(realToPos) + realFromSize.point.distance(realToSize.point)
// duration is 0.2 @ 0 to 0.375 @ 500
return 0.208 + Double(movePoints.clamp(0, 500)) / 3000
}
}
extension UIView {
/// Computes the rotation of the view.
open var motionRotationAngle: CGFloat {
get {
return CGFloat(atan2f(Float(transform.b), Float(transform.a))) * 180 / CGFloat(Double.pi)
}
set(value) {
transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi) * value / 180)
}
}
/// The global position of a view.
open var motionPosition: CGPoint {
return superview?.convert(layer.position, to: nil) ?? layer.position
}
/// The layer.transform of a view.
open var motionTransform: CATransform3D {
get {
return layer.transform
}
set(value) {
layer.transform = value
}
}
/// Computes the scale X axis value of the view.
open var motionScaleX: CGFloat {
return transform.a
}
/// Computes the scale Y axis value of the view.
open var motionScaleY: CGFloat {
return transform.b
}
/**
A function that accepts CAAnimation objects and executes them on the
view's backing layer.
- Parameter animations: A list of CAAnimations.
*/
open func animate(_ animations: CAAnimation...) {
layer.animate(animations)
}
/**
A function that accepts an Array of CAAnimation objects and executes
them on the view's backing layer.
- Parameter animations: An Array of CAAnimations.
*/
open func animate(_ animations: [CAAnimation]) {
layer.animate(animations)
}
/**
A function that accepts a list of MotionAnimation values and executes
them on the view's backing layer.
- Parameter animations: A list of MotionAnimation values.
*/
open func motion(_ animations: MotionAnimation...) {
layer.motion(animations)
}
/**
A function that accepts an Array of MotionAnimation values and executes
them on the view's backing layer.
- Parameter animations: An Array of MotionAnimation values.
- Parameter completion: An optional completion block.
*/
open func motion(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
layer.motion(animations, completion: completion)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
fileprivate var MotionInstanceControllerKey: UInt8 = 0
fileprivate struct MotionInstanceController {
/// A boolean indicating whether Motion is enabled.
fileprivate var isEnabled: Bool
/// An optional reference to the current snapshot.
fileprivate var snapshot: UIView?
/// An optional reference to the previous UINavigationControllerDelegate.
fileprivate var previousNavigationDelegate: UINavigationControllerDelegate?
/// An optional reference to the previous UITabBarControllerDelegate.
fileprivate var previousTabBarDelegate: UITabBarControllerDelegate?
}
extension UIViewController {
/// MotionInstanceController reference.
fileprivate var motionControllerInstance: MotionInstanceController {
get {
return AssociatedObject.get(base: self, key: &MotionInstanceControllerKey) {
return MotionInstanceController(isEnabled: false, snapshot: nil, previousNavigationDelegate: nil, previousTabBarDelegate: nil)
}
}
set(value) {
AssociatedObject.set(base: self, key: &MotionInstanceControllerKey, value: value)
}
}
/// A boolean that indicates whether motion is enabled.
@IBInspectable
public var isMotionEnabled: Bool {
get {
return transitioningDelegate is Motion
}
set {
guard newValue != isMotionEnabled else {
return
}
if newValue {
transitioningDelegate = Motion.shared
if let v = self as? UINavigationController {
motionPreviousNavigationDelegate = v.delegate
v.delegate = Motion.shared
}
if let v = self as? UITabBarController {
motionPreviousTabBarDelegate = v.delegate
v.delegate = Motion.shared
}
} else {
transitioningDelegate = nil
if let v = self as? UINavigationController, v.delegate is Motion {
v.delegate = motionPreviousNavigationDelegate
}
if let v = self as? UITabBarController, v.delegate is Motion {
v.delegate = motionPreviousTabBarDelegate
}
}
}
}
/// An optional reference to the current snapshot.
internal var motionSnapshot: UIView? {
get {
return motionControllerInstance.snapshot
}
set(value) {
motionControllerInstance.snapshot = value
}
}
/// An optional reference to the previous UINavigationControllerDelegate.
internal var motionPreviousNavigationDelegate: UINavigationControllerDelegate? {
get {
return motionControllerInstance.previousNavigationDelegate
}
set(value) {
motionControllerInstance.previousNavigationDelegate = value
}
}
/// An optional reference to the previous UITabBarControllerDelegate.
internal var motionPreviousTabBarDelegate: UITabBarControllerDelegate? {
get {
return motionControllerInstance.previousTabBarDelegate
}
set(value) {
motionControllerInstance.previousTabBarDelegate = value
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
* The MIT License (MIT)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 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:
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 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 UIKit
public typealias MotionDelayCancelBlock = (Bool) -> Void
/**
### The singleton class/object for controlling interactive transitions.
public class Motion: MotionController {
/// A reference to an optional transitioning context provided by UIKit.
fileprivate weak var transitionContext: UIViewControllerContextTransitioning?
```swift
Motion.shared
```
/// A boolean inficating whether or not the transition is animating.
fileprivate var isAnimating = false
#### Use the following methods for controlling the interactive transition:
/**
A boolean indicating if the transition view controller is a
UINavigationController.
```swift
func update(progress:Double)
func end()
func cancel()
func apply(modifiers:[MotionTransition], to view:UIView)
```
*/
internal fileprivate(set) var isNavigationController = false
public class Motion: MotionController {
// MARK: Shared Access
/**
A boolean indicating if the transition view controller is a
UITabBarController.
*/
internal fileprivate(set) var isTabBarController = false
/// Shared singleton object for controlling the transition
public static let shared = Motion()
/**
A boolean indicating if the transition view controller is a
UINavigationController or UITabBarController.
*/
internal var isContainerController: Bool {
return isNavigationController || isTabBarController
}
// MARK: Properties
/// A reference to the fromViewController.view.
internal var fromView: UIView {
return fromViewController!.view
}
/// destination view controller
public internal(set) var toViewController: UIViewController?
/// source view controller
public internal(set) var fromViewController: UIViewController?
/// whether or not we are presenting the destination view controller
public internal(set) var presenting = true
/// A reference to the toViewController.view.
internal var toView: UIView {
return toViewController!.view
/// progress of the current transition. 0 if no transition is happening
public override var progress: Double {
didSet {
if transitioning {
transitionContext?.updateInteractiveTransition(CGFloat(progress))
}
/// A reference to the screen snapshot.
fileprivate var screenSnapshot: UIView!
/// A boolean indicating if the fromView is at full screen.
internal var isFromViewFullScreen: Bool {
return !isContainerController && (.overFullScreen == fromViewController!.modalPresentationStyle || .overCurrentContext == fromViewController!.modalPresentationStyle)
}
/// A boolean indicating if the toView is at full screen.
internal var isToViewFullScreen: Bool {
return !isContainerController && (.overFullScreen == toViewController!.modalPresentationStyle || .overCurrentContext == toViewController!.modalPresentationStyle)
}
/**
A reference to a shared Motion instance to control interactive
transitions.
*/
public static let shared = Motion()
public var isAnimating: Bool = false
/// a UIViewControllerContextTransitioning object provided by UIKit,
/// might be nil when transitioning. This happens when calling motionReplaceViewController
internal weak var transitionContext: UIViewControllerContextTransitioning?
/// A reference to the source view controller.
public fileprivate(set) var fromViewController: UIViewController?
internal var fullScreenSnapshot: UIView!
/// A reference to the destination view controller.
public fileprivate(set) var toViewController: UIViewController?
internal var defaultAnimation: MotionDefaultAnimationType = .auto
internal var containerColor: UIColor?
/// A boolean indicating if the view controller is presenting.
public fileprivate(set) var isPresenting = true
// By default, Motion will always appear to be interactive to UIKit. This forces it to appear non-interactive.
// Used when doing a motion_replaceViewController within a UINavigationController, to fix a bug with
// UINavigationController.setViewControllers not able to handle interactive transition
internal var forceNotInteractive = false
/// A reference to the animation elapsed time.
public override var elapsedTime: TimeInterval {
didSet {
guard isTransitioning else {
return
}
internal var insertToViewFirst = false
transitionContext?.updateInteractiveTransition(CGFloat(elapsedTime))
internal var inNavigationController = false
internal var inTabBarController = false
internal var inContainerController: Bool {
return inNavigationController || inTabBarController
}
internal var toOverFullScreen: Bool {
return !inContainerController && (toViewController!.modalPresentationStyle == .overFullScreen || toViewController!.modalPresentationStyle == .overCurrentContext)
}
}
extension Motion {
/**
Executes a block of code after a time delay.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter execute block: A completion block that is executed once
the animations have completed.
*/
@discardableResult
public class func delay(_ time: TimeInterval, execute block: @escaping () -> Void) -> MotionDelayCancelBlock? {
var cancelable: MotionDelayCancelBlock?
let delayed: MotionDelayCancelBlock = {
if !$0 {
DispatchQueue.main.async(execute: block)
internal var fromOverFullScreen: Bool {
return !inContainerController && (fromViewController!.modalPresentationStyle == .overFullScreen || fromViewController!.modalPresentationStyle == .overCurrentContext)
}
cancelable = nil
}
cancelable = delayed
DispatchQueue.main.asyncAfter(deadline: .now() + time) {
cancelable?(false)
}
internal var toView: UIView { return toViewController!.view }
internal var fromView: UIView { return fromViewController!.view }
return cancelable
}
internal override init() { super.init() }
}
/**
Cancels the delayed MotionDelayCancelBlock.
- Parameter delayed completion: An MotionDelayCancelBlock.
*/
public class func cancel(delayed completion: MotionDelayCancelBlock) {
completion(true)
}
public extension Motion {
/**
Disables the default animations set on CALayers.
- Parameter animations: A callback that wraps the animations to disable.
*/
public class func disable(_ animations: (() -> Void)) {
animate(duration: 0, animations: animations)
/// Turn off built-in animation for next transition
func disableDefaultAnimationForNextTransition() {
defaultAnimation = .none
}
/**
Runs an animation with a specified duration.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter timingFunction: An MotionAnimationTimingFunction value.
- Parameter completion: A completion block that is executed once
the animations have completed.
*/
public class func animate(duration: CFTimeInterval, timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, animations: (() -> Void), completion: (() -> Void)? = nil) {
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
CATransaction.setCompletionBlock(completion)
CATransaction.setAnimationTimingFunction(MotionAnimationTimingFunctionToValue(timingFunction: timingFunction))
animations()
CATransaction.commit()
/// Set the default animation for next transition
/// This usually overrides rootView's motionModifiers during the transition
///
/// - Parameter animation: animation type
func setDefaultAnimationForNextTransition(_ animation: MotionDefaultAnimationType) {
defaultAnimation = animation
}
/**
Creates a CAAnimationGroup.
- Parameter animations: An Array of CAAnimation objects.
- Parameter timingFunction: An MotionAnimationTimingFunction value.
- Parameter duration: An animation duration time for the group.
- Returns: A CAAnimationGroup.
*/
public class func animate(group animations: [CAAnimation], timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, duration: CFTimeInterval = 0.5) -> CAAnimationGroup {
let group = CAAnimationGroup()
group.fillMode = MotionAnimationFillModeToValue(mode: .both)
group.isRemovedOnCompletion = false
group.animations = animations
group.duration = duration
group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)
return group
/// Set the container color for next transition
///
/// - Parameter color: container color
func setContainerColorForNextTransition(_ color: UIColor?) {
containerColor = color
}
}
extension Motion {
/**
Removes a snapshot from a given view controller.
- Parameter for viewController: A UIViewController.
*/
fileprivate func removeSnapshot(for viewController: UIViewController?) {
guard let v = viewController?.motionSnapshot else {
return
// internal methods for transition
internal extension Motion {
func start() {
guard transitioning else { return }
if let fvc = fromViewController, let tvc = toViewController {
closureProcessForMotionDelegate(vc: fvc) {
$0.motionWillStartTransition?()
$0.motionWillStartAnimatingTo?(viewController: tvc)
}
v.removeFromSuperview()
viewController?.motionSnapshot = nil
closureProcessForMotionDelegate(vc: tvc) {
$0.motionWillStartTransition?()
$0.motionWillStartAnimatingFrom?(viewController: fvc)
}
}
extension Motion {
/// Prepares the screen snapshot.
fileprivate func prepareScreenSnapshot() {
screenSnapshot?.removeFromSuperview()
screenSnapshot = (transitionContainer.window ?? fromView).snapshotView(afterScreenUpdates: true)
(transitionContainer.window ?? transitionContainer)?.addSubview(screenSnapshot)
}
/// Prepares the preprocessors.
fileprivate func preparePreprocessors() {
preprocessors = [
MatchPreprocessor(),
SourcePreprocessor(),
AnimationPreprocessor(motion: self),
DurationPreprocessor(),
]
}
// take a snapshot to hide all the flashing that might happen
fullScreenSnapshot = transitionContainer.window?.snapshotView(afterScreenUpdates: true) ?? fromView.snapshotView(afterScreenUpdates: true)
(transitionContainer.window ?? transitionContainer)?.addSubview(fullScreenSnapshot)
/// Prepares the animators.
fileprivate func prepareAnimators() {
animators = []
if let oldSnapshot = fromViewController?.motionStoredSnapshot {
oldSnapshot.removeFromSuperview()
fromViewController?.motionStoredSnapshot = nil
}
/// Prepares the transitionContainer.
fileprivate func prepareTransitionContainer() {
transitionContainer.isUserInteractionEnabled = false
if let oldSnapshot = toViewController?.motionStoredSnapshot {
oldSnapshot.removeFromSuperview()
toViewController?.motionStoredSnapshot = nil
}
/// Prepares the context.
fileprivate func prepareContext() {
context = MotionContext(container: container)
prepareForTransition()
insert(preprocessor: DefaultAnimationPreprocessor(motion: self), before: DurationPreprocessor.self)
context.loadViewAlpha(rootView: toView)
context.loadViewAlpha(rootView: fromView)
container.addSubview(toView)
container.addSubview(fromView)
toView.frame = fromView.frame
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
context.set(fromViews: fromView.flattenedViewHierarchy, toViews: toView.flattenedViewHierarchy)
for v in preprocessors {
v.context = context
processContext()
prepareForAnimation()
context.hide(view: toView)
#if os(tvOS)
animate()
#else
if inNavigationController {
// 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 {
self.animate()
}
} else {
animate()
}
#endif
}
override func animate() {
context.unhide(view: toView)
for v in animators {
v.context = context
if let containerColor = containerColor {
container.backgroundColor = containerColor
} else if !toOverFullScreen && !fromOverFullScreen {
container.backgroundColor = toView.backgroundColor
}
if fromOverFullScreen {
insertToViewFirst = true
}
for animator in animators {
if let animator = animator as? HasInsertOrder {
animator.insertToViewFirst = insertToViewFirst
}
}
/// Prepares the toView.
fileprivate func prepareToView() {
toView.frame = fromView.frame
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
super.animate()
fullScreenSnapshot!.removeFromSuperview()
}
}
extension Motion {
/// Iterates through all the processors.
fileprivate func processContext() {
for v in preprocessors {
v.process(fromViews: context.fromViews, toViews: context.toViews)
override func complete(finished: Bool) {
guard transitioning else { return }
context.clean()
if finished && presenting && toOverFullScreen {
// finished presenting a overFullScreen VC
context.unhide(rootView: toView)
context.removeSnapshots(rootView: toView)
context.storeViewAlpha(rootView: fromView)
fromViewController!.motionStoredSnapshot = container
fromView.removeFromSuperview()
fromView.addSubview(container)
} else if !finished && !presenting && fromOverFullScreen {
// cancelled dismissing a overFullScreen VC
context.unhide(rootView: fromView)
context.removeSnapshots(rootView: fromView)
context.storeViewAlpha(rootView: toView)
toViewController!.motionStoredSnapshot = container
toView.removeFromSuperview()
toView.addSubview(container)
} else {
context.unhideAll()
context.removeAllSnapshots()
container.removeFromSuperview()
}
// move fromView & toView back from our container back to the one supplied by UIKit
if (toOverFullScreen && finished) || (fromOverFullScreen && !finished) {
transitionContainer.addSubview(finished ? fromView : toView)
}
}
transitionContainer.addSubview(finished ? toView : fromView)
extension Motion {
/// Starts the transition.
fileprivate func start() {
guard isTransitioning else {
return
if presenting != finished, !inContainerController {
// only happens when present a .overFullScreen VC
// bug: http://openradar.appspot.com/radar?id=5320103646199808
UIApplication.shared.keyWindow!.addSubview(presenting ? fromView : toView)
}
removeSnapshot(for: fromViewController)
removeSnapshot(for: toViewController)
// use temp variables to remember these values
// because we have to reset everything before calling
// any delegate or completion block
let tContext = transitionContext
let fvc = fromViewController
let tvc = toViewController
prepareScreenSnapshot()
preparePreprocessors()
prepareAnimators()
prepareTransitionContainer()
prepareToView()
prepareContext()
transitionContext = nil
fromViewController = nil
toViewController = nil
containerColor = nil
inNavigationController = false
inTabBarController = false
forceNotInteractive = false
insertToViewFirst = false
defaultAnimation = .auto
processContext()
super.complete(finished: finished)
if finished {
if let fvc = fvc, let tvc = tvc {
closureProcessForMotionDelegate(vc: fvc) {
$0.motionDidEndAnimatingTo?(viewController: tvc)
$0.motionDidEndTransition?()
}
/**
Called when the animation is thought to be completed.
- Parameter isFinished: A boolean value indicatin if the
transition has completed.
*/
fileprivate func completed(isFinished: Bool) {
transitionContext?.completeTransition(!isFinished)
closureProcessForMotionDelegate(vc: tvc) {
$0.motionDidEndAnimatingFrom?(viewController: fvc)
$0.motionDidEndTransition?()
}
}
tContext?.finishInteractiveTransition()
} else {
if let fvc = fvc, let tvc = tvc {
closureProcessForMotionDelegate(vc: fvc) {
$0.motionDidCancelAnimatingTo?(viewController: tvc)
$0.motionDidCancelTransition?()
}
}
extension Motion {
/**
Helper transition function.
- Parameter from: A UIViewController.
- Parameter to: A UIViewController.
- Parameter in container: A UIView.
- Parameter completion: A completion block.
*/
fileprivate func transition(from: UIViewController, to: UIViewController, in container: UIView, completion: ((Bool) -> Void)? = nil) {
guard !isTransitioning else {
return
closureProcessForMotionDelegate(vc: tvc) {
$0.motionDidCancelAnimatingFrom?(viewController: fvc)
$0.motionDidCancelTransition?()
}
}
tContext?.cancelInteractiveTransition()
}
tContext?.completeTransition(finished)
}
}
isPresenting = true
transitionContainer = container
// custom transition helper, used in motion_replaceViewController
internal extension Motion {
func transition(from: UIViewController, to: UIViewController, in view: UIView, completion: ((Bool) -> Void)? = nil) {
guard !transitioning else { return }
presenting = true
transitionContainer = view
fromViewController = from
toViewController = to
completionCallback = completion
start()
}
}
extension Motion: UIViewControllerAnimatedTransitioning {
/**
The animation method that is used to coordinate the transition.
- Parameter using transitionContext: A UIViewControllerContextTransitioning.
*/
public func animateTransition(using context: UIViewControllerContextTransitioning) {
guard !isTransitioning else {
return
// delegate helper
internal extension Motion {
func closureProcessForMotionDelegate<T: UIViewController>(vc: T, closure: (MotionViewControllerDelegate) -> Void) {
if let delegate = vc as? MotionViewControllerDelegate {
closure(delegate)
}
if let navigationController = vc as? UINavigationController,
let delegate = navigationController.topViewController as? MotionViewControllerDelegate {
closure(delegate)
}
if let tabBarController = vc as? UITabBarController,
let delegate = tabBarController.viewControllers?[tabBarController.selectedIndex] as? MotionViewControllerDelegate {
closure(delegate)
}
}
}
// MARK: UIKit Protocol Conformance
/*****************************
* UIKit protocol extensions *
*****************************/
extension Motion: UIViewControllerAnimatedTransitioning {
public func animateTransition(using context: UIViewControllerContextTransitioning) {
guard !transitioning else { return }
transitionContext = context
fromViewController = fromViewController ?? context.viewController(forKey: .from)
toViewController = toViewController ?? context.viewController(forKey: .to)
transitionContainer = context.containerView
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.
return 0.375 // doesn't matter, real duration will be calculated later
}
public func animationEnded(_ transitionCompleted: Bool) {
......@@ -350,20 +357,20 @@ extension Motion: UIViewControllerAnimatedTransitioning {
}
}
extension Motion: UIViewControllerTransitioningDelegate {
extension Motion:UIViewControllerTransitioningDelegate {
var interactiveTransitioning: UIViewControllerInteractiveTransitioning? {
return self
return forceNotInteractive ? nil : self
}
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = true
self.presenting = true
self.fromViewController = fromViewController ?? presenting
self.toViewController = toViewController ?? presented
return self
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = false
self.presenting = false
self.fromViewController = fromViewController ?? dismissed
return self
}
......@@ -388,11 +395,10 @@ extension Motion: UIViewControllerInteractiveTransitioning {
extension Motion: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = .push == operation
self.presenting = operation == .push
self.fromViewController = fromViewController ?? fromVC
self.toViewController = toViewController ?? toVC
self.isNavigationController = true
self.inNavigationController = true
return self
}
......@@ -412,15 +418,52 @@ extension Motion: UITabBarControllerDelegate {
public func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isAnimating = true
let fromVCIndex = tabBarController.childViewControllers.index(of: fromVC)!
let toVCIndex = tabBarController.childViewControllers.index(of: toVC)!
self.isPresenting = toVCIndex > fromVCIndex
self.presenting = toVCIndex > fromVCIndex
self.fromViewController = fromViewController ?? fromVC
self.toViewController = toViewController ?? toVC
self.isTabBarController = true
self.inTabBarController = true
return self
}
}
public typealias MotionDelayCancelBlock = (Bool) -> Void
extension Motion {
/**
Executes a block of code after a time delay.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter execute block: A completion block that is executed once
the animations have completed.
*/
@discardableResult
public class func delay(_ time: TimeInterval, execute block: @escaping () -> Void) -> MotionDelayCancelBlock? {
var cancelable: MotionDelayCancelBlock?
let delayed: MotionDelayCancelBlock = {
if !$0 {
DispatchQueue.main.async(execute: block)
}
cancelable = nil
}
cancelable = delayed
DispatchQueue.main.asyncAfter(deadline: .now() + time) {
cancelable?(false)
}
return cancelable
}
/**
Cancels the delayed MotionDelayCancelBlock.
- Parameter delayed completion: An MotionDelayCancelBlock.
*/
public class func cancel(delayed completion: MotionDelayCancelBlock) {
completion(true)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public enum MotionAnimation {
case delay(TimeInterval)
case timingFunction(MotionAnimationTimingFunction)
case duration(TimeInterval)
case custom(CABasicAnimation)
case backgroundColor(UIColor)
case barTintColor(UIColor)
case borderColor(UIColor)
case borderWidth(CGFloat)
case cornerRadius(CGFloat)
case transform(CATransform3D)
case rotationAngle(CGFloat)
case rotationAngleX(CGFloat)
case rotationAngleY(CGFloat)
case rotationAngleZ(CGFloat)
case spin(CGFloat)
case spinX(CGFloat)
case spinY(CGFloat)
case spinZ(CGFloat)
case scale(CGFloat)
case scaleX(CGFloat)
case scaleY(CGFloat)
case scaleZ(CGFloat)
case translate(x: CGFloat, y: CGFloat)
case translateX(CGFloat)
case translateY(CGFloat)
case translateZ(CGFloat)
case x(CGFloat)
case y(CGFloat)
case point(x: CGFloat, y: CGFloat)
case position(x: CGFloat, y: CGFloat)
case fade(Double)
case zPosition(CGFloat)
case width(CGFloat)
case height(CGFloat)
case size(width: CGFloat, height: CGFloat)
case shadowPath(CGPath)
case shadowColor(UIColor)
case shadowOffset(CGSize)
case shadowOpacity(Float)
case shadowRadius(CGFloat)
case depth(shadowOffset: CGSize, shadowOpacity: Float, shadowRadius: CGFloat)
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(MotionAnimationFillMode)
public enum MotionAnimationFillMode: Int {
case forwards
case backwards
case both
case removed
}
/**
Converts the MotionAnimationFillMode enum value to a corresponding String.
- Parameter mode: An MotionAnimationFillMode enum value.
*/
public func MotionAnimationFillModeToValue(mode: MotionAnimationFillMode) -> String {
switch mode {
case .forwards:
return kCAFillModeForwards
case .backwards:
return kCAFillModeBackwards
case .both:
return kCAFillModeBoth
case .removed:
return kCAFillModeRemoved
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(MotionAnimationTimingFunction)
public enum MotionAnimationTimingFunction: Int {
case `default`
case linear
case easeIn
case easeOut
case easeInEaseOut
}
/**
Converts the MotionAnimationTimingFunction enum value to a corresponding CAMediaTimingFunction.
- Parameter function: An MotionAnimationTimingFunction enum value.
- Returns: A CAMediaTimingFunction.
*/
public func MotionAnimationTimingFunctionToValue(timingFunction: MotionAnimationTimingFunction) -> CAMediaTimingFunction {
switch timingFunction {
case .default:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
case .linear:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
case .easeIn:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
case .easeOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
case .easeInEaseOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public enum MotionCascadeDirection {
case topToBottom
case bottomToTop
case leftToRight
case rightToLeft
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
* The MIT License (MIT)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 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:
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 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 UIKit
open class MotionContext {
/// A reference to the transition container.
internal fileprivate(set) var container: UIView
public class MotionContext {
internal var motionIDToSourceView = [String: UIView]()
internal var motionIDToDestinationView = [String: UIView]()
internal var snapshotViews = [UIView: UIView]()
internal var viewAlphas = [UIView: CGFloat]()
internal var targetStates = [UIView: MotionTargetState]()
internal var superviewToNoSnapshotSubviewMap: [UIView: [(Int, UIView)]] = [:]
/// An index source of identifiers to their corresponding view.
internal fileprivate(set) var sourceIdentifierToView = [String: UIView]()
internal var defaultCoordinateSpace: MotionCoordinateSpace = .local
/// An index of destination identifiers to their corresponding view.
internal fileprivate(set) var destinationIdentifierToView = [String: UIView]()
/// An index of views to their corresponding snapshot view.
internal fileprivate(set) var snapshotToView = [UIView: UIView]()
internal init(container: UIView) {
self.container = container
}
/// An index of views to their MotionTransitionState.
internal var viewToMotionTransitionState = [UIView: MotionTransitionState]()
internal func set(fromViews: [UIView], toViews: [UIView]) {
self.fromViews = fromViews
self.toViews = toViews
process(views: fromViews, idMap: &motionIDToSourceView)
process(views: toViews, idMap: &motionIDToDestinationView)
}
/// An index of views to their alpha value.
internal var viewToAlpha = [UIView: CGFloat]()
internal func process(views: [UIView], idMap: inout [String: UIView]) {
for view in views {
view.layer.removeAllAnimations()
if container.convert(view.bounds, from: view).intersects(container.bounds) {
if let motionID = view.motionID {
idMap[motionID] = view
}
if let modifiers = view.motionModifiers {
targetStates[view] = MotionTargetState(modifiers: modifiers)
}
}
}
}
/// A reference to the transition from views.
internal var fromViews: [UIView]!
/**
The container holding all of the animating views
*/
public let container: UIView
/// A reference to the transition to views.
internal var toViews: [UIView]!
/**
A flattened list of all views from source ViewController
*/
public var fromViews: [UIView]!
/**
An initializer that accepts a given transition container view.
- Parameter container: A UIView.
A flattened list of all views from destination ViewController
*/
init(container: UIView) {
self.container = container
}
public var toViews: [UIView]!
}
// public
extension MotionContext {
/**
Prepares the source views to their identifiers.
- Parameter views: An Array of UIViews.
- Parameter identifierIndex: An Dictionary of identifiers to UIViews.
- Returns: a source view matching the motionID, nil if not found
*/
fileprivate func prepare(views: [UIView], identifierIndex: inout [String: UIView]) {
for v in views {
v.layer.removeAllAnimations()
guard container.convert(v.bounds, from: v).intersects(container.bounds) else {
return
public func sourceView(for motionID: String) -> UIView? {
return motionIDToSourceView[motionID]
}
guard let i = v.motionIdentifier else {
return
/**
- Returns: a destination view matching the motionID, nil if not found
*/
public func destinationView(for motionID: String) -> UIView? {
return motionIDToDestinationView[motionID]
}
identifierIndex[i] = v
/**
- Returns: a view with the same motionID, but on different view controller, nil if not found
*/
public func pairedView(for view: UIView) -> UIView? {
if let id = view.motionID {
if sourceView(for: id) == view {
return destinationView(for: id)
} else if destinationView(for: id) == view {
return sourceView(for: id)
}
}
}
return nil
}
extension MotionContext {
/**
Retrieves the transition pair for a given view, if one exists.
- Parameter for view: A UIView.
- Returns: An optional UIView.
- Returns: a snapshot view for animation
*/
internal func transitionPair(for view: UIView) -> UIView? {
guard let identifier = view.motionIdentifier else {
return nil
public func snapshotView(for view: UIView) -> UIView {
if let snapshot = snapshotViews[view] {
return snapshot
}
guard let source = sourceIdentifierToView[identifier] else {
return nil
var containerView = container
let coordinateSpace = targetStates[view]?.coordinateSpace ?? defaultCoordinateSpace
switch coordinateSpace {
case .local:
containerView = view
while containerView != container, snapshotViews[containerView] == nil, let superview = containerView.superview {
containerView = superview
}
if let snapshot = snapshotViews[containerView] {
containerView = snapshot
}
case .sameParent:
containerView = view.superview!
case .global:
break
}
guard let destination = destinationIdentifierToView[identifier] else {
return nil
unhide(view: view)
// capture a snapshot without alpha & cornerRadius
let oldCornerRadius = view.layer.cornerRadius
let oldAlpha = view.alpha
view.layer.cornerRadius = 0
view.alpha = 1
let snapshot: UIView
let snapshotType: MotionSnapshotType = self[view]?.snapshotType ?? .optimized
switch snapshotType {
case .normal:
snapshot = view.snapshotView(afterScreenUpdates: true)!
case .layerRender:
snapshot = view.slowSnapshotView()
case .noSnapshot:
if superviewToNoSnapshotSubviewMap[view.superview!] == nil {
superviewToNoSnapshotSubviewMap[view.superview!] = []
}
superviewToNoSnapshotSubviewMap[view.superview!]!.append((view.superview!.subviews.index(of: view)!, view))
snapshot = view
case .optimized:
#if os(tvOS)
snapshot = view.snapshotView(afterScreenUpdates: true)!
#else
if #available(iOS 9.0, *), let stackView = view as? UIStackView {
snapshot = stackView.slowSnapshotView()
} else if let imageView = view as? UIImageView, view.subviews.isEmpty {
let contentView = UIImageView(image: imageView.image)
contentView.frame = imageView.bounds
contentView.contentMode = imageView.contentMode
contentView.tintColor = imageView.tintColor
contentView.backgroundColor = imageView.backgroundColor
let snapShotView = UIView()
snapShotView.addSubview(contentView)
snapshot = snapShotView
} else if let barView = view as? UINavigationBar, barView.isTranslucent {
let newBarView = UINavigationBar(frame: barView.frame)
newBarView.barStyle = barView.barStyle
newBarView.tintColor = barView.tintColor
newBarView.barTintColor = barView.barTintColor
newBarView.clipsToBounds = false
// take a snapshot without the background
barView.layer.sublayers![0].opacity = 0
let realSnapshot = barView.snapshotView(afterScreenUpdates: true)!
barView.layer.sublayers![0].opacity = 1
newBarView.addSubview(realSnapshot)
snapshot = newBarView
} else if let effectView = view as? UIVisualEffectView {
snapshot = UIVisualEffectView(effect: effectView.effect)
snapshot.frame = effectView.bounds
} else {
snapshot = view.snapshotView(afterScreenUpdates: true)!
}
#endif
}
return view == source ? destination : view == destination ? source : nil
#if os(tvOS)
if let imageView = view as? UIImageView, imageView.adjustsImageWhenAncestorFocused {
snapshot.frame = imageView.focusedFrameGuide.layoutFrame
}
#endif
/**
Sets the views that will transition from one state to another.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
internal func set(fromViews: [UIView], toViews: [UIView]) {
self.fromViews = fromViews
self.toViews = toViews
view.layer.cornerRadius = oldCornerRadius
view.alpha = oldAlpha
if snapshotType != .noSnapshot {
snapshot.layer.allowsGroupOpacity = false
prepare(views: fromViews, identifierIndex: &sourceIdentifierToView)
prepare(views: toViews, identifierIndex: &destinationIdentifierToView)
if !(view is UINavigationBar), let contentView = snapshot.subviews.get(0) {
// the Snapshot's contentView must have hold the cornerRadius value,
// since the snapshot might not have maskToBounds set
contentView.layer.cornerRadius = view.layer.cornerRadius
contentView.layer.masksToBounds = true
}
internal func hide(view: UIView) {
guard nil == viewToAlpha[view] else {
return
snapshot.layer.cornerRadius = view.layer.cornerRadius
snapshot.layer.zPosition = view.layer.zPosition
snapshot.layer.opacity = view.layer.opacity
snapshot.layer.isOpaque = view.layer.isOpaque
snapshot.layer.anchorPoint = view.layer.anchorPoint
snapshot.layer.masksToBounds = view.layer.masksToBounds
snapshot.layer.borderColor = view.layer.borderColor
snapshot.layer.borderWidth = view.layer.borderWidth
snapshot.layer.transform = view.layer.transform
snapshot.layer.contentsRect = view.layer.contentsRect
snapshot.layer.contentsScale = view.layer.contentsScale
if self[view]?.displayShadow ?? true {
snapshot.layer.shadowRadius = view.layer.shadowRadius
snapshot.layer.shadowOpacity = view.layer.shadowOpacity
snapshot.layer.shadowColor = view.layer.shadowColor
snapshot.layer.shadowOffset = view.layer.shadowOffset
snapshot.layer.shadowPath = view.layer.shadowPath
}
}
snapshot.frame = containerView.convert(view.bounds, from: view)
snapshot.motionID = view.motionID
hide(view: view)
guard .none != viewToMotionTransitionState[view]?.motionSnapshot else {
return
if let pairedView = pairedView(for: view), let pairedSnapshot = snapshotViews[pairedView] {
let siblingViews = pairedView.superview!.subviews
let nextSiblings = siblingViews[siblingViews.index(of: pairedView)!+1..<siblingViews.count]
containerView.addSubview(pairedSnapshot)
containerView.addSubview(snapshot)
for subview in pairedView.subviews {
insertGlobalViewTree(view: subview)
}
for sibling in nextSiblings {
insertGlobalViewTree(view: sibling)
}
} else {
containerView.addSubview(snapshot)
}
containerView.addSubview(snapshot)
snapshotViews[view] = snapshot
return snapshot
}
guard view is UIVisualEffectView else {
view.isHidden = true
viewToAlpha[view] = 1
return
func insertGlobalViewTree(view: UIView) {
if targetStates[view]?.coordinateSpace == .global, let snapshot = snapshotViews[view] {
container.addSubview(snapshot)
}
for subview in view.subviews {
insertGlobalViewTree(view: subview)
}
}
viewToAlpha[view] = view.isOpaque ? .infinity : view.alpha
view.alpha = 0
public subscript(view: UIView) -> MotionTargetState? {
get {
return targetStates[view]
}
set {
targetStates[view] = newValue
}
}
internal func show(view: UIView) {
guard let v = viewToAlpha[view] else {
return
public func clean() {
for (superview, subviews) in superviewToNoSnapshotSubviewMap {
for (index, view) in subviews.reversed() {
superview.insertSubview(view, at: index)
}
}
}
}
// internal
extension MotionContext {
public func hide(view: UIView) {
if viewAlphas[view] == nil, self[view]?.snapshotType != .noSnapshot {
if view is UIVisualEffectView {
view.isHidden = true
viewAlphas[view] = 1
} else {
viewAlphas[view] = view.isOpaque ? .infinity : view.alpha
view.alpha = 0
}
}
}
public func unhide(view: UIView) {
if let oldAlpha = viewAlphas[view] {
if view is UIVisualEffectView {
view.isHidden = false
} else if v == .infinity {
} else if oldAlpha == .infinity {
view.alpha = 1
view.isOpaque = true
} else {
view.alpha = v
view.alpha = oldAlpha
}
viewToAlpha[view] = nil
viewAlphas[view] = nil
}
internal func showAll() {
for v in viewToAlpha.keys {
show(view: v)
}
viewToAlpha.removeAll()
internal func unhideAll() {
for view in viewAlphas.keys {
unhide(view: view)
}
internal func showSubviews(for view: UIView) {
show(view: view)
for subview in view.subviews {
showSubviews(for: subview)
viewAlphas.removeAll()
}
internal func unhide(rootView: UIView) {
unhide(view: rootView)
for subview in rootView.subviews {
unhide(rootView: subview)
}
internal func removeAllSnapshots() {
for (view, snapshot) in snapshotToView {
guard view != snapshot else {
continue
}
// Do not remove when using .useNoSnapshot.
internal func removeAllSnapshots() {
for (view, snapshot) in snapshotViews {
if view != snapshot {
// do not remove when it is using .useNoSnapshot
snapshot.removeFromSuperview()
}
}
}
internal func removeSnapshots(rootView: UIView) {
if let snapshot = snapshotToView[rootView], snapshot != rootView {
if let snapshot = snapshotViews[rootView], snapshot != rootView {
snapshot.removeFromSuperview()
}
for subview in rootView.subviews {
removeSnapshots(rootView: subview)
}
}
internal func snapshots(for view: UIView) -> [UIView] {
internal func snapshots(rootView: UIView) -> [UIView] {
var snapshots = [UIView]()
for v in view.flattenedViewHierarchy {
guard let snapshot = snapshotToView[v] else {
continue
}
for v in rootView.flattenedViewHierarchy {
if let snapshot = snapshotViews[v] {
snapshots.append(snapshot)
}
}
return snapshots
}
internal func loadViewToAlpha(for view: UIView) {
if let v = view.motionAlpha {
view.alpha = v
view.motionAlpha = nil
internal func loadViewAlpha(rootView: UIView) {
if let storedAlpha = rootView.motionStoredAlpha {
rootView.alpha = storedAlpha
rootView.motionStoredAlpha = nil
}
for subview in view.subviews {
loadViewToAlpha(for: subview)
for subview in rootView.subviews {
loadViewAlpha(rootView: subview)
}
}
internal func storeViewToAlpha(for view: UIView) {
view.motionAlpha = viewToAlpha[view]
for subview in view.subviews {
storeViewToAlpha(for: subview)
internal func storeViewAlpha(rootView: UIView) {
rootView.motionStoredAlpha = viewAlphas[rootView]
for subview in rootView.subviews {
storeViewAlpha(rootView: subview)
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
* The MIT License (MIT)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 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:
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 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 UIKit
public class MotionController: NSObject, MotionSubscriber {
/// An optional reference to the animation display link.
fileprivate var displayLink: CADisplayLink? {
willSet {
guard let v = displayLink else {
return
}
v.isPaused = true
v.remove(from: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
/// Base class for managing a Motion transition
public class MotionController: NSObject {
// MARK: Properties
/// context object holding transition informations
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 {
guard let v = displayLink else {
return
if transitioning {
if let progressUpdateObservers = progressUpdateObservers {
for observer in progressUpdateObservers {
observer.motionDidUpdateProgress(progress: progress)
}
}
v.add(to: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
let timePassed = progress * totalDuration
if interactive {
for animator in animators {
animator.seekTo(timePassed: timePassed)
}
} else {
for plugin in plugins where plugin.requirePerFrameCallback {
plugin.seekTo(timePassed: timePassed)
}
}
}
}
}
/// whether or not we are doing a transition
public var transitioning: Bool {
return transitionContainer != nil
}
/// A reference to the animation duration.
fileprivate var duration: TimeInterval = 0
/// container we created to hold all animating views, will be a subview of the
/// transitionContainer when transitioning
public internal(set) var container: UIView!
/**
A reference to the animation total duration,
which is the total running animation time.
*/
fileprivate var totalDuration: TimeInterval = 0
/// this is the container supplied by UIKit
internal var transitionContainer: UIView!
internal var completionCallback: ((Bool) -> Void)?
/// A reference to the animation start time.
fileprivate var startTime: TimeInterval? {
internal var displayLink: CADisplayLink?
internal var progressUpdateObservers: [MotionProgressUpdateObserver]?
/// max duration needed by the default animator and plugins
public internal(set) var totalDuration: TimeInterval = 0.0
/// current animation complete duration.
/// (differs from totalDuration because this one could be the duration for finishing interactive transition)
internal var duration: TimeInterval = 0.0
internal var beginTime: TimeInterval? {
didSet {
guard nil != startTime else {
if beginTime != nil {
if displayLink == nil {
displayLink = CADisplayLink(target: self, selector: #selector(displayUpdate(_:)))
displayLink!.add(to: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
}
} else {
displayLink?.isPaused = true
displayLink?.remove(from: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
displayLink = nil
return
}
guard nil == displayLink else {
return
}
displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLinkUpdate(displayLink:)))
}
func displayUpdate(_ link: CADisplayLink) {
if transitioning, duration > 0, let beginTime = beginTime {
let timePassed = CACurrentMediaTime() - beginTime
if timePassed > duration {
progress = finishing ? 1 : 0
self.beginTime = nil
complete(finished: finishing)
} else {
var completed = timePassed / totalDuration
if !finishing {
completed = 1 - completed
}
/// A reference to the animation elapsed time.
public internal(set) var elapsedTime: TimeInterval = 0 {
didSet {
guard isTransitioning else {
return
completed = max(0, min(1, completed))
progress = completed
}
updateMotionObservers()
updateMotionAnimators()
}
}
/// A reference to a MotionContext.
public internal(set) var context: MotionContext!
internal var finishing: Bool = true
/// A reference to an Array of MotionObservers.
public internal(set) var observers = [MotionObserver]()
internal var processors: [MotionPreprocessor]!
internal var animators: [MotionAnimator]!
internal var plugins: [MotionPlugin]!
/// A reference to an Array of MotionTransitionAnimator.
public internal(set) var animators = [MotionTransitionAnimator]()
internal var animatingViews: [(fromViews: [UIView], toViews: [UIView])]!
/// A reference to the preprocessors.
public internal(set) var preprocessors = [TransitionPreprocessor]()
internal static var enabledPlugins: [MotionPlugin.Type] = []
/// A boolean indicating if a transition is in progress.
public var isTransitioning: Bool {
return nil == transitionContainer
}
internal override init() {}
}
/// A boolean indicating if the animation is finished.
public fileprivate(set) var isFinished = false
public extension MotionController {
// MARK: Interactive Transition
/// A boolean indicating if the animation is interactive.
public var isInteractive: Bool {
return nil == displayLink
/**
Update the progress for the interactive transition.
- Parameters:
- progress: the current progress, must be between -1...1
*/
public func update(progress: Double) {
guard transitioning else { return }
self.beginTime = nil
self.progress = max(-1, min(1, progress))
}
/// An Array of from and to view paris to be animated.
public fileprivate(set) var transitionParis = [(fromViews: [UIView], toViews: [UIView])]()
/**
Finish the interactive transition.
Will stop the interactive transition and animate from the
current state to the **end** state
*/
public func end(animate: Bool = true) {
guard transitioning else { return }
if !animate {
self.complete(finished:true)
return
}
var maxTime: TimeInterval = 0
for animator in self.animators {
maxTime = max(maxTime, animator.resume(timePassed:self.progress * self.totalDuration,
reverse: false))
}
self.complete(after: maxTime, finishing: true)
}
/**
An animation container used within the transitionContainer
during a transition.
Cancel the interactive transition.
Will stop the interactive transition and animate from the
current state to the **begining** state
*/
public fileprivate(set) var container: UIView!
public func cancel(animate: Bool = true) {
guard transitioning else { return }
if !animate {
self.complete(finished:false)
return
}
var maxTime: TimeInterval = 0
for animator in self.animators {
var adjustedProgress = self.progress
if adjustedProgress < 0 {
adjustedProgress = -adjustedProgress
}
maxTime = max(maxTime, animator.resume(timePassed:adjustedProgress * self.totalDuration,
reverse: true))
}
self.complete(after: maxTime, finishing: false)
}
/// Transition container.
public internal(set) var transitionContainer: UIView! {
didSet {
container = UIView(frame: transitionContainer.bounds)
transitionContainer.addSubview(container)
/**
Override modifiers during an interactive animation.
For example:
Motion.shared.apply([.position(x:50, y:50)], to:view)
will set the view's position to 50, 50
- Parameters:
- modifiers: the modifiers to override
- view: the view to override to
*/
public func apply(modifiers: [MotionTransition], to view: UIView) {
guard transitioning else { return }
let targetState = MotionTargetState(modifiers: modifiers)
if let otherView = self.context.pairedView(for: view) {
for animator in self.animators {
animator.apply(state: targetState, to: otherView)
}
}
for animator in self.animators {
animator.apply(state: targetState, to: view)
}
}
}
extension MotionController {
public extension MotionController {
// MARK: Observe Progress
/**
Handles the animation update for the display link.
- Parameter displayLink: A CADisplayLink.animation
Receive callbacks on each animation frame.
Observers will be cleaned when transition completes
- Parameters:
- observer: the observer
*/
@objc
fileprivate func handleDisplayLinkUpdate(displayLink: CADisplayLink) {
guard isTransitioning else {
return
func observeForProgressUpdate(observer: MotionProgressUpdateObserver) {
if progressUpdateObservers == nil {
progressUpdateObservers = []
}
progressUpdateObservers!.append(observer)
}
}
guard 0 < duration else {
return
// internal methods for transition
internal extension MotionController {
/// Load plugins, processors, animators, container, & context
/// must have transitionContainer set already
/// subclass should call context.set(fromViews:toViews) after inserting fromViews & toViews into the container
func prepareForTransition() {
guard transitioning else { fatalError() }
plugins = Motion.enabledPlugins.map({ return $0.init() })
processors = [
IgnoreSubviewModifiersPreprocessor(),
MatchPreprocessor(),
SourcePreprocessor(),
CascadePreprocessor(),
DurationPreprocessor()
]
animators = [
MotionDefaultAnimator<MotionCoreAnimationViewContext>()
]
if #available(iOS 10, tvOS 10, *) {
animators.append(MotionDefaultAnimator<MotionViewPropertyViewContext>())
}
guard let v = startTime else {
return
// There is no covariant in Swift, so we need to add plugins one by one.
for plugin in plugins {
processors.append(plugin)
animators.append(plugin)
}
var t = CACurrentMediaTime() - v
transitionContainer.isUserInteractionEnabled = false
if t > duration {
elapsedTime = isFinished ? 1 : 0
completeTransition()
// a view to hold all the animating views
container = UIView(frame: transitionContainer.bounds)
transitionContainer.addSubview(container)
} else {
t = t / duration
context = MotionContext(container:container)
if !isFinished {
t = 1 - t
for processor in processors {
processor.context = context
}
for animator in animators {
animator.context = context
}
}
elapsedTime = max(0, min(1, t))
func processContext() {
guard transitioning else { fatalError() }
for processor in processors {
processor.process(fromViews: context.fromViews, toViews: context.toViews)
}
}
}
extension MotionController {
fileprivate func updateMotionObservers() {
for v in observers {
v.update(elapsedTime: elapsedTime)
func prepareForAnimation() {
guard transitioning else { fatalError() }
animatingViews = [([UIView], [UIView])]()
for animator in animators {
let currentFromViews = context.fromViews.filter { (view: UIView) -> Bool in
return animator.canAnimate(view: view, appearing: false)
}
let currentToViews = context.toViews.filter { (view: UIView) -> Bool in
return animator.canAnimate(view: view, appearing: true)
}
animatingViews.append((currentFromViews, currentToViews))
}
}
/// Updates the motion animators.
fileprivate func updateMotionAnimators() {
let t = elapsedTime * totalDuration
/// Actually animate the views
/// subclass should call `prepareForTransition` & `prepareForAnimation` before calling `animate`
func animate() {
guard transitioning else { fatalError() }
for (currentFromViews, currentToViews) in animatingViews {
// auto hide all animated views
for view in currentFromViews {
context.hide(view: view)
}
for view in currentToViews {
context.hide(view: view)
}
}
for v in animators {
v.seekTo(elapsedTime: t)
var totalDuration: TimeInterval = 0
var animatorWantsInteractive = false
for (i, animator) in animators.enumerated() {
let duration = animator.animate(fromViews: animatingViews[i].0,
toViews: animatingViews[i].1)
if duration == .infinity {
animatorWantsInteractive = true
} else {
totalDuration = max(totalDuration, duration)
}
}
}
extension MotionController {
/// Completes the transition.
fileprivate func completeTransition() {
cleanMotionAnimators()
cleanTransitionValues()
self.totalDuration = totalDuration
if animatorWantsInteractive {
update(progress: 0)
} else {
complete(after: totalDuration, finishing: true)
}
}
/// Cleans the motion animators Array.
fileprivate func cleanMotionAnimators() {
for v in animators {
v.clean()
func complete(after: TimeInterval, finishing: Bool) {
guard transitioning else { fatalError() }
if after <= 0.001 {
complete(finished: finishing)
return
}
let timePassed = (finishing ? progress : 1 - progress) * totalDuration
self.finishing = finishing
self.duration = after + timePassed
self.beginTime = CACurrentMediaTime() - timePassed
}
/// Cleans the transition values.
fileprivate func cleanTransitionValues() {
startTime = nil
elapsedTime = 0
func complete(finished: Bool) {
guard transitioning else { fatalError() }
for animator in animators {
animator.clean()
}
transitionContainer!.isUserInteractionEnabled = true
let completion = completionCallback
animatingViews = nil
progressUpdateObservers = nil
transitionContainer = nil
completionCallback = nil
container = nil
processors = nil
animators = nil
plugins = nil
context = nil
beginTime = nil
progress = 0
totalDuration = 0
completion?(finished)
}
}
// MARK: Plugin Support
internal extension MotionController {
static func isEnabled(plugin: MotionPlugin.Type) -> Bool {
return enabledPlugins.index(where: { return $0 == plugin}) != nil
}
static func enable(plugin: MotionPlugin.Type) {
disable(plugin: plugin)
enabledPlugins.append(plugin)
}
static func disable(plugin: MotionPlugin.Type) {
if let index = enabledPlugins.index(where: { return $0 == plugin}) {
enabledPlugins.remove(at: index)
}
}
}
internal extension MotionController {
// should call this after `prepareForTransition` & before `processContext`
func insert<T>(preprocessor: MotionPreprocessor, before: T.Type) {
let processorIndex = processors.index {
$0 is T
} ?? processors.count
preprocessor.context = context
processors.insert(preprocessor, at: processorIndex)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public enum MotionCoordinateSpace {
case global
case local
case parent
}
/*
* 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 UIKit
public class MotionIndependentController: MotionController {
public override init() {
super.init()
}
public func transition(rootView: UIView, fromViews: [UIView], toViews: [UIView], completion: ((Bool) -> Void)? = nil) {
transitionContainer = rootView
completionCallback = completion
prepareForTransition()
context.defaultCoordinateSpace = .sameParent
context.set(fromViews: fromViews, toViews: toViews)
processContext()
prepareForAnimation()
animate()
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public protocol MotionObserver {
/**
An update method called when an animation elapsed time is updated
during a transition.
- Parameter elapsedTime: A TimeInterval.
*/
func update(elapsedTime: TimeInterval)
}
public protocol MotionSubscriber {
/// A reference to an Array of MotionObservers.
var observers: [MotionObserver] { get }
}
/*
* 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 UIKit
open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
weak public var context: MotionContext!
/**
Determines whether or not to receive `seekTo` callback on every frame.
Default is false.
When **requirePerFrameCallback** is **false**, the plugin needs to start its own animations inside `animate` & `resume`
The `seekTo` method is only being called during an interactive transition.
When **requirePerFrameCallback** is **true**, the plugin will receive `seekTo` callback on every animation frame. Hence it is possible for the plugin to do per-frame animations without implementing `animate` & `resume`
*/
open var requirePerFrameCallback = false
public override required init() {}
/**
Called before any animation.
Override this method when you want to preprocess modifiers for views
- Parameters:
- context: object holding all parsed and changed modifiers,
- fromViews: A flattened list of all views from source ViewController
- toViews: A flattened list of all views from destination ViewController
To check a view's modifiers:
context[view]
context[view, "modifierName"]
To set a view's modifiers:
context[view] = [("modifier1", ["parameter1"]), ("modifier2", [])]
context[view, "modifier1"] = ["parameter1", "parameter2"]
*/
open func process(fromViews: [UIView], toViews: [UIView]) {}
/**
- Returns: return true if the plugin can handle animating the view.
- Parameters:
- context: object holding all parsed and changed modifiers,
- view: the view to check whether or not the plugin can handle the animation
- appearing: true if the view is appearing(i.e. a view in destination ViewController)
If return true, Motion won't animate and won't let any other plugins animate this view.
The view will also be hidden automatically during the animation.
*/
open func canAnimate(view: UIView, appearing: Bool) -> Bool { return false }
/**
Perform the animation.
Note: views in `fromViews` & `toViews` are hidden already. Unhide then if you need to take snapshots.
- Parameters:
- context: object holding all parsed and changed modifiers,
- fromViews: A flattened list of all views from source ViewController (filtered by `canAnimate`)
- toViews: A flattened list of all views from destination ViewController (filtered by `canAnimate`)
- Returns: The duration needed to complete the animation
*/
open func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval { return 0 }
/**
Called when all animations are completed.
Should perform cleanup and release any reference
*/
open func clean() {}
/**
For supporting interactive animation only.
This method is called when an interactive animation is in place
The plugin should pause the animation, and seek to the given progress
- Parameters:
- timePassed: time of the animation to seek to.
*/
open func seekTo(timePassed: TimeInterval) {}
/**
For supporting interactive animation only.
This method is called when an interactive animation is ended
The plugin should resume the animation.
- Parameters:
- timePassed: will be the same value since last `seekTo`
- reverse: a boolean value indicating whether or not the animation should reverse
*/
open func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval { return 0 }
/**
For supporting interactive animation only.
This method is called when user wants to override animation modifiers during an interactive animation
- Parameters:
- state: the target state to override
- view: the view to override
*/
open func apply(state: MotionTargetState, to view: UIView) {}
}
// methods for enable/disable the current plugin
extension MotionPlugin {
public static var isEnabled: Bool {
get {
return Motion.isEnabled(plugin: self)
}
set {
if newValue {
enable()
} else {
disable()
}
}
}
public static func enable() {
Motion.enable(plugin: self)
}
public static func disable() {
Motion.disable(plugin: self)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public enum MotionSnapshot {
case optimized
case normal
case layerRender
case none
}
/*
* 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 UIKit
public protocol MotionStringConvertible {
static func from(node: ExprNode) -> Self?
}
extension String {
func parse<T: MotionStringConvertible>() -> [T]? {
let lexer = Lexer(input: self)
let parser = Parser(tokens: lexer.tokenize())
do {
let nodes = try parser.parse()
var results = [T]()
for node in nodes {
if let modifier = T.from(node: node) {
results.append(modifier)
} else {
print("\(node.name) doesn't exist in \(T.self)")
}
}
return results
} catch let error {
print("failed to parse \"\(self)\", error: \(error)")
}
return nil
}
func parseOne<T: MotionStringConvertible>() -> T? {
return parse()?.last
}
}
/*
* 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 UIKit
public enum MotionSnapshotType {
/// Will optimize for different type of views
/// For custom views or views with masking, .optimizedDefault might create snapshots
/// that appear differently than the actual view.
/// In that case, use .normal or .slowRender to disable the optimization
case optimized
/// snapshotView(afterScreenUpdates:)
case normal
/// layer.render(in: currentContext)
case layerRender
/// will not create snapshot. animate the view directly.
/// This will mess up the view hierarchy, therefore, view controllers have to rebuild
/// its view structure after the transition finishes
case noSnapshot
}
public enum MotionCoordinateSpace {
case global
case local
case sameParent
}
public struct MotionTargetState {
class MotionTargetStateWrapper {
var state: MotionTargetState
init(state: MotionTargetState) {
self.state = state
}
}
internal var beginState: MotionTargetStateWrapper?
public var beginStateIfMatched: [MotionTransition]?
public var position: CGPoint?
public var size: CGSize?
public var transform: CATransform3D?
public var opacity: Float?
public var cornerRadius: CGFloat?
public var backgroundColor: CGColor?
public var zPosition: CGFloat?
public var contentsRect: CGRect?
public var contentsScale: CGFloat?
public var borderWidth: CGFloat?
public var borderColor: CGColor?
public var shadowColor: CGColor?
public var shadowOpacity: Float?
public var shadowOffset: CGSize?
public var shadowRadius: CGFloat?
public var shadowPath: CGPath?
public var masksToBounds: Bool?
public var displayShadow: Bool = true
public var overlay: (color: CGColor, opacity: CGFloat)?
public var spring: (CGFloat, CGFloat)?
public var delay: TimeInterval = 0
public var duration: TimeInterval?
public var timingFunction: CAMediaTimingFunction?
public var arc: CGFloat?
public var source: String?
public var cascade: (TimeInterval, CascadeDirection, Bool)?
public var ignoreSubviewModifiers: Bool?
public var coordinateSpace: MotionCoordinateSpace?
public var useScaleBasedSizeChange: Bool?
public var snapshotType: MotionSnapshotType?
public var nonFade: Bool = false
public var forceAnimate: Bool = false
public var custom: [String:Any]?
init(modifiers: [MotionTransition]) {
append(contentsOf: modifiers)
}
public mutating func append(_ modifier: MotionTransition) {
modifier.apply(&self)
}
public mutating func append(contentsOf modifiers: [MotionTransition]) {
for modifier in modifiers {
modifier.apply(&self)
}
}
/**
- Returns: custom item for a specific key
*/
public subscript(key: String) -> Any? {
get {
return custom?[key]
}
set {
if custom == nil {
custom = [:]
}
custom![key] = newValue
}
}
}
extension MotionTargetState: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: MotionTransition...) {
append(contentsOf: elements)
}
}
/*
* 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 UIKit
/// used to construct MotionTransition from motionModifierString
extension MotionTransition: MotionStringConvertible {
public static func from(node: ExprNode) -> MotionTransition? {
let name: String = node.name
let parameters: [ExprNode] = (node as? CallNode)?.arguments ?? []
switch name {
case "fade":
return .fade
case "opacity":
return MotionTransition.opacity(parameters.getFloat(0) ?? 1)
case "position":
return .position(CGPoint(x: parameters.getCGFloat(0) ?? 0, y: parameters.getCGFloat(1) ?? 0))
case "size":
return .size(CGSize(width: parameters.getCGFloat(0) ?? 0, height: parameters.getCGFloat(1) ?? 0))
case "scale":
if parameters.count == 1 {
return .scale(parameters.getCGFloat(0) ?? 1)
} else {
return .scale(x: parameters.getCGFloat(0) ?? 1,
y: parameters.getCGFloat(1) ?? 1,
z: parameters.getCGFloat(2) ?? 1)
}
case "rotate":
if parameters.count == 1 {
return .rotate(parameters.getCGFloat(0) ?? 0)
} else {
return .rotate(x: parameters.getCGFloat(0) ?? 0,
y: parameters.getCGFloat(1) ?? 0,
z: parameters.getCGFloat(2) ?? 0)
}
case "translate":
return .translate(x: parameters.getCGFloat(0) ?? 0,
y: parameters.getCGFloat(1) ?? 0,
z: parameters.getCGFloat(2) ?? 0)
case "overlay":
return .overlay(color: UIColor(red: parameters.getCGFloat(0) ?? 1,
green: parameters.getCGFloat(1) ?? 1,
blue: parameters.getCGFloat(2) ?? 1,
alpha: 1),
opacity: parameters.getCGFloat(3) ?? 1)
case "duration":
if let duration = parameters.getDouble(0) {
return .duration(duration)
}
case "durationMatchLongest":
return .durationMatchLongest
case "delay":
if let delay = parameters.getDouble(0) {
return .delay(delay)
}
case "spring":
if #available(iOS 9, *) {
return .spring(stiffness: parameters.getCGFloat(0) ?? 250, damping: parameters.getCGFloat(1) ?? 30)
}
case "timingFunction":
if let c1 = parameters.getFloat(0),
let c2 = parameters.getFloat(1),
let c3 = parameters.getFloat(2),
let c4 = parameters.getFloat(3) {
return .timingFunction(CAMediaTimingFunction(controlPoints: c1, c2, c3, c4))
} else if let name = parameters.get(0)?.name, let timingFunction = CAMediaTimingFunction.from(name:name) {
return .timingFunction(timingFunction)
}
case "arc":
return .arc(intensity: parameters.getCGFloat(0) ?? 1)
case "cascade":
var cascadeDirection = CascadeDirection.topToBottom
if let directionString = parameters.get(1)?.name,
let direction = CascadeDirection(directionString) {
cascadeDirection = direction
}
return .cascade(delta: parameters.getDouble(0) ?? 0.02, direction: cascadeDirection, delayMatchedViews:parameters.getBool(2) ?? false)
case "source":
if let motionID = parameters.get(0)?.name {
return .source(motionID: motionID)
}
case "useGlobalCoordinateSpace":
return .useGlobalCoordinateSpace
case "useSameParentCoordinateSpace":
return .useSameParentCoordinateSpace
case "ignoreSubviewModifiers":
return .ignoreSubviewModifiers(recursive:parameters.getBool(0) ?? false)
case "zPosition":
if let zPosition = parameters.getCGFloat(0) {
return .zPosition(zPosition)
}
case "useOptimizedSnapshot":
return .useOptimizedSnapshot
case "useNormalSnapshot":
return .useNormalSnapshot
case "useLayerRenderSnapshot":
return .useLayerRenderSnapshot
case "useNoSnapshot":
return .useNoSnapshot
case "forceAnimate":
return .forceAnimate
default: break
}
return nil
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
* The MIT License (MIT)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Copyright (C) 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Original Inspiration & Author
* Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 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:
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 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 UIKit
public final class MotionTransition {
/// A reference to the callback that applies the MotionTransitionState.
internal let apply: (inout MotionTransitionState) -> Void
/**
An initializer that accepts a given callback.
- Parameter applyFunction: A given callback.
*/
public init(applyFunction: @escaping (inout MotionTransitionState) -> Void) {
internal let apply:(inout MotionTargetState) -> Void
public init(applyFunction:@escaping (inout MotionTargetState) -> Void) {
apply = applyFunction
}
}
// basic modifiers
extension MotionTransition {
/**
Animates the view with a matching motion identifier.
- Parameter _ identifier: A String.
- Returns: A MotionTransition.
Fade the view during transition
*/
public static func motionIdentifier(_ identifier: String) -> MotionTransition {
return MotionTransition {
$0.motionIdentifier = identifier
}
public static var fade = MotionTransition { targetState in
targetState.opacity = 0
}
/**
Animates the view's current background color to the
given color.
- Parameter color: A UIColor.
- Returns: A MotionTransition.
Set the opacity for the view to animate from/to.
- Parameters:
- opacity: opacity for the view to animate from/to
*/
public static func background(color: UIColor) -> MotionTransition {
return MotionTransition {
$0.backgroundColor = color.cgColor
public static func opacity(_ opacity: Float) -> MotionTransition {
return MotionTransition { targetState in
targetState.opacity = opacity
}
}
/**
Animates the view's current border color to the
given color.
- Parameter color: A UIColor.
- Returns: A MotionTransition.
Force don't fade view during transition
*/
public static func border(color: UIColor) -> MotionTransition {
return MotionTransition {
$0.borderColor = color.cgColor
}
public static var forceNonFade = MotionTransition { targetState in
targetState.nonFade = true
}
/**
Animates the view's current border width to the
given width.
- Parameter width: A CGFloat.
- Returns: A MotionTransition.
Set the position for the view to animate from/to.
- Parameters:
- position: position for the view to animate from/to
*/
public static func border(width: CGFloat) -> MotionTransition {
return MotionTransition {
$0.borderWidth = width
public static func position(_ position: CGPoint) -> MotionTransition {
return MotionTransition { targetState in
targetState.position = position
}
}
/**
Animates the view's current corner radius to the
given radius.
- Parameter radius: A CGFloat.
- Returns: A MotionTransition.
Set the size for the view to animate from/to.
- Parameters:
- size: size for the view to animate from/to
*/
public static func corner(radius: CGFloat) -> MotionTransition {
return MotionTransition {
$0.cornerRadius = radius
public static func size(_ size: CGSize) -> MotionTransition {
return MotionTransition { targetState in
targetState.size = size
}
}
}
// transform modifiers
extension MotionTransition {
/**
Animates the view's current transform (perspective, scale, rotation)
to the given one.
- Parameter _ transform: A CATransform3D.
- Returns: A MotionTransition.
Set the transform for the view to animate from/to. Will override previous perspective, scale, translate, & rotate modifiers
- Parameters:
- t: the CATransform3D object
*/
public static func transform(_ transform: CATransform3D) -> MotionTransition {
return MotionTransition {
$0.transform = transform
public static func transform(_ t: CATransform3D) -> MotionTransition {
return MotionTransition { targetState in
targetState.transform = t
}
}
/**
Animates the view's current perspective to the gievn one through
a CATransform3D object.
- Parameter _ perspective: A CGFloat.
- Returns: A MotionTransition.
Set the perspective on the transform. use in combination with the rotate modifier.
- Parameters:
- perspective: set the camera distance of the transform
*/
public static func perspective(_ perspective: CGFloat) -> MotionTransition {
return MotionTransition {
var t = $0.transform ?? CATransform3DIdentity
t.m34 = 1 / -perspective
$0.transform = t
return MotionTransition { targetState in
var transform = targetState.transform ?? CATransform3DIdentity
transform.m34 = 1.0 / -perspective
targetState.transform = transform
}
}
/**
Animates the view's current rotation to the given x, y,
and z values.
- Parameter x: A CGFloat.
- Parameter y: A CGFloat.
- Parameter z: A CGFloat.
- Returns: A MotionTransition.
Scale 3d
- Parameters:
- x: scale factor on x axis, default 1
- y: scale factor on y axis, default 1
- z: scale factor on z axis, default 1
*/
public static func rotation(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionTransition {
return MotionTransition {
var t = $0.transform ?? CATransform3DIdentity
t = CATransform3DRotate(t, x, 1, 0, 0)
t = CATransform3DRotate(t, y, 0, 1, 0)
$0.transform = CATransform3DRotate(t, z, 0, 0, 1)
public static func scale(x: CGFloat = 1, y: CGFloat = 1, z: CGFloat = 1) -> MotionTransition {
return MotionTransition { targetState in
targetState.transform = CATransform3DScale(targetState.transform ?? CATransform3DIdentity, x, y, z)
}
}
/**
Animates the view's current rotation to the given point.
- Parameter _ point: A CGPoint.
- Parameter z: A CGFloat, default is 0.
- Returns: A MotionTransition.
Scale in x & y axis
- Parameters:
- xy: scale factor in both x & y axis
*/
public static func rotation(_ point: CGPoint, z: CGFloat = 0) -> MotionTransition {
return .rotation(x: point.x, y: point.y, z: z)
public static func scale(_ xy: CGFloat) -> MotionTransition {
return .scale(x: xy, y: xy)
}
/**
Animates the view's current scale to the given x, y, z scale values.
- Parameter x: A CGFloat.
- Parameter y: A CGFloat.
- Parameter z: A CGFloat.
- Returns: A MotionTransition.
Translate 3d
- Parameters:
- x: translation distance on x axis in display pixel, default 0
- y: translation distance on y axis in display pixel, default 0
- z: translation distance on z axis in display pixel, default 0
*/
public static func scale(x: CGFloat = 1, y: CGFloat = 1, z: CGFloat = 1) -> MotionTransition {
return MotionTransition {
$0.transform = CATransform3DScale($0.transform ?? CATransform3DIdentity, x, y, z)
public static func translate(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionTransition {
return MotionTransition { targetState in
targetState.transform = CATransform3DTranslate(targetState.transform ?? CATransform3DIdentity, x, y, z)
}
}
public static func translate(_ point: CGPoint, z: CGFloat = 0) -> MotionTransition {
return translate(x: point.x, y: point.y, z: z)
}
/**
Animates the view's current x & y scale to the given scale value.
- Parameter to scale: A CGFloat.
- Returns: A MotionTransition.
Rotate 3d
- Parameters:
- x: rotation on x axis in radian, default 0
- y: rotation on y axis in radian, default 0
- z: rotation on z axis in radian, default 0
*/
public static func scale(to scale: CGFloat) -> MotionTransition {
return .scale(x: scale, y: scale)
public static func rotate(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionTransition {
return MotionTransition { targetState in
targetState.transform = CATransform3DRotate(targetState.transform ?? CATransform3DIdentity, x, 1, 0, 0)
targetState.transform = CATransform3DRotate(targetState.transform!, y, 0, 1, 0)
targetState.transform = CATransform3DRotate(targetState.transform!, z, 0, 0, 1)
}
}
public static func rotate(_ point: CGPoint, z: CGFloat = 0) -> MotionTransition {
return rotate(x: point.x, y: point.y, z: z)
}
/**
Animates the view's current translation to the given
x, y, and z values.
- Parameter x: A CGFloat.
- Parameter y: A CGFloat.
- Parameter z: A CGFloat.
- Returns: A MotionTransition.
Rotate 2d
- Parameters:
- z: rotation in radian
*/
public static func translate(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionTransition {
return MotionTransition {
$0.transform = CATransform3DTranslate($0.transform ?? CATransform3DIdentity, x, y, z)
public static func rotate(_ z: CGFloat) -> MotionTransition {
return .rotate(z: z)
}
}
extension MotionTransition {
/**
Set the opacity for the view to animate from/to.
- Parameters:
- opacity: opacity for the view to animate from/to
*/
public static func opacity(_ opacity: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.opacity = Float(opacity)
}
}
/**
Animates the view's current translation to the given
point value (x & y), and a z value.
- Parameter to point: A CGPoint.
- Parameter z: A CGFloat, default is 0.
- Returns: A MotionTransition.
Set the backgroundColor for the view to animate from/to.
- Parameters:
- backgroundColor: backgroundColor for the view to animate from/to
*/
public static func translate(to point: CGPoint, z: CGFloat = 0) -> MotionTransition {
return .translate(x: point.x, y: point.y, z: z)
public static func backgroundColor(_ backgroundColor: UIColor) -> MotionTransition {
return MotionTransition { targetState in
targetState.backgroundColor = backgroundColor.cgColor
}
}
/**
Animates the view's current position to the given point.
- Parameter to point: A CGPoint.
- Returns: A MotionTransition.
Set the cornerRadius for the view to animate from/to.
- Parameters:
- cornerRadius: cornerRadius for the view to animate from/to
*/
public static func position(to point: CGPoint) -> MotionTransition {
return MotionTransition {
$0.position = point
public static func cornerRadius(_ cornerRadius: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.cornerRadius = cornerRadius
}
}
/// Fades the view out during a transition.
public static var fadeOut = MotionTransition {
$0.opacity = 0
/**
Set the zPosition for the view to animate from/to.
- Parameters:
- zPosition: zPosition for the view to animate from/to
*/
public static func zPosition(_ zPosition: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.zPosition = zPosition
}
}
/// Fades the view in during a transition.
public static var fadeIn = MotionTransition {
$0.opacity = 1
/**
Set the contentsRect for the view to animate from/to.
- Parameters:
- contentsRect: contentsRect for the view to animate from/to
*/
public static func contentsRect(_ contentsRect: CGRect) -> MotionTransition {
return MotionTransition { targetState in
targetState.contentsRect = contentsRect
}
}
/**
Animates the view's current opacity to the given one.
- Parameter to opacity: A Float value.
- Returns: A MotionTransition.
Set the contentsScale for the view to animate from/to.
- Parameters:
- contentsScale: contentsScale for the view to animate from/to
*/
public static func fade(to opacity: Float) -> MotionTransition {
return MotionTransition {
$0.opacity = opacity
public static func contentsScale(_ contentsScale: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.contentsScale = contentsScale
}
}
/**
Animates the view's current zPosition to the given position.
- Parameter _ position: An Int.
- Returns: A MotionTransition.
Set the borderWidth for the view to animate from/to.
- Parameters:
- borderWidth: borderWidth for the view to animate from/to
*/
public static func zPosition(_ position: CGFloat) -> MotionTransition {
return MotionTransition {
$0.zPosition = position
public static func borderWidth(_ borderWidth: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.borderWidth = borderWidth
}
}
/**
Animates the view's current size to the given one.
- Parameter _ size: A CGSize.
- Returns: A MotionTransition.
Set the borderColor for the view to animate from/to.
- Parameters:
- borderColor: borderColor for the view to animate from/to
*/
public static func size(_ size: CGSize) -> MotionTransition {
return MotionTransition {
$0.size = size
public static func borderColor(_ borderColor: UIColor) -> MotionTransition {
return MotionTransition { targetState in
targetState.borderColor = borderColor.cgColor
}
}
/**
Animates the view's current shadow path to the given one.
- Parameter path: A CGPath.
- Returns: A MotionTransition.
Set the shadowColor for the view to animate from/to.
- Parameters:
- shadowColor: shadowColor for the view to animate from/to
*/
public static func shadow(path: CGPath) -> MotionTransition {
return MotionTransition {
$0.shadowPath = path
public static func shadowColor(_ shadowColor: UIColor) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowColor = shadowColor.cgColor
}
}
/**
Animates the view's current shadow color to the given one.
- Parameter color: A UIColor.
- Returns: A MotionTransition.
Set the shadowOpacity for the view to animate from/to.
- Parameters:
- shadowOpacity: shadowOpacity for the view to animate from/to
*/
public static func shadow(color: UIColor) -> MotionTransition {
return MotionTransition {
$0.shadowColor = color.cgColor
public static func shadowOpacity(_ shadowOpacity: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowOpacity = Float(shadowOpacity)
}
}
/**
Animates the view's current shadow offset to the given one.
- Parameter offset: A CGSize.
- Returns: A MotionTransition.
Set the shadowOffset for the view to animate from/to.
- Parameters:
- shadowOffset: shadowOffset for the view to animate from/to
*/
public static func shadow(offset: CGSize) -> MotionTransition {
return MotionTransition {
$0.shadowOffset = offset
public static func shadowOffset(_ shadowOffset: CGSize) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowOffset = shadowOffset
}
}
/**
Animates the view's current shadow opacity to the given one.
- Parameter opacity: A CGFloat.
- Returns: A MotionTransition.
Set the shadowRadius for the view to animate from/to.
- Parameters:
- shadowRadius: shadowRadius for the view to animate from/to
*/
public static func shadow(opacity: CGFloat) -> MotionTransition {
return MotionTransition {
$0.shadowOpacity = Float(opacity)
public static func shadowRadius(_ shadowRadius: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowRadius = shadowRadius
}
}
/**
Animates the view's current shadow radius to the given one.
- Parameter radius: A CGFloat.
- Returns: A MotionTransition.
Set the shadowPath for the view to animate from/to.
- Parameters:
- shadowPath: shadowPath for the view to animate from/to
*/
public static func shadow(radius: CGFloat) -> MotionTransition {
return MotionTransition {
$0.shadowRadius = radius
public static func shadowPath(_ shadowPath: CGPath) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowPath = shadowPath
}
}
/**
Animates the view's contents rect to the given one.
- Parameter rect: A CGRect.
- Returns: A MotionTransition.
Set the masksToBounds for the view to animate from/to.
- Parameters:
- masksToBounds: masksToBounds for the view to animate from/to
*/
public static func contents(rect: CGRect) -> MotionTransition {
return MotionTransition {
$0.contentsRect = rect
public static func masksToBounds(_ masksToBounds: Bool) -> MotionTransition {
return MotionTransition { targetState in
targetState.masksToBounds = masksToBounds
}
}
/**
Animates the view's contents scale to the given one.
- Parameter scale: A CGFloat.
- Returns: A MotionTransition.
Create an overlay on the animating view.
- Parameters:
- color: color of the overlay
- opacity: opacity of the overlay
*/
public static func contents(scale: CGFloat) -> MotionTransition {
return MotionTransition {
$0.contentsScale = scale
public static func overlay(color: UIColor, opacity: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.overlay = (color.cgColor, opacity)
}
}
}
// timing modifiers
extension MotionTransition {
/**
The duration of the view's animation.
- Parameter _ duration: A TimeInterval.
- Returns: A MotionTransition.
Sets the duration of the animation for a given view. If not used, Motion will use determine the duration based on the distance and size changes.
- Parameters:
- duration: duration of the animation
Note: a duration of .infinity means matching the duration of the longest animation. same as .durationMatchLongest
*/
public static func duration(_ duration: TimeInterval) -> MotionTransition {
return MotionTransition {
$0.duration = duration
return MotionTransition { targetState in
targetState.duration = duration
}
}
/**
Sets the view's animation duration to the longest
running animation within a transition.
Sets the duration of the animation for a given view to match the longest animation of the transition.
*/
public static var preferredDurationMatchesLongest = MotionTransition.duration(.infinity)
public static var durationMatchLongest: MotionTransition = MotionTransition { targetState in
targetState.duration = .infinity
}
/**
Delays the animation of a given view.
- Parameter _ time: TimeInterval.
- Returns: A MotionTransition.
Sets the delay of the animation for a given view.
- Parameters:
- delay: delay of the animation
*/
public static func delay(_ time: TimeInterval) -> MotionTransition {
return MotionTransition {
$0.delay = time
public static func delay(_ delay: TimeInterval) -> MotionTransition {
return MotionTransition { targetState in
targetState.delay = delay
}
}
/**
Sets the view's timing function for the animation.
- Parameter _ timingFunction: A MotionAnimationTimingFunction.
- Returns: A MotionTransition.
Sets the timing function of the animation for a given view. If not used, Motion will use determine the timing function based on whether or not the view is entering or exiting the screen.
- Parameters:
- timingFunction: timing function of the animation
*/
public static func timingFunction(_ timingFunction: MotionAnimationTimingFunction) -> MotionTransition {
return MotionTransition {
$0.timingFunction = timingFunction
public static func timingFunction(_ timingFunction: CAMediaTimingFunction) -> MotionTransition {
return MotionTransition { targetState in
targetState.timingFunction = timingFunction
}
}
/**
Available in iOS 9+, animates a view using the spring API,
given a stiffness and damping.
- Parameter stiffness: A CGFlloat.
- Parameter damping: A CGFloat.
- Returns: A MotionTransition.
(iOS 9+) Use spring animation with custom stiffness & damping. The duration will be automatically calculated. Will be ignored if arc, timingFunction, or duration is set.
- Parameters:
- stiffness: stiffness of the spring
- damping: damping of the spring
*/
@available(iOS 9, *)
public static func spring(stiffness: CGFloat, damping: CGFloat) -> MotionTransition {
return MotionTransition {
$0.spring = (stiffness, damping)
return MotionTransition { targetState in
targetState.spring = (stiffness, damping)
}
}
}
// other modifiers
extension MotionTransition {
/**
Animates the natural curve of a view. A value of 1 represents
a curve in a downward direction, and a value of -1
represents a curve in an upward direction.
- Parameter intensity: A CGFloat.
Transition from/to the state of the view with matching motionID
Will also force the view to use global coordinate space.
The following layer properties will be animated from the given view.
position
bounds.size
cornerRadius
transform
shadowColor
shadowOpacity
shadowOffset
shadowRadius
shadowPath
Note that the following properties **won't** be taken from the source view.
backgroundColor
borderWidth
borderColor
- Parameters:
- motionID: the source view's motionId.
*/
public static func source(motionID: String) -> MotionTransition {
return MotionTransition { targetState in
targetState.source = motionID
}
}
/**
Works in combination with position modifier to apply a natural curve when moving to the destination.
*/
public static var arc: MotionTransition = .arc()
/**
Works in combination with position modifier to apply a natural curve when moving to the destination.
- Parameters:
- intensity: a value of 1 represent a downward natural curve ╰. a value of -1 represent a upward curve ╮.
default is 1.
*/
public static func arc(intensity: CGFloat = 1) -> MotionTransition {
return MotionTransition {
$0.arc = intensity
return MotionTransition { targetState in
targetState.arc = intensity
}
}
/**
Animates subviews with an increasing delay between each animation.
- Parameter delta: A TimeInterval.
- Parameter direction: A MotionCascadeDirection.
- Paramater animationDelayUntilMatchedViews: A boolean indicating whether
or not to delay the subview animation until all have started.
Cascade applys increasing delay modifiers to subviews
*/
public static var cascade: MotionTransition = .cascade()
/**
Cascade applys increasing delay modifiers to subviews
- Parameters:
- delta: delay in between each animation
- direction: cascade direction
- delayMatchedViews: whether or not to delay matched subviews until all cascading animation have started
*/
public static func cascade(delta: TimeInterval = 0.02, direction: MotionCascadeDirection = .topToBottom, animationDelayUntilMatchedViews: Bool = false) -> MotionTransition {
return MotionTransition {
$0.cascade = (delta, direction, animationDelayUntilMatchedViews)
public static func cascade(delta: TimeInterval = 0.02,
direction: CascadeDirection = .topToBottom,
delayMatchedViews: Bool = false) -> MotionTransition {
return MotionTransition { targetState in
targetState.cascade = (delta, direction, delayMatchedViews)
}
}
}
// advance modifiers
extension MotionTransition {
/**
Applies the transition state directly to the view before the animation
begins. For source views, the state is applied immediately. For destination
views, the state is applied at the end of the transition.
Apply modifiers directly to the view at the start of the transition.
The modifiers supplied here won't be animated.
For source views, modifiers are set directly at the begining of the animation.
For destination views, they replace the target state (final appearance).
*/
public static func startWith(animations: [MotionTransition]) -> MotionTransition {
return MotionTransition {
if nil == $0.startState {
$0.startState = MotionTransitionStateWrapper(state: [])
public static func beginWith(modifiers: [MotionTransition]) -> MotionTransition {
return MotionTransition { targetState in
if targetState.beginState == nil {
targetState.beginState = MotionTargetState.MotionTargetStateWrapper(state: [])
}
targetState.beginState!.state.append(contentsOf: modifiers)
}
}
$0.startState!.state.append(contentsOf: animations)
/**
Apply modifiers directly to the view at the start of the transition if the view is matched with another view.
The modifiers supplied here won't be animated.
For source views, modifiers are set directly at the begining of the animation.
For destination views, they replace the target state (final appearance).
*/
public static func beginWithIfMatched(modifiers: [MotionTransition]) -> MotionTransition {
return MotionTransition { targetState in
if targetState.beginStateIfMatched == nil {
targetState.beginStateIfMatched = []
}
targetState.beginStateIfMatched!.append(contentsOf: modifiers)
}
}
/**
Applies the transition state directly to the view before the animation
begins. For source views, the state is applied immediately. For destination
views, the state is applied at the end of the transition. This only takes
affect if the view is matched.
Use global coordinate space.
When using global coordinate space. The view become a independent view that is not a subview of any view.
It won't move when its parent view moves, and won't be affected by parent view's attributes.
When a view is matched, this is automatically enabled.
The `source` modifier will also enable this.
Global coordinate space is default for all views prior to version 0.1.3
*/
public static var useGlobalCoordinateSpace: MotionTransition = MotionTransition { targetState in
targetState.coordinateSpace = .global
}
/**
Use same parent coordinate space.
*/
public static func startWithIfMatched(animations: [MotionTransition]) -> MotionTransition {
return MotionTransition {
if nil == $0.startStateIfMatched {
$0.startStateIfMatched = []
public static var useSameParentCoordinateSpace: MotionTransition = MotionTransition { targetState in
targetState.coordinateSpace = .sameParent
}
$0.startStateIfMatched!.append(contentsOf: animations)
/**
ignore all motionModifiers attributes for a view's direct subviews.
*/
public static var ignoreSubviewModifiers: MotionTransition = .ignoreSubviewModifiers()
/**
ignore all motionModifiers attributes for a view's subviews.
- Parameters:
- recursive: if false, will only ignore direct subviews' modifiers. default false.
*/
public static func ignoreSubviewModifiers(recursive: Bool = false) -> MotionTransition {
return MotionTransition { targetState in
targetState.ignoreSubviewModifiers = recursive
}
}
/**
Will create snapshot optimized for different view type.
For custom views or views with masking, useOptimizedSnapshot might create snapshots
that appear differently than the actual view.
In that case, use .useNormalSnapshot or .useSlowRenderSnapshot to disable the optimization.
This modifier actually does nothing by itself since .useOptimizedSnapshot is the default.
*/
public static var useOptimizedSnapshot: MotionTransition = MotionTransition { targetState in
targetState.snapshotType = .optimized
}
/**
Create snapshot using snapshotView(afterScreenUpdates:).
*/
public static var useNormalSnapshot: MotionTransition = MotionTransition { targetState in
targetState.snapshotType = .normal
}
/**
Create snapshot using layer.render(in: currentContext).
This is slower than .useNormalSnapshot but gives more accurate snapshot for some views (eg. UIStackView).
*/
public static var useLayerRenderSnapshot: MotionTransition = MotionTransition { targetState in
targetState.snapshotType = .layerRender
}
/**
Force Motion to not create any snapshot when animating this view.
This will mess up the view hierarchy, therefore, view controllers have to rebuild
its view structure after the transition finishes.
*/
public static var useNoSnapshot: MotionTransition = MotionTransition { targetState in
targetState.snapshotType = .noSnapshot
}
/**
Force the view to animate (Motion will create animation context & snapshots for them, so that they can be interact)
*/
public static var forceAnimate = MotionTransition { targetState in
targetState.forceAnimate = true
}
/**
Force Motion use scale based size animation. This will convert all .size modifier into .scale modifier.
This is to help Motion animate layers that doesn't support bounds animation. Also gives better performance.
*/
public static var useScaleBasedSizeChange: MotionTransition = MotionTransition { targetState in
targetState.useScaleBasedSizeChange = true
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public protocol MotionTransitionAnimator: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/**
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool
/**
*/
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
/**
*/
func seekTo(elapsedTime: TimeInterval)
/**
*/
func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval
///
func clean()
func apply(motionTransitions: [MotionTransition], to view: UIView)
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
internal class MotionTransitionStateWrapper {
/// A reference to a MotionTransitionState.
internal var state: MotionTransitionState
/**
An initializer that accepts a given MotionTransitionState.
- Parameter state: A MotionTransitionState.
*/
internal init(state: MotionTransitionState) {
self.state = state
}
}
public struct MotionTransitionState {
/// An optional reference to the start state of the view.
internal var startState: MotionTransitionStateWrapper?
/// A reference to the motion identifier.
public var motionIdentifier: String?
public var startStateIfMatched: [MotionTransition]?
public var position: CGPoint?
public var size: CGSize?
public var transform: CATransform3D?
public var opacity: Float?
public var cornerRadius: CGFloat?
public var backgroundColor: CGColor?
public var zPosition: CGFloat?
public var contentsRect: CGRect?
public var contentsScale: CGFloat?
public var borderWidth: CGFloat?
public var borderColor: CGColor?
public var shadowColor: CGColor?
public var shadowOpacity: Float?
public var shadowOffset: CGSize?
public var shadowRadius: CGFloat?
public var shadowPath: CGPath?
public var masksToBounds: Bool?
public var displayShadow: Bool = true
public var overlay: (color: CGColor, opacity: CGFloat)?
public var spring: (CGFloat, CGFloat)?
public var delay: TimeInterval = 0
public var duration: TimeInterval?
public var timingFunction: MotionAnimationTimingFunction?
public var arc: CGFloat?
public var cascade: (TimeInterval, MotionCascadeDirection, Bool)?
public var ignoreSubviewTransitionAnimations: Bool?
public var coordinateSpace: MotionCoordinateSpace?
public var useScaleBasedSizeChange: Bool?
public var motionSnapshot: MotionSnapshot?
public var forceAnimate: Bool = false
public var custom: [String:Any]?
init(transitionAnimations: [MotionTransition]) {
append(contentsOf: transitionAnimations)
}
public mutating func append(_ transitionAnimations: MotionTransition) {
transitionAnimations.apply(&self)
}
public mutating func append(contentsOf transitionAnimations: [MotionTransition]) {
for v in transitionAnimations {
v.apply(&self)
}
}
}
extension MotionTransitionState: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: MotionTransition...) {
append(contentsOf: elements)
}
}
/*
* 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 UIKit
public protocol MotionPreprocessor: class {
weak var context: MotionContext! { get set }
func process(fromViews: [UIView], toViews: [UIView])
}
public protocol MotionAnimator: class {
weak var context: MotionContext! { get set }
func canAnimate(view: UIView, appearing: Bool) -> Bool
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
func clean()
func seekTo(timePassed: TimeInterval)
func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval
func apply(state: MotionTargetState, to view: UIView)
}
public protocol MotionProgressUpdateObserver {
func motionDidUpdateProgress(progress: Double)
}
@objc public protocol MotionViewControllerDelegate {
@objc optional func motionWillStartAnimatingFrom(viewController: UIViewController)
@objc optional func motionDidEndAnimatingFrom(viewController: UIViewController)
@objc optional func motionDidCancelAnimatingFrom(viewController: UIViewController)
@objc optional func motionWillStartTransition()
@objc optional func motionDidEndTransition()
@objc optional func motionDidCancelTransition()
@objc optional func motionWillStartAnimatingTo(viewController: UIViewController)
@objc optional func motionDidEndAnimatingTo(viewController: UIViewController)
@objc optional func motionDidCancelAnimatingTo(viewController: UIViewController)
}
//
// Lexer.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
public enum Token {
case identifier(String, CountableRange<Int>)
case number(Float, CountableRange<Int>)
case parensOpen(CountableRange<Int>)
case parensClose(CountableRange<Int>)
case comma(CountableRange<Int>)
case other(String, CountableRange<Int>)
}
typealias TokenGenerator = (String, CountableRange<Int>) -> Token?
let tokenList: [(String, TokenGenerator)] = [
("[ \t\n]", { _ in nil }),
("[a-zA-Z][a-zA-Z0-9]*", { .identifier($0, $1) }),
("\\-?[0-9.]+", { .number(Float($0)!, $1) }),
("\\(", { .parensOpen($1) }),
("\\)", { .parensClose($1) }),
(",", { .comma($1) })
]
public class Lexer {
let input: String
public init(input: String) {
self.input = input
}
public func tokenize() -> [Token] {
var tokens = [Token]()
var content = input
while !content.characters.isEmpty {
var matched = false
for (pattern, generator) in tokenList {
if let (m, r) = content.match(regex: pattern) {
if let t = generator(m, r) {
tokens.append(t)
}
content = content.substring(from: content.index(content.startIndex, offsetBy: m.characters.count))
matched = true
break
}
}
if !matched {
let index = content.index(content.startIndex, offsetBy: 1)
let intIndex = content.distance(from: content.startIndex, to: index)
tokens.append(.other(content.substring(to: index), intIndex..<intIndex+1))
content = content.substring(from: index)
}
}
return tokens
}
}
//
// Nodes.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
public class ExprNode: CustomStringConvertible, Equatable {
public var range: CountableRange<Int> = 0..<0
public let name: String
public var description: String {
return "ExprNode(name: \"\(name)\")"
}
public init(name: String) {
self.name = name
}
}
public func == (lhs: ExprNode, rhs: ExprNode) -> Bool {
return lhs.description == rhs.description
}
public class NumberNode: ExprNode {
public let value: Float
public override var description: String {
return "NumberNode(value: \(value))"
}
public init(value: Float) {
self.value = value
super.init(name: "\(value)")
}
}
public class VariableNode: ExprNode {
public override var description: String {
return "VariableNode(name: \"\(name)\")"
}
}
public class BinaryOpNode: ExprNode {
public let lhs: ExprNode
public let rhs: ExprNode
public override var description: String {
return "BinaryOpNode(name: \"\(name)\", lhs: \(lhs), rhs: \(rhs))"
}
public init(name: String, lhs: ExprNode, rhs: ExprNode) {
self.lhs = lhs
self.rhs = rhs
super.init(name: "\(name)")
}
}
public class CallNode: ExprNode {
public let arguments: [ExprNode]
public override var description: String {
return "CallNode(name: \"\(name)\", arguments: \(arguments))"
}
public init(name: String, arguments: [ExprNode]) {
self.arguments = arguments
super.init(name: "\(name)")
}
}
public class PrototypeNode: ExprNode {
public let argumentNames: [String]
public override var description: String {
return "PrototypeNode(name: \"\(name)\", argumentNames: \(argumentNames))"
}
public init(name: String, argumentNames: [String]) {
self.argumentNames = argumentNames
super.init(name: "\(name)")
}
}
public class FunctionNode: ExprNode {
public let prototype: PrototypeNode
public let body: ExprNode
public override var description: String {
return "FunctionNode(prototype: \(prototype), body: \(body))"
}
public init(prototype: PrototypeNode, body: ExprNode) {
self.prototype = prototype
self.body = body
super.init(name: "\(prototype.name)")
}
}
//
// Parser.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
public enum ParseError: Error {
case unexpectToken
case undefinedOperator(String)
case expectCharacter(Character)
case expectExpression
case expectArgumentList
case expectFunctionName
}
public class Parser {
let tokens: [Token]
var index = 0
public init(tokens: [Token]) {
self.tokens = tokens
}
func peekCurrentToken() -> Token {
if index >= tokens.count {
return .other("", 0..<0)
}
return tokens[index]
}
@discardableResult func popCurrentToken() -> Token {
defer { index += 1 }
return tokens[index]
}
func parseNumber() throws -> ExprNode {
guard case let .number(value, _) = popCurrentToken() else {
throw ParseError.unexpectToken
}
return NumberNode(value: value)
}
func parseExpression() throws -> ExprNode {
let node = try parsePrimary()
return try parseBinaryOp(node: node)
}
func parseParens() throws -> ExprNode {
guard case .parensOpen = popCurrentToken() else {
throw ParseError.expectCharacter("(")
}
let exp = try parseExpression()
guard case .parensClose = popCurrentToken() else {
throw ParseError.expectCharacter(")")
}
return exp
}
func parseIdentifier() throws -> ExprNode {
guard case let .identifier(name, _) = popCurrentToken() else {
throw ParseError.unexpectToken
}
guard case .parensOpen = peekCurrentToken() else {
return VariableNode(name: name)
}
popCurrentToken()
var arguments = [ExprNode]()
if case .parensClose = peekCurrentToken() {
} else {
while true {
let argument = try parseExpression()
arguments.append(argument)
if case .parensClose = peekCurrentToken() {
break
}
guard case .comma = popCurrentToken() else {
throw ParseError.expectArgumentList
}
}
}
popCurrentToken()
return CallNode(name: name, arguments: arguments)
}
func parsePrimary() throws -> ExprNode {
switch peekCurrentToken() {
case .identifier:
return try parseIdentifier()
case .number:
return try parseNumber()
case .parensOpen:
return try parseParens()
default:
throw ParseError.expectExpression
}
}
let operatorPrecedence: [String: Int] = [
"+": 20,
"-": 20,
"*": 40,
"/": 40
]
func getCurrentTokenPrecedence() throws -> Int {
guard index < tokens.count else {
return -1
}
guard case let .other(op, _) = peekCurrentToken() else {
return -1
}
guard let precedence = operatorPrecedence[op] else {
throw ParseError.undefinedOperator(op)
}
return precedence
}
func parseBinaryOp(node: ExprNode, exprPrecedence: Int = 0) throws -> ExprNode {
var lhs = node
while true {
let tokenPrecedence = try getCurrentTokenPrecedence()
if tokenPrecedence < exprPrecedence {
return lhs
}
guard case let .other(op, _) = popCurrentToken() else {
throw ParseError.unexpectToken
}
var rhs = try parsePrimary()
let nextPrecedence = try getCurrentTokenPrecedence()
if tokenPrecedence < nextPrecedence {
rhs = try parseBinaryOp(node: rhs, exprPrecedence: tokenPrecedence+1)
}
lhs = BinaryOpNode(name: op, lhs: lhs, rhs: rhs)
}
}
public func parse() throws -> [ExprNode] {
index = 0
var nodes = [ExprNode]()
while index < tokens.count {
let expr = try parsePrimary()
nodes.append(expr)
}
return nodes
}
}
//
// Regex.swift
// Kaleidoscope
//
// Created by Matthew Cheok on 15/11/15.
// Copyright © 2015 Matthew Cheok. All rights reserved.
//
import Foundation
var expressions = [String: NSRegularExpression]()
public extension String {
public func match(regex: String) -> (String, CountableRange<Int>)? {
let expression: NSRegularExpression
if let exists = expressions[regex] {
expression = exists
} else {
do {
expression = try NSRegularExpression(pattern: "^\(regex)", options: [])
expressions[regex] = expression
} catch {
return nil
}
}
let range = expression.rangeOfFirstMatch(in: self, options: [], range: NSRange(0 ..< self.utf16.count))
if range.location != NSNotFound {
return ((self as NSString).substring(with: range), range.location ..< range.location + range.length )
}
return nil
}
}
//
// CascadeEffect.swift
/*
* 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 UIKit
class BasePreprocessor: MotionPreprocessor {
weak public var context: MotionContext!
func process(fromViews: [UIView], toViews: [UIView]) {}
}
//
// CascadeEffect.swift
/*
* 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 UIKit
public enum CascadeDirection {
case topToBottom
case bottomToTop
case leftToRight
case rightToLeft
case radial(center:CGPoint)
case inverseRadial(center:CGPoint)
var comparator: (UIView, UIView) -> Bool {
switch self {
case .topToBottom:
return { return $0.frame.minY < $1.frame.minY }
case .bottomToTop:
return { return $0.frame.maxY == $1.frame.maxY ? $0.frame.maxX > $1.frame.maxX : $0.frame.maxY > $1.frame.maxY }
case .leftToRight:
return { return $0.frame.minX < $1.frame.minX }
case .rightToLeft:
return { return $0.frame.maxX > $1.frame.maxX }
case .radial(let center):
return { return $0.center.distance(center) < $1.center.distance(center) }
case .inverseRadial(let center):
return { return $0.center.distance(center) > $1.center.distance(center) }
}
}
init?(_ string: String) {
switch string {
case "bottomToTop":
self = .bottomToTop
case "leftToRight":
self = .leftToRight
case "rightToLeft":
self = .rightToLeft
case "topToBottom":
self = .topToBottom
default:
return nil
}
}
}
class CascadePreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
process(views:fromViews)
process(views:toViews)
}
func process(views: [UIView]) {
for view in views {
guard let (deltaTime, direction, delayMatchedViews) = context[view]?.cascade else { continue }
var parentView = view
if view is UITableView, let wrapperView = view.subviews.get(0) {
parentView = wrapperView
}
let sortedSubviews = parentView.subviews.sorted(by: direction.comparator)
let initialDelay = context[view]!.delay
let finalDelay = TimeInterval(sortedSubviews.count) * deltaTime + initialDelay
for (i, subview) in sortedSubviews.enumerated() {
let delay = TimeInterval(i) * deltaTime + initialDelay
func applyDelay(view: UIView) {
if context.pairedView(for: view) == nil {
context[view]?.delay = delay
} else if delayMatchedViews, let paired = context.pairedView(for: view) {
context[view]?.delay = finalDelay
context[paired]?.delay = finalDelay
}
for subview in view.subviews {
applyDelay(view: subview)
}
}
applyDelay(view: subview)
}
}
}
}
//
// DurationPreprocessor.swift
// Motion
//
// Created by Luke on 3/16/17.
// Copyright © 2017 Luke Zhao. All rights reserved.
//
import UIKit
class DurationPreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
var maxDuration: TimeInterval = 0
maxDuration = applyOptimizedDurationIfNoDuration(views:fromViews)
maxDuration = max(maxDuration, applyOptimizedDurationIfNoDuration(views:toViews))
setDurationForInfiniteDuration(views: fromViews, duration: maxDuration)
setDurationForInfiniteDuration(views: toViews, duration: maxDuration)
}
func optimizedDurationFor(view: UIView) -> TimeInterval {
let targetState = context[view]!
return view.optimizedDuration(fromPosition: context.container.convert(view.layer.position, from: view.superview),
toPosition: targetState.position,
size: targetState.size,
transform: targetState.transform)
}
func applyOptimizedDurationIfNoDuration(views: [UIView]) -> TimeInterval {
var maxDuration: TimeInterval = 0
for view in views where context[view] != nil {
if context[view]?.duration == nil {
context[view]!.duration = optimizedDurationFor(view: view)
}
if context[view]!.duration! == .infinity {
maxDuration = max(maxDuration, optimizedDurationFor(view: view))
} else {
maxDuration = max(maxDuration, context[view]!.duration!)
}
}
return maxDuration
}
func setDurationForInfiniteDuration(views: [UIView], duration: TimeInterval) {
for view in views where context[view]?.duration == .infinity {
context[view]!.duration = duration
}
}
}
/*
* 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 UIKit
class IgnoreSubviewModifiersPreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
process(views:fromViews)
process(views:toViews)
}
func process(views: [UIView]) {
for view in views {
guard let recursive = context[view]?.ignoreSubviewModifiers else { continue }
var parentView = view
if view is UITableView, let wrapperView = view.subviews.get(0) {
parentView = wrapperView
}
if recursive {
cleanSubviewModifiers(parentView)
} else {
for subview in parentView.subviews {
context[subview] = nil
}
}
}
}
private func cleanSubviewModifiers(_ parentView: UIView) {
for view in parentView.subviews {
context[view] = nil
cleanSubviewModifiers(view)
}
}
}
/*
* 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 UIKit
class MatchPreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let id = tv.motionID, let fv = context.sourceView(for: id) else { continue }
var tvState = context[tv] ?? MotionTargetState()
var fvState = context[fv] ?? MotionTargetState()
if let beginStateIfMatched = tvState.beginStateIfMatched {
tvState.append(.beginWith(modifiers: beginStateIfMatched))
}
if let beginStateIfMatched = fvState.beginStateIfMatched {
fvState.append(.beginWith(modifiers: beginStateIfMatched))
}
// match is just a two-way source effect
tvState.source = id
fvState.source = id
fvState.arc = tvState.arc
fvState.duration = tvState.duration
fvState.timingFunction = tvState.timingFunction
fvState.delay = tvState.delay
fvState.spring = tvState.spring
tvState.opacity = 0
let forceNonFade = tvState.nonFade || fvState.nonFade
let isNonOpaque = !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1
if !forceNonFade && isNonOpaque {
// cross fade if from/toViews are not opaque
fvState.opacity = 0
} else {
// no cross fade in this case, fromView is always displayed during the transition.
fvState.opacity = nil
// we dont want two shadows showing up. Therefore we disable toView's shadow when fromView is able to display its shadow
if !fv.layer.masksToBounds && fvState.displayShadow {
tvState.displayShadow = false
}
}
context[tv] = tvState
context[fv] = fvState
}
}
}
/*
* 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 UIKit
class SourcePreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
for fv in fromViews {
guard let id = context[fv]?.source,
let tv = context.destinationView(for: id) else { continue }
prepareFor(view: fv, targetView: tv)
}
for tv in toViews {
guard let id = context[tv]?.source,
let fv = context.sourceView(for: id) else { continue }
prepareFor(view: tv, targetView: fv)
}
}
func prepareFor(view: UIView, targetView: UIView) {
let targetPos = context.container.convert(targetView.layer.position, from: targetView.superview!)
var state = context[view]!
// use global coordinate space since over target position is converted from the global container
state.coordinateSpace = .global
// remove incompatible options
state.position = targetPos
state.transform = nil
state.size = nil
state.cornerRadius = nil
if view.bounds.size != targetView.bounds.size {
state.size = targetView.bounds.size
}
if view.layer.cornerRadius != targetView.layer.cornerRadius {
state.cornerRadius = targetView.layer.cornerRadius
}
if view.layer.transform != targetView.layer.transform {
state.transform = targetView.layer.transform
}
if view.layer.shadowColor != targetView.layer.shadowColor {
state.shadowColor = targetView.layer.shadowColor
}
if view.layer.shadowOpacity != targetView.layer.shadowOpacity {
state.shadowOpacity = targetView.layer.shadowOpacity
}
if view.layer.shadowOffset != targetView.layer.shadowOffset {
state.shadowOffset = targetView.layer.shadowOffset
}
if view.layer.shadowRadius != targetView.layer.shadowRadius {
state.shadowRadius = targetView.layer.shadowRadius
}
if view.layer.shadowPath != targetView.layer.shadowPath {
state.shadowPath = targetView.layer.shadowPath
}
if view.layer.contentsRect != targetView.layer.contentsRect {
state.contentsRect = targetView.layer.contentsRect
}
if view.layer.contentsScale != targetView.layer.contentsScale {
state.contentsScale = targetView.layer.contentsScale
}
context[view] = state
}
}
// The MIT License (MIT)
//
// 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 UIKit
public class SourcePreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
prepare(fromViews: fromViews)
prepare(toViews: toViews)
}
}
extension SourcePreprocessor {
/**
Prepares the from views.
- Parameter fromViews: An Array of UIViews.
*/
fileprivate func prepare(fromViews: [UIView]) {
for fv in fromViews {
guard let identifier = context.viewToMotionTransitionState[fv]?.motionIdentifier else {
continue
}
guard let tv = context.destinationIdentifierToView[identifier] else {
continue
}
prepare(view: fv, for: tv)
}
}
/**
Prepares the to views.
- Parameter fromViews: An Array of UIViews.
*/
fileprivate func prepare(toViews: [UIView]) {
for tv in context.toViews {
guard let identifier = context.viewToMotionTransitionState[tv]?.motionIdentifier else {
continue
}
guard let fv = context.sourceIdentifierToView[identifier] else {
continue
}
prepare(view: tv, for: fv)
}
}
fileprivate func prepare(view: UIView, for targetView: UIView) {
guard var state = context.viewToMotionTransitionState[view] else {
return
}
state.coordinateSpace = .global
state.position = context.container.convert(targetView.layer.position, from: targetView.superview!)
state.transform = nil
state.size = nil
state.cornerRadius = nil
if view.bounds.size != targetView.bounds.size {
state.size = targetView.bounds.size
}
if view.layer.cornerRadius != targetView.layer.cornerRadius {
state.cornerRadius = targetView.layer.cornerRadius
}
if view.layer.transform != targetView.layer.transform {
state.transform = targetView.layer.transform
}
if view.layer.shadowColor != targetView.layer.shadowColor {
state.shadowColor = targetView.layer.shadowColor
}
if view.layer.shadowOpacity != targetView.layer.shadowOpacity {
state.shadowOpacity = targetView.layer.shadowOpacity
}
if view.layer.shadowOffset != targetView.layer.shadowOffset {
state.shadowOffset = targetView.layer.shadowOffset
}
if view.layer.shadowRadius != targetView.layer.shadowRadius {
state.shadowRadius = targetView.layer.shadowRadius
}
if view.layer.shadowPath != targetView.layer.shadowPath {
state.shadowPath = targetView.layer.shadowPath
}
if view.layer.contentsRect != targetView.layer.contentsRect {
state.contentsRect = targetView.layer.contentsRect
}
if view.layer.contentsScale != targetView.layer.contentsScale {
state.contentsScale = targetView.layer.contentsScale
}
context.viewToMotionTransitionState[view] = state
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public protocol TransitionPreprocessor: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView])
}
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