Commit 19e673bf by Daniel Dahan

partial rework to MotionTransition and its relating parts

parent 06ec8deb
......@@ -42,7 +42,6 @@
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 */; };
......@@ -86,7 +85,6 @@
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>"; };
......@@ -163,7 +161,6 @@
isa = PBXGroup;
children = (
968989DB1EE65F2B003B8F3D /* MotionPreprocessor.swift */,
96AEB6871EE4610F009A3BE0 /* BasePreprocessor.swift */,
96AEB6881EE4610F009A3BE0 /* CascadePreprocessor.swift */,
96AEB6891EE4610F009A3BE0 /* DurationPreprocessor.swift */,
96AEB68A1EE4610F009A3BE0 /* IgnoreSubviewModifiersPreprocessor.swift */,
......@@ -335,7 +332,6 @@
96AEB69E1EE4610F009A3BE0 /* MotionController.swift in Sources */,
96AEB6A11EE4610F009A3BE0 /* MotionTransition.swift in Sources */,
96AEB6971EE4610F009A3BE0 /* Motion+CG.swift in Sources */,
96AEB6AB1EE4610F009A3BE0 /* BasePreprocessor.swift in Sources */,
963150DA1EE51EB4002B0D42 /* MotionAnimationFillMode.swift in Sources */,
96AEB6AC1EE4610F009A3BE0 /* CascadePreprocessor.swift in Sources */,
);
......
......@@ -94,12 +94,12 @@ extension MotionDebugPlugin:MotionDebugViewDelegate {
Motion.shared.update(elapsedTime: Double(seekValue))
}
func onPerspectiveChanged(translation: CGPoint, rotation: CGFloat, scale: CGFloat) {
func onPerspectiveChanged(translation: CGPoint, rotate: 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)
t = CATransform3DRotate(t, rotate, 0, 1, 0)
Motion.shared.container.layer.sublayerTransform = t
}
......@@ -160,7 +160,7 @@ extension MotionDebugPlugin:MotionDebugViewDelegate {
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)
t = CATransform3DRotate(t, debugView!.rotate, 0, 1, 0)
} else {
for v in Motion.shared.container.subviews {
animateZPosition(view:v, to:self.zPositionMap[v] ?? 0)
......
......@@ -31,7 +31,7 @@ import UIKit
#if os(iOS)
protocol MotionDebugViewDelegate: class {
func onProcessSliderChanged(progress: Float)
func onPerspectiveChanged(translation: CGPoint, rotation: CGFloat, scale: CGFloat)
func onPerspectiveChanged(translation: CGPoint, rotate: CGFloat, scale: CGFloat)
func on3D(wants3D: Bool)
func onDisplayArcCurve(wantsCurve: Bool)
func onDone()
......@@ -56,7 +56,7 @@ class MotionDebugView: UIView {
}
var showOnTop: Bool = false
var rotation: CGFloat = .pi / 6
var rotate: CGFloat = .pi / 6
var scale: CGFloat = 0.6
var translation: CGPoint = .zero
var progress: Float {
......@@ -142,15 +142,15 @@ class MotionDebugView: UIView {
var startRotation: CGFloat = 0
@objc public func pan() {
if panGR.state == .began {
startRotation = rotation
startRotation = rotate
}
rotation = startRotation + panGR.translation(in: nil).x / 150
if rotation > .pi {
rotation -= 2 * .pi
} else if rotation < -.pi {
rotation += 2 * .pi
rotate = startRotation + panGR.translation(in: nil).x / 150
if rotate > .pi {
rotate -= 2 * .pi
} else if rotate < -.pi {
rotate += 2 * .pi
}
delegate?.onPerspectiveChanged(translation:translation, rotation: rotation, scale:scale)
delegate?.onPerspectiveChanged(translation:translation, rotate: rotate, scale:scale)
}
var startLocation: CGPoint = .zero
......@@ -167,7 +167,7 @@ class MotionDebugView: UIView {
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)
delegate?.onPerspectiveChanged(translation:translation, rotate: rotate, scale:scale)
}
default:
break
......
......@@ -209,8 +209,10 @@ extension MotionDefaultAnimationType: MotionStringConvertible {
}
}
class DefaultAnimationPreprocessor: BasePreprocessor {
class DefaultAnimationPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
weak var motion: Motion?
init(motion: Motion) {
......@@ -232,7 +234,12 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
return rtn
}
override func process(fromViews: [UIView], toViews: [UIView]) {
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
guard let motion = motion else { return }
var defaultAnimation = motion.defaultAnimation
let inNavigationController = motion.isNavigationController
......@@ -279,60 +286,60 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
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),
let shadowState: [MotionTransition] = [.shadow(opacity: 0.5),
.shadow(color: .black),
.shadow(radius: 5),
.shadow(offset: .zero),
.masksToBounds(false)]
switch defaultAnimation {
case .push(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false) / 3),
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: 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, isAppearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true) / 3),
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true) / 3),
.overlay(color: .black, opacity: 0.1)])
case .slide(let direction):
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false))])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true))])
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false))])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true))])
case .zoomSlide(let direction):
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false)), .scale(0.8)])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)), .scale(0.8)])
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)), .scale(to: 0.8)])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)), .scale(to: 0.8)])
case .cover(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: 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, isAppearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1)])
case .pageIn(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.scale(0.7),
context[fromView]!.append(contentsOf: [.scale(to: 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, isAppearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.scale(0.7),
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.scale(to: 0.7),
.overlay(color: .black, opacity: 0.1)])
case .fade:
// TODO: clean up this. overFullScreen logic shouldn't be here
......@@ -348,15 +355,15 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
}
#endif
context[toView]!.append(.durationMatchLongest)
context[fromView]!.append(.durationMatchLongest)
context[toView]!.append(.preferredDurationMatchesLongest)
context[fromView]!.append(.preferredDurationMatchesLongest)
case .zoom:
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.scale(1.3), .fade])
context[toView]!.append(contentsOf: [.scale(0.7)])
context[fromView]!.append(contentsOf: [.scale(to: 1.3), .fade])
context[toView]!.append(contentsOf: [.scale(to: 0.7)])
case .zoomOut:
context[toView]!.append(contentsOf: [.scale(1.3), .fade])
context[fromView]!.append(contentsOf: [.scale(0.7)])
context[toView]!.append(contentsOf: [.scale(to: 1.3), .fade])
context[fromView]!.append(contentsOf: [.scale(to: 0.7)])
default:
fatalError("Not implemented")
}
......
......@@ -232,30 +232,30 @@ extension CALayer {
case let .transform(transform):
a.append(MotionBasicAnimation.transform(transform: transform))
case let .rotationAngle(angle):
let rotate = MotionBasicAnimation.rotation(angle: angle)
case let .rotate(angle):
let rotate = MotionBasicAnimation.rotate(angle: angle)
a.append(rotate)
case let .rotationAngleX(angle):
a.append(MotionBasicAnimation.rotationX(angle: angle))
case let .rotateX(angle):
a.append(MotionBasicAnimation.rotateX(angle: angle))
case let .rotationAngleY(angle):
a.append(MotionBasicAnimation.rotationY(angle: angle))
case let .rotateY(angle):
a.append(MotionBasicAnimation.rotateY(angle: angle))
case let .rotationAngleZ(angle):
a.append(MotionBasicAnimation.rotationZ(angle: angle))
case let .rotateZ(angle):
a.append(MotionBasicAnimation.rotateZ(angle: angle))
case let .spin(rotations):
a.append(MotionBasicAnimation.spin(rotations: rotations))
case let .spin(rotates):
a.append(MotionBasicAnimation.spin(rotates: rotates))
case let .spinX(rotations):
a.append(MotionBasicAnimation.spinX(rotations: rotations))
case let .spinX(rotates):
a.append(MotionBasicAnimation.spinX(rotates: rotates))
case let .spinY(rotations):
a.append(MotionBasicAnimation.spinY(rotations: rotations))
case let .spinY(rotates):
a.append(MotionBasicAnimation.spinY(rotates: rotates))
case let .spinZ(rotations):
a.append(MotionBasicAnimation.spinZ(rotations: rotations))
case let .spinZ(rotates):
a.append(MotionBasicAnimation.spinZ(rotates: rotates))
case let .scale(to):
a.append(MotionBasicAnimation.scale(to: to))
......
......@@ -29,7 +29,7 @@
import UIKit
fileprivate let parameterRegex = "(?:\\-?\\d+(\\.?\\d+)?)|\\w+"
fileprivate let modifiersRegex = "(\\w+)(?:\\(([^\\)]*)\\))?"
fileprivate let transitionsRegex = "(\\w+)(?:\\(([^\\)]*)\\))?"
internal extension NSObject {
func copyWithArchiver() -> Any? {
......
......@@ -170,7 +170,7 @@ public extension UIView {
}
extension UIView {
/// Computes the rotation of the view.
/// Computes the rotate of the view.
open var motionRotationAngle: CGFloat {
get {
return CGFloat(atan2f(Float(transform.b), Float(transform.a))) * 180 / CGFloat(Double.pi)
......
......@@ -41,10 +41,10 @@ public enum MotionAnimation {
case borderWidth(CGFloat)
case cornerRadius(CGFloat)
case transform(CATransform3D)
case rotationAngle(CGFloat)
case rotationAngleX(CGFloat)
case rotationAngleY(CGFloat)
case rotationAngleZ(CGFloat)
case rotate(CGFloat)
case rotateX(CGFloat)
case rotateY(CGFloat)
case rotateZ(CGFloat)
case spin(CGFloat)
case spinX(CGFloat)
case spinY(CGFloat)
......@@ -81,10 +81,10 @@ public enum MotionAnimationKeyPath: String {
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 rotate = "transform.rotate"
case rotateX = "transform.rotate.x"
case rotateY = "transform.rotate.y"
case rotateZ = "transform.rotate.z"
case scale = "transform.scale"
case scaleX = "transform.scale.x"
case scaleY = "transform.scale.y"
......@@ -184,90 +184,90 @@ public struct MotionBasicAnimation {
}
/**
Creates a CABasicAnimation for the transform.rotation key path.
Creates a CABasicAnimation for the transform.rotate key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotation(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation)
public static func rotate(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotate)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.x key path.
Creates a CABasicAnimation for the transform.rotate.x key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationX(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX)
public static func rotateX(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotateX)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.y key path.
Creates a CABasicAnimation for the transform.rotate.y key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationY(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY)
public static func rotateY(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotateY)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.z key path.
Creates a CABasicAnimation for the transform.rotate.z key path.
- Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation.
*/
public static func rotationZ(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ)
public static func rotateZ(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotateZ)
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.
Creates a CABasicAnimation for the transform.rotate key path.
- Parameter rotates: 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))
public static func spin(rotates: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotate)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotates))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.x key path.
- Parameter rotations: An optional CGFloat.
Creates a CABasicAnimation for the transform.rotate.x key path.
- Parameter rotates: 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))
public static func spinX(rotates: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotateX)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotates))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.y key path.
- Parameter rotations: An optional CGFloat.
Creates a CABasicAnimation for the transform.rotate.y key path.
- Parameter rotates: 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))
public static func spinY(rotates: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotateY)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotates))
return animation
}
/**
Creates a CABasicAnimation for the transform.rotation.z key path.
- Parameter rotations: An optional CGFloat.
Creates a CABasicAnimation for the transform.rotate.z key path.
- Parameter rotates: 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))
public static func spinZ(rotates: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotateZ)
animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotates))
return animation
}
......
......@@ -47,18 +47,18 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
/**
Called before any animation.
Override this method when you want to preprocess modifiers for views
Override this method when you want to preprocess transitions for views
- Parameters:
- context: object holding all parsed and changed modifiers,
- context: object holding all parsed and changed transitions,
- 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:
To check a view's transitions:
context[view]
context[view, "modifierName"]
To set a view's modifiers:
To set a view's transitions:
context[view] = [("modifier1", ["parameter1"]), ("modifier2", [])]
context[view, "modifier1"] = ["parameter1", "parameter2"]
......@@ -69,7 +69,7 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
/**
- Returns: return true if the plugin can handle animating the view.
- Parameters:
- context: object holding all parsed and changed modifiers,
- context: object holding all parsed and changed transitions,
- view: the view to check whether or not the plugin can handle the animation
- isAppearing: true if the view is isAppearing(i.e. a view in destination ViewController)
If return true, Motion won't animate and won't let any other plugins animate this view.
......@@ -82,7 +82,7 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
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,
- context: object holding all parsed and changed transitions,
- 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
......@@ -121,7 +121,7 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
/**
For supporting interactive animation only.
This method is called when user wants to override animation modifiers during an interactive animation
This method is called when user wants to override animation transitions during an interactive animation
- Parameters:
- state: the target state to override
......
......@@ -93,7 +93,7 @@ public struct MotionTargetState {
public var timingFunction: CAMediaTimingFunction?
public var arc: CGFloat?
public var source: String?
public var motionIdentifier: String?
public var cascade: (TimeInterval, CascadeDirection, Bool)?
public var ignoreSubviewModifiers: Bool?
......
......@@ -38,14 +38,14 @@ extension MotionTransition: MotionStringConvertible {
case "fade":
return .fade
case "opacity":
return MotionTransition.opacity(parameters.getFloat(0) ?? 1)
return MotionTransition.fade(to: parameters.getFloat(0) ?? 1)
case "position":
return .position(CGPoint(x: parameters.getCGFloat(0) ?? 0, y: parameters.getCGFloat(1) ?? 0))
return .position(to: 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)
return .scale(to: parameters.getCGFloat(0) ?? 1)
} else {
return .scale(x: parameters.getCGFloat(0) ?? 1,
y: parameters.getCGFloat(1) ?? 1,
......@@ -73,8 +73,8 @@ extension MotionTransition: MotionStringConvertible {
if let duration = parameters.getDouble(0) {
return .duration(duration)
}
case "durationMatchLongest":
return .durationMatchLongest
case "preferredDurationMatchesLongest":
return .preferredDurationMatchesLongest
case "delay":
if let delay = parameters.getDouble(0) {
return .delay(delay)
......@@ -100,10 +100,10 @@ extension MotionTransition: MotionStringConvertible {
let direction = CascadeDirection(directionString) {
cascadeDirection = direction
}
return .cascade(delta: parameters.getDouble(0) ?? 0.02, direction: cascadeDirection, delayMatchedViews:parameters.getBool(2) ?? false)
return .cascade(delta: parameters.getDouble(0) ?? 0.02, direction: cascadeDirection, animationDelayUntilMatchedViews:parameters.getBool(2) ?? false)
case "source":
if let motionIdentifier = parameters.get(0)?.name {
return .source(motionIdentifier: motionIdentifier)
return .motionIdentifier(motionIdentifier)
}
case "useGlobalCoordinateSpace":
return .useGlobalCoordinateSpace
......
......@@ -29,571 +29,542 @@
import UIKit
public final class MotionTransition {
internal let apply:(inout MotionTargetState) -> Void
public init(applyFunction:@escaping (inout MotionTargetState) -> Void) {
apply = applyFunction
}
}
// basic modifiers
extension MotionTransition {
/**
Fade the view during transition
*/
public static var fade = MotionTransition { targetState in
targetState.opacity = 0
}
/**
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: Float) -> MotionTransition {
return MotionTransition { targetState in
targetState.opacity = opacity
}
}
/**
Force don't fade view during transition
*/
public static var forceNonFade = MotionTransition { targetState in
targetState.nonFade = true
}
/**
Set the position for the view to animate from/to.
- Parameters:
- position: position for the view to animate from/to
*/
public static func position(_ position: CGPoint) -> MotionTransition {
return MotionTransition { targetState in
targetState.position = position
}
}
/**
Set the size for the view to animate from/to.
- Parameters:
- size: size for the view to animate from/to
*/
public static func size(_ size: CGSize) -> MotionTransition {
return MotionTransition { targetState in
targetState.size = size
}
}
}
// transform modifiers
extension 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(_ t: CATransform3D) -> MotionTransition {
return MotionTransition { targetState in
targetState.transform = t
}
}
/**
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 { targetState in
var transform = targetState.transform ?? CATransform3DIdentity
transform.m34 = 1.0 / -perspective
targetState.transform = transform
}
}
/**
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 scale(x: CGFloat = 1, y: CGFloat = 1, z: CGFloat = 1) -> MotionTransition {
return MotionTransition { targetState in
targetState.transform = CATransform3DScale(targetState.transform ?? CATransform3DIdentity, x, y, z)
}
}
/**
Scale in x & y axis
- Parameters:
- xy: scale factor in both x & y axis
*/
public static func scale(_ xy: CGFloat) -> MotionTransition {
return .scale(x: xy, y: xy)
}
/**
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 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)
}
/**
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 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)
}
/**
Rotate 2d
- Parameters:
- z: rotation in radian
*/
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)
}
}
/**
Set the backgroundColor for the view to animate from/to.
- Parameters:
- backgroundColor: backgroundColor for the view to animate from/to
*/
public static func backgroundColor(_ backgroundColor: UIColor) -> MotionTransition {
return MotionTransition { targetState in
targetState.backgroundColor = backgroundColor.cgColor
}
}
/**
Set the cornerRadius for the view to animate from/to.
- Parameters:
- cornerRadius: cornerRadius for the view to animate from/to
*/
public static func cornerRadius(_ cornerRadius: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.cornerRadius = cornerRadius
}
}
/**
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
}
}
/**
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
}
}
/**
Set the contentsScale for the view to animate from/to.
- Parameters:
- contentsScale: contentsScale for the view to animate from/to
*/
public static func contentsScale(_ contentsScale: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.contentsScale = contentsScale
}
}
/**
Set the borderWidth for the view to animate from/to.
- Parameters:
- borderWidth: borderWidth for the view to animate from/to
*/
public static func borderWidth(_ borderWidth: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.borderWidth = borderWidth
}
}
/**
Set the borderColor for the view to animate from/to.
- Parameters:
- borderColor: borderColor for the view to animate from/to
*/
public static func borderColor(_ borderColor: UIColor) -> MotionTransition {
return MotionTransition { targetState in
targetState.borderColor = borderColor.cgColor
}
}
/**
Set the shadowColor for the view to animate from/to.
- Parameters:
- shadowColor: shadowColor for the view to animate from/to
*/
public static func shadowColor(_ shadowColor: UIColor) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowColor = shadowColor.cgColor
}
}
/**
Set the shadowOpacity for the view to animate from/to.
- Parameters:
- shadowOpacity: shadowOpacity for the view to animate from/to
*/
public static func shadowOpacity(_ shadowOpacity: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowOpacity = Float(shadowOpacity)
}
}
/**
Set the shadowOffset for the view to animate from/to.
- Parameters:
- shadowOffset: shadowOffset for the view to animate from/to
*/
public static func shadowOffset(_ shadowOffset: CGSize) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowOffset = shadowOffset
}
}
/**
Set the shadowRadius for the view to animate from/to.
- Parameters:
- shadowRadius: shadowRadius for the view to animate from/to
*/
public static func shadowRadius(_ shadowRadius: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowRadius = shadowRadius
}
}
/**
Set the shadowPath for the view to animate from/to.
- Parameters:
- shadowPath: shadowPath for the view to animate from/to
*/
public static func shadowPath(_ shadowPath: CGPath) -> MotionTransition {
return MotionTransition { targetState in
targetState.shadowPath = shadowPath
}
}
/**
Set the masksToBounds for the view to animate from/to.
- Parameters:
- masksToBounds: masksToBounds for the view to animate from/to
*/
public static func masksToBounds(_ masksToBounds: Bool) -> MotionTransition {
return MotionTransition { targetState in
targetState.masksToBounds = masksToBounds
}
}
/**
Create an overlay on the animating view.
- Parameters:
- color: color of the overlay
- opacity: opacity of the overlay
*/
public static func overlay(color: UIColor, opacity: CGFloat) -> MotionTransition {
return MotionTransition { targetState in
targetState.overlay = (color.cgColor, opacity)
}
}
}
// timing modifiers
extension 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 { targetState in
targetState.duration = duration
}
}
/**
Sets the duration of the animation for a given view to match the longest animation of the transition.
*/
public static var durationMatchLongest: MotionTransition = MotionTransition { targetState in
targetState.duration = .infinity
}
/**
Sets the delay of the animation for a given view.
- Parameters:
- delay: delay of the animation
*/
public static func delay(_ delay: TimeInterval) -> MotionTransition {
return MotionTransition { targetState in
targetState.delay = delay
}
}
/**
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: CAMediaTimingFunction) -> MotionTransition {
return MotionTransition { targetState in
targetState.timingFunction = timingFunction
}
}
/**
(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 { targetState in
targetState.spring = (stiffness, damping)
}
}
/// A reference to the callback that applies the MotionTargetState.
internal let apply: (inout MotionTargetState) -> Void
/**
An initializer that accepts a given callback.
- Parameter applyFunction: A given callback.
*/
public init(applyFunction: @escaping (inout MotionTargetState) -> Void) {
apply = applyFunction
}
}
// other modifiers
extension MotionTransition {
/**
Transition from/to the state of the view with matching motionIdentifier
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:
- motionIdentifier: the source view's motionId.
*/
public static func source(motionIdentifier: String) -> MotionTransition {
return MotionTransition { targetState in
targetState.source = motionIdentifier
}
}
/**
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 { targetState in
targetState.arc = intensity
}
}
/**
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: CascadeDirection = .topToBottom,
delayMatchedViews: Bool = false) -> MotionTransition {
return MotionTransition { targetState in
targetState.cascade = (delta, direction, delayMatchedViews)
}
}
/**
Animates the view with a matching motion identifier.
- Parameter _ identifier: A String.
- Returns: A MotionTransition.
*/
public static func motionIdentifier(_ identifier: String) -> MotionTransition {
return MotionTransition {
$0.motionIdentifier = identifier
}
}
/**
Animates the view's current masksToBounds to the
given masksToBounds.
- Parameter masksToBounds: A boolean value indicating the
masksToBounds state.
- Returns: A MotionTransition.
*/
public static func masksToBounds(_ masksToBounds: Bool) -> MotionTransition {
return MotionTransition {
$0.masksToBounds = masksToBounds
}
}
/**
Animates the view's current background color to the
given color.
- Parameter color: A UIColor.
- Returns: A MotionTransition.
*/
public static func background(color: UIColor) -> MotionTransition {
return MotionTransition {
$0.backgroundColor = color.cgColor
}
}
/**
Animates the view's current border color to the
given color.
- Parameter color: A UIColor.
- Returns: A MotionTransition.
*/
public static func border(color: UIColor) -> MotionTransition {
return MotionTransition {
$0.borderColor = color.cgColor
}
}
/**
Animates the view's current border width to the
given width.
- Parameter width: A CGFloat.
- Returns: A MotionTransition.
*/
public static func border(width: CGFloat) -> MotionTransition {
return MotionTransition {
$0.borderWidth = width
}
}
/**
Animates the view's current corner radius to the
given radius.
- Parameter radius: A CGFloat.
- Returns: A MotionTransition.
*/
public static func corner(radius: CGFloat) -> MotionTransition {
return MotionTransition {
$0.cornerRadius = radius
}
}
/**
Animates the view's current transform (perspective, scale, rotate)
to the given one.
- Parameter _ transform: A CATransform3D.
- Returns: A MotionTransition.
*/
public static func transform(_ transform: CATransform3D) -> MotionTransition {
return MotionTransition {
$0.transform = transform
}
}
/**
Animates the view's current perspective to the gievn one through
a CATransform3D object.
- Parameter _ perspective: A CGFloat.
- Returns: A MotionTransition.
*/
public static func perspective(_ perspective: CGFloat) -> MotionTransition {
return MotionTransition {
var t = $0.transform ?? CATransform3DIdentity
t.m34 = 1 / -perspective
$0.transform = t
}
}
/**
Animates the view's current rotate to the given x, y,
and z values.
- Parameter x: A CGFloat.
- Parameter y: A CGFloat.
- Parameter z: A CGFloat.
- Returns: A MotionTransition.
*/
public static func rotate(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)
}
}
/**
Animates the view's current rotate to the given point.
- Parameter _ point: A CGPoint.
- Parameter z: A CGFloat, default is 0.
- Returns: A MotionTransition.
*/
public static func rotate(_ point: CGPoint, z: CGFloat = 0) -> MotionTransition {
return .rotate(x: point.x, y: point.y, z: z)
}
/**
Rotate 2d.
- Parameter _ z: A CGFloat.
- Returns: A MotionTransition.
*/
public static func rotate(_ z: CGFloat) -> MotionTransition {
return .rotate(z: z)
}
/**
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.
*/
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)
}
}
/**
Animates the view's current x & y scale to the given scale value.
- Parameter to scale: A CGFloat.
- Returns: A MotionTransition.
*/
public static func scale(to scale: CGFloat) -> MotionTransition {
return .scale(x: scale, y: scale)
}
/**
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.
*/
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)
}
}
/**
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.
*/
public static func translate(to point: CGPoint, z: CGFloat = 0) -> MotionTransition {
return .translate(x: point.x, y: point.y, z: z)
}
/**
Animates the view's current position to the given point.
- Parameter to point: A CGPoint.
- Returns: A MotionTransition.
*/
public static func position(to point: CGPoint) -> MotionTransition {
return MotionTransition {
$0.position = point
}
}
/// Forces the view to not fade during a transition.
public static var forceNonFade = MotionTransition { targetState in
targetState.nonFade = true
}
/// Fades the view out during a transition.
public static var fade = MotionTransition {
$0.opacity = 0
}
/**
Animates the view's current opacity to the given one.
- Parameter to opacity: A Float value.
- Returns: A MotionTransition.
*/
public static func fade(to opacity: Float) -> MotionTransition {
return MotionTransition {
$0.opacity = opacity
}
}
/**
Animates the view's current zPosition to the given position.
- Parameter _ position: An Int.
- Returns: A MotionTransition.
*/
public static func zPosition(_ position: CGFloat) -> MotionTransition {
return MotionTransition {
$0.zPosition = position
}
}
/**
Animates the view's current size to the given one.
- Parameter _ size: A CGSize.
- Returns: A MotionTransition.
*/
public static func size(_ size: CGSize) -> MotionTransition {
return MotionTransition {
$0.size = size
}
}
/**
Animates the view's current shadow path to the given one.
- Parameter path: A CGPath.
- Returns: A MotionTransition.
*/
public static func shadow(path: CGPath) -> MotionTransition {
return MotionTransition {
$0.shadowPath = path
}
}
/**
Animates the view's current shadow color to the given one.
- Parameter color: A UIColor.
- Returns: A MotionTransition.
*/
public static func shadow(color: UIColor) -> MotionTransition {
return MotionTransition {
$0.shadowColor = color.cgColor
}
}
/**
Animates the view's current shadow offset to the given one.
- Parameter offset: A CGSize.
- Returns: A MotionTransition.
*/
public static func shadow(offset: CGSize) -> MotionTransition {
return MotionTransition {
$0.shadowOffset = offset
}
}
/**
Animates the view's current shadow opacity to the given one.
- Parameter opacity: A CGFloat.
- Returns: A MotionTransition.
*/
public static func shadow(opacity: CGFloat) -> MotionTransition {
return MotionTransition {
$0.shadowOpacity = Float(opacity)
}
}
/**
Animates the view's current shadow radius to the given one.
- Parameter radius: A CGFloat.
- Returns: A MotionTransition.
*/
public static func shadow(radius: CGFloat) -> MotionTransition {
return MotionTransition {
$0.shadowRadius = radius
}
}
/**
Animates the view's contents rect to the given one.
- Parameter rect: A CGRect.
- Returns: A MotionTransition.
*/
public static func contents(rect: CGRect) -> MotionTransition {
return MotionTransition {
$0.contentsRect = rect
}
}
/**
Animates the view's contents scale to the given one.
- Parameter scale: A CGFloat.
- Returns: A MotionTransition.
*/
public static func contents(scale: CGFloat) -> MotionTransition {
return MotionTransition {
$0.contentsScale = scale
}
}
/**
The duration of the view's animation.
- Parameter _ duration: A TimeInterval.
- Returns: A MotionTransition.
*/
public static func duration(_ duration: TimeInterval) -> MotionTransition {
return MotionTransition {
$0.duration = duration
}
}
/**
Sets the view's animation duration to the longest
running animation within a transition.
*/
public static var preferredDurationMatchesLongest = MotionTransition.duration(.infinity)
/**
Delays the animation of a given view.
- Parameter _ time: TimeInterval.
- Returns: A MotionTransition.
*/
public static func delay(_ time: TimeInterval) -> MotionTransition {
return MotionTransition {
$0.delay = time
}
}
/**
Sets the view's timing function for the animation.
- Parameter _ timingFunction: A CAMediaTimingFunction.
- Returns: A MotionTransition.
*/
public static func timingFunction(_ timingFunction: CAMediaTimingFunction) -> MotionTransition {
return MotionTransition {
$0.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.
*/
@available(iOS 9, *)
public static func spring(stiffness: CGFloat, damping: CGFloat) -> MotionTransition {
return MotionTransition {
$0.spring = (stiffness, damping)
}
}
/**
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.
- Returns: A MotionTransition.
*/
public static func arc(intensity: CGFloat = 1) -> MotionTransition {
return MotionTransition {
$0.arc = intensity
}
}
/**
Animates subviews with an increasing delay between each animation.
- Parameter delta: A TimeInterval.
- Parameter direction: A CascadeDirection.
- Parameter animationDelayUntilMatchedViews: A boolean indicating whether
or not to delay the subview animation until all have started.
- Returns: A MotionTransition.
*/
public static func cascade(delta: TimeInterval = 0.02, direction: CascadeDirection = .topToBottom, animationDelayUntilMatchedViews: Bool = false) -> MotionTransition {
return MotionTransition {
$0.cascade = (delta, direction, animationDelayUntilMatchedViews)
}
}
/**
Creates an overlay on the animating view with a given color and opacity.
- Parameter color: A UIColor.
- Parameter opacity: A CGFloat.
- Returns: A MotionTransition.
*/
public static func overlay(color: UIColor, opacity: CGFloat) -> MotionTransition {
return MotionTransition {
$0.overlay = (color.cgColor, opacity)
}
}
}
// advance modifiers
// advance transitions
extension MotionTransition {
/**
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 beginWith(modifiers: [MotionTransition]) -> MotionTransition {
return MotionTransition { targetState in
if targetState.beginState == nil {
targetState.beginState = MotionTargetState.MotionTargetStateWrapper(state: [])
}
targetState.beginState!.state.append(contentsOf: modifiers)
}
}
/**
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)
}
}
/**
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 var useSameParentCoordinateSpace: MotionTransition = MotionTransition { targetState in
targetState.coordinateSpace = .sameParent
}
/**
ignore all motionTransitions attributes for a view's direct subviews.
*/
public static var ignoreSubviewModifiers: MotionTransition = .ignoreSubviewModifiers()
/**
ignore all motionTransitions 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
}
/**
Apply transitions directly to the view at the start of the transition.
The transitions supplied here won't be animated.
For source views, transitions are set directly at the begining of the animation.
For destination views, they replace the target state (final appearance).
*/
public static func beginWith(transitions: [MotionTransition]) -> MotionTransition {
return MotionTransition { targetState in
if targetState.beginState == nil {
targetState.beginState = MotionTargetState.MotionTargetStateWrapper(state: [])
}
targetState.beginState!.state.append(contentsOf: transitions)
}
}
/**
Apply transitions directly to the view at the start of the transition if the view is matched with another view.
The transitions supplied here won't be animated.
For source views, transitions are set directly at the begining of the animation.
For destination views, they replace the target state (final appearance).
*/
public static func beginWithIfMatched(transitions: [MotionTransition]) -> MotionTransition {
return MotionTransition { targetState in
if targetState.beginStateIfMatched == nil {
targetState.beginStateIfMatched = []
}
targetState.beginStateIfMatched!.append(contentsOf: transitions)
}
}
/**
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 var useSameParentCoordinateSpace: MotionTransition = MotionTransition { targetState in
targetState.coordinateSpace = .sameParent
}
/**
ignore all motionTransitions attributes for a view's direct subviews.
*/
public static var ignoreSubviewModifiers: MotionTransition = .ignoreSubviewModifiers()
/**
ignore all motionTransitions attributes for a view's subviews.
- Parameters:
- recursive: if false, will only ignore direct subviews' transitions. 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
}
}
/*
* 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]) {}
}
......@@ -70,8 +70,11 @@ public enum CascadeDirection {
}
}
class CascadePreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
class CascadePreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
func process(fromViews: [UIView], toViews: [UIView]) {
process(views:fromViews)
process(views:toViews)
}
......
......@@ -8,15 +8,22 @@
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)
}
class DurationPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
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]!
......
......@@ -28,8 +28,11 @@
import UIKit
class IgnoreSubviewModifiersPreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
class IgnoreSubviewModifiersPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
func process(fromViews: [UIView], toViews: [UIView]) {
process(views:fromViews)
process(views:toViews)
}
......
......@@ -28,49 +28,57 @@
import UIKit
class MatchPreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let id = tv.motionIdentifier, 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
class MatchPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let id = tv.motionIdentifier, 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(transitions: beginStateIfMatched))
}
if let beginStateIfMatched = fvState.beginStateIfMatched {
fvState.append(.beginWith(transitions: beginStateIfMatched))
}
// match is just a two-way source effect
tvState.motionIdentifier = id
fvState.motionIdentifier = 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
}
}
context[tv] = tvState
context[fv] = fvState
}
}
}
......@@ -28,15 +28,18 @@
import UIKit
class SourcePreprocessor: BasePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) {
class SourcePreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
weak var context: MotionContext!
func process(fromViews: [UIView], toViews: [UIView]) {
for fv in fromViews {
guard let id = context[fv]?.source,
guard let id = context[fv]?.motionIdentifier,
let tv = context.destinationView(for: id) else { continue }
prepareFor(view: fv, targetView: tv)
}
for tv in toViews {
guard let id = context[tv]?.source,
guard let id = context[tv]?.motionIdentifier,
let fv = context.sourceView(for: id) else { continue }
prepareFor(view: tv, targetView: fv)
}
......
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