Commit 1bce1231 by Daniel Dahan

reworked MotionDefaultAnimator

parent 2caa4eb1
......@@ -11,6 +11,7 @@
963150D21EE50DA6002B0D42 /* Motion+Obj-C.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963150D11EE50DA6002B0D42 /* Motion+Obj-C.swift */; };
963150D61EE51C7A002B0D42 /* MotionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963150D41EE51C7A002B0D42 /* MotionAnimation.swift */; };
963150DA1EE51EB4002B0D42 /* MotionAnimationFillMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963150D91EE51EB4002B0D42 /* MotionAnimationFillMode.swift */; };
968989B91EE5B34B003B8F3D /* MotionHasInsertOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 968989B81EE5B34B003B8F3D /* MotionHasInsertOrder.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 */; };
......@@ -52,6 +53,7 @@
963150D11EE50DA6002B0D42 /* Motion+Obj-C.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+Obj-C.swift"; sourceTree = "<group>"; };
963150D41EE51C7A002B0D42 /* MotionAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimation.swift; sourceTree = "<group>"; };
963150D91EE51EB4002B0D42 /* MotionAnimationFillMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimationFillMode.swift; sourceTree = "<group>"; };
968989B81EE5B34B003B8F3D /* MotionHasInsertOrder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionHasInsertOrder.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>"; };
......@@ -111,6 +113,7 @@
96AEB6661EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift */,
96AEB6671EE4610F009A3BE0 /* MotionDefaultAnimator.swift */,
96AEB6681EE4610F009A3BE0 /* MotionViewPropertyViewContext.swift */,
968989B81EE5B34B003B8F3D /* MotionHasInsertOrder.swift */,
);
path = Animator;
sourceTree = "<group>";
......@@ -296,6 +299,7 @@
96AEB69D1EE4610F009A3BE0 /* Motion.swift in Sources */,
963150D61EE51C7A002B0D42 /* MotionAnimation.swift in Sources */,
96AEB6A21EE4610F009A3BE0 /* MotionTransition+MotionStringConvertible.swift in Sources */,
968989B91EE5B34B003B8F3D /* MotionHasInsertOrder.swift in Sources */,
96AEB6A51EE4610F009A3BE0 /* MotionTargetState.swift in Sources */,
96AEB6961EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift in Sources */,
96AEB6941EE4610F009A3BE0 /* Motion+Array.swift in Sources */,
......
......@@ -81,19 +81,13 @@ internal class MotionAnimatorViewContext {
}
/**
Applies the given state to the target state.
- Parameter state: A MotionTargetState.
*/
func apply(state: MotionTargetState) {}
/**
Resumes the animation with a given elapsed time and
optional reversed boolean.
- Parameter elapsedTime: A TimeInterval.
- Parameter at elapsedTime: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation
or not.
*/
func resume(elapsedTime: TimeInterval, isReversed: Bool) {}
func resume(at elapsedTime: TimeInterval, isReversed: Bool) {}
/**
Moves the animation to the given elapsed time.
......@@ -102,6 +96,12 @@ internal class MotionAnimatorViewContext {
func seek(to elapsedTime: TimeInterval) {}
/**
Applies the given state to the target state.
- Parameter state: A MotionTargetState.
*/
func apply(state: MotionTargetState) {}
/**
Starts the animations with an appearing boolean flag.
- Parameter isAppearing: A boolean value whether the view
is appearing or not.
......
......@@ -80,7 +80,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
}
}
override func resume(elapsedTime: TimeInterval, isReversed: Bool) {
override func resume(at elapsedTime: TimeInterval, isReversed: Bool) {
for (key, (fromValue, toValue)) in transitionStates {
transitionStates[key] = (currentValue(for: key), !isReversed ? toValue : fromValue)
}
......@@ -318,7 +318,7 @@ extension MotionCoreAnimationViewContext {
}
/**
Constructs a map of key paths to animation values.
Constructs a map of key paths to animation state values.
- Parameter targetState state: A MotionTargetState.
- Returns: A map of key paths to animation values.
*/
......
......@@ -28,93 +28,102 @@
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
internal class MotionDefaultAnimator<T: MotionAnimatorViewContext>: MotionAnimator, MotionHasInsertOrder {
/// A reference to a MotionContext.
weak public var context: MotionContext!
let realFromPos = CGPoint.zero.transform(fromTransform) + fromPos
let realToPos = CGPoint.zero.transform(toTransform) + toPos
/// An index of views to their corresponding animator context.
var viewToContexts = [UIView: T]()
let realFromSize = fromSize.transform(fromTransform)
let realToSize = toSize.transform(toTransform)
var insertToViewFirst = false
}
let movePoints = (realFromPos.distance(realToPos) + realFromSize.point.distance(realToSize.point))
extension MotionDefaultAnimator {
/**
Animates a given view.
- Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
fileprivate func animate(view: UIView, isAppearing: Bool) {
let s = context.snapshotView(for: view)
let v = T(animator: self, snapshot: s, targetState: context[view]!)
// duration is 0.2 @ 0 to 0.375 @ 500
let duration = 0.208 + Double(movePoints.clamp(0, 500)) / 3000
return duration
viewToContexts[view] = v
v.startAnimations(isAppearing: isAppearing)
}
}
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
extension MotionDefaultAnimator {
func clean() {
for v in viewToContexts.values {
v.clean()
}
public func seekTo(elapsedTime: TimeInterval) {
for viewContext in viewContexts.values {
viewContext.seek(to: elapsedTime)
viewToContexts.removeAll()
insertToViewFirst = false
}
func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
guard let state = context[view] else {
return false
}
return T.canAnimate(view: view, state: state, isAppearing: isAppearing)
}
public func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
for (_, context) in viewContexts {
context.resume(elapsedTime: elapsedTime, isReversed: isReversed)
duration = max(duration, context.duration)
if insertToViewFirst {
for v in toViews {
animate(view: v, isAppearing: true)
}
return duration
for v in fromViews {
animate(view: v, isAppearing: false)
}
} else {
for v in fromViews {
animate(view: v, isAppearing: false)
}
public func apply(state: MotionTargetState, to view: UIView) {
if let context = viewContexts[view] {
context.apply(state:state)
for v in toViews {
animate(view: v, isAppearing: true)
}
}
public func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
guard let state = context[view] else { return false }
return ViewContext.canAnimate(view: view, state: state, isAppearing: isAppearing)
for v in viewToContexts.values {
duration = max(duration, v.duration)
}
public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
return duration
}
if insertToViewFirst {
for v in toViews { animate(view: v, isAppearing: true) }
for v in fromViews { animate(view: v, isAppearing: false) }
} else {
for v in fromViews { animate(view: v, isAppearing: false) }
for v in toViews { animate(view: v, isAppearing: true) }
func seek(to elapsedTime: TimeInterval) {
for v in viewToContexts.values {
v.seek(to: elapsedTime)
}
}
for viewContext in viewContexts.values {
duration = max(duration, viewContext.duration)
func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
var duration: TimeInterval = 0
for (_, v) in viewToContexts {
v.resume(at: elapsedTime, isReversed: isReversed)
duration = max(duration, v.duration)
}
return duration
}
func animate(view: UIView, isAppearing: Bool) {
let snapshot = context.snapshotView(for: view)
let viewContext = ViewContext(animator:self, snapshot: snapshot, targetState: context[view]!)
viewContexts[view] = viewContext
viewContext.startAnimations(isAppearing: isAppearing)
func apply(state: MotionTargetState, to view: UIView) {
guard let v = viewToContexts[view] else {
return
}
public func clean() {
for vc in viewContexts.values {
vc.clean()
}
viewContexts.removeAll()
insertToViewFirst = false
v.apply(state: state)
}
}
/*
* 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.
*/
internal protocol MotionHasInsertOrder: class {
/// A boolean indicating whether to insert the to-view first or not.
var insertToViewFirst: Bool { get set }
}
......@@ -59,7 +59,7 @@ public class MotionDebugPlugin: MotionPlugin {
return .infinity
}
public override func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
public override func resume(at: TimeInterval, isReversed: Bool) -> TimeInterval {
guard let debugView = debugView else { return 0.4 }
debugView.delegate = nil
......
......@@ -28,6 +28,29 @@
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
}
}
public extension UIView {
private struct AssociatedKeys {
static var motionID = "motionID"
......
......@@ -203,7 +203,7 @@ internal extension Motion {
insertToViewFirst = true
}
for animator in animators {
if let animator = animator as? HasInsertOrder {
if let animator = animator as? MotionHasInsertOrder {
animator.insertToViewFirst = insertToViewFirst
}
}
......
......@@ -50,11 +50,11 @@ public class MotionController: NSObject {
let elapsedTime = progress * totalDuration
if interactive {
for animator in animators {
animator.seekTo(elapsedTime: elapsedTime)
animator.seek(to: elapsedTime)
}
} else {
for plugin in plugins where plugin.requirePerFrameCallback {
plugin.seekTo(elapsedTime: elapsedTime)
plugin.seek(to: elapsedTime)
}
}
}
......@@ -156,8 +156,7 @@ public extension MotionController {
}
var maxTime: TimeInterval = 0
for animator in self.animators {
maxTime = max(maxTime, animator.resume(elapsedTime:self.progress * self.totalDuration,
isReversed: false))
maxTime = max(maxTime, animator.resume(at: self.progress * self.totalDuration, isReversed: false))
}
self.complete(after: maxTime, finishing: true)
}
......@@ -179,8 +178,7 @@ public extension MotionController {
if adjustedProgress < 0 {
adjustedProgress = -adjustedProgress
}
maxTime = max(maxTime, animator.resume(elapsedTime:adjustedProgress * self.totalDuration,
isReversed: true))
maxTime = max(maxTime, animator.resume(at: adjustedProgress * self.totalDuration, isReversed: true))
}
self.complete(after: maxTime, finishing: false)
}
......
......@@ -105,7 +105,7 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
- Parameters:
- elapsedTime: time of the animation to seek to.
*/
open func seekTo(elapsedTime: TimeInterval) {}
open func seek(to elapsedTime: TimeInterval) {}
/**
For supporting interactive animation only.
......@@ -116,7 +116,7 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
- elapsedTime: will be the same value since last `seekTo`
- reverse: a boolean value indicating whether or not the animation should reverse
*/
open func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval { return 0 }
open func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval { return 0 }
/**
For supporting interactive animation only.
......
......@@ -34,13 +34,48 @@ public protocol MotionPreprocessor: class {
}
public protocol MotionAnimator: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/// Cleans the contexts.
func clean()
/**
A function that determines if a view can be animated.
- Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool
/**
Animates the from-views to the to-views.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
- Returns: A TimeInterval.
*/
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
func clean()
func seekTo(elapsedTime: TimeInterval)
func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval
/**
Moves the view's animation to the given elapsed time.
- Parameter to elapsedTime: A TimeInterval.
*/
func seek(to elapsedTime: TimeInterval)
/**
Resumes the animation with a given elapsed time and
optional reversed boolean.
- Parameter at elapsedTime: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation
or not.
*/
func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval
/**
Applies the given state to the given view.
- Parameter state: A MotionTargetState.
- Parameter to view: A UIView.
*/
func apply(state: MotionTargetState, to view: UIView)
}
......@@ -48,16 +83,32 @@ 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(MotionViewControllerDelegate)
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 motionWillStartTransition()
@objc optional func motionDidEndTransition()
@objc optional func motionDidCancelTransition()
@objc
optional func motionDidEndAnimatingTo(viewController: UIViewController)
@objc optional func motionWillStartAnimatingTo(viewController: UIViewController)
@objc optional func motionDidEndAnimatingTo(viewController: UIViewController)
@objc optional func motionDidCancelAnimatingTo(viewController: UIViewController)
@objc
optional func motionDidCancelAnimatingTo(viewController: UIViewController)
}
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