Working on uiview property animator apis.
diff --git a/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj b/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj
index 5c6c3aa..201b383 100644
--- a/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj
+++ b/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj
@@ -19,6 +19,7 @@
667A3F541DEE273000CB3A99 /* TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667A3F531DEE273000CB3A99 /* TableOfContents.swift */; };
6687264A1EF04B4C00113675 /* MotionAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668726491EF04B4C00113675 /* MotionAnimatorTests.swift */; };
66A6A6681FBA158000DE54CB /* AnimationRemovalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */; };
+ 66A6A66A1FBB364100DE54CB /* TimelineAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A6A6691FBB364100DE54CB /* TimelineAnimatorTests.swift */; };
66BF5A8F1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */; };
66DD4BF51EEF0ECB00207119 /* CalendarCardExpansionExample.m in Sources */ = {isa = PBXBuildFile; fileRef = 66DD4BF41EEF0ECB00207119 /* CalendarCardExpansionExample.m */; };
66DD4BF81EEF1C4B00207119 /* CalendarChipMotionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 66DD4BF71EEF1C4B00207119 /* CalendarChipMotionSpec.m */; };
@@ -65,6 +66,7 @@
667A3F531DEE273000CB3A99 /* TableOfContents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableOfContents.swift; sourceTree = "<group>"; };
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionAnimatorTests.swift; sourceTree = "<group>"; };
66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationRemovalTests.swift; sourceTree = "<group>"; };
+ 66A6A6691FBB364100DE54CB /* TimelineAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineAnimatorTests.swift; sourceTree = "<group>"; };
66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImplicitAnimationTests.swift; sourceTree = "<group>"; };
66DD4BF31EEF0ECB00207119 /* CalendarCardExpansionExample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CalendarCardExpansionExample.h; sourceTree = "<group>"; };
66DD4BF41EEF0ECB00207119 /* CalendarCardExpansionExample.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CalendarCardExpansionExample.m; sourceTree = "<group>"; };
@@ -218,6 +220,7 @@
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */,
66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */,
6625876B1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift */,
+ 66A6A6691FBB364100DE54CB /* TimelineAnimatorTests.swift */,
660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */,
);
path = unit;
@@ -500,6 +503,7 @@
66BF5A8F1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift in Sources */,
66A6A6681FBA158000DE54CB /* AnimationRemovalTests.swift in Sources */,
6687264A1EF04B4C00113675 /* MotionAnimatorTests.swift in Sources */,
+ 66A6A66A1FBB364100DE54CB /* TimelineAnimatorTests.swift in Sources */,
66FD99FA1EE9FBBE00C53A82 /* MotionAnimatorTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/src/MDMTimelineAnimator.h b/src/MDMTimelineAnimator.h
index 4a97bd5..433fe99 100644
--- a/src/MDMTimelineAnimator.h
+++ b/src/MDMTimelineAnimator.h
@@ -22,6 +22,18 @@
#import "MDMAnimatableKeyPaths.h"
#import "MDMCoreAnimationTraceable.h"
+typedef NS_ENUM(NSInteger, MDMTimelineAnimatorState) {
+ MDMTimelineAnimatorStateInactive, // The animation is not executing.
+ MDMTimelineAnimatorStateActive, // The animation is executing.
+ MDMTimelineAnimatorStateStopped, // The animation has been stopped and has not transitioned to inactive.
+};
+
+typedef NS_ENUM(NSInteger, MDMTimelineAnimatorPosition) {
+ MDMTimelineAnimatorPositionPositionEnd,
+ MDMTimelineAnimatorPositionPositionStart,
+ MDMTimelineAnimatorPositionPositionCurrent,
+};
+
/**
An animator adds Core Animation animations to a layer based on a provided motion timing.
*/
@@ -29,73 +41,138 @@
@interface MDMTimelineAnimator : NSObject <MDMCoreAnimationTraceable>
/**
- The scaling factor to apply to all time-related values.
-
- For example, a timeScaleFactor of 2 will double the length of all animations.
-
- 1.0 by default.
+ Initializes an animator with the given duration. The animator object returned by this method begins
+ in the UIViewAnimatingStateInactive state. You must explicitly start the animations by calling the
+ startAnimation method.
*/
-@property(nonatomic, assign) CGFloat timeScaleFactor;
+- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration
+ NS_DESIGNATED_INITIALIZER;
/**
- If enabled, all animations will be added with their values reversed.
-
- Disabled by default.
+ Adds the given animations to the animator's queue of animations to be applied when the animator
+ transitions to the MDMTimelineAnimatorStateActive state.
*/
-@property(nonatomic, assign) BOOL shouldReverseValues;
+- (void)animateWithTiming:(MDMMotionTiming)timing animations:(nonnull void(^)(void))animations;
/**
- Adds a single animation to the layer with the given timing structure.
-
- @param timing The timing to be used for the animation.
- @param layer The layer to be animated.
- @param values The values to be used in the animation. Must contain exactly two values. Supported
- UIKit types will be coerced to their Core Animation equivalent. Supported UIKit values include
- UIColor and UIBezierPath.
- @param keyPath The key path of the property to be animated.
+ The total duration (in seconds) of the overall animation.
*/
-- (void)animateWithTiming:(MDMMotionTiming)timing
- toLayer:(nonnull CALayer *)layer
- withValues:(nonnull NSArray *)values
- keyPath:(nonnull MDMAnimatableKeyPath)keyPath;
+@property(nonatomic, readonly) NSTimeInterval duration;
+
+#pragma mark - UIViewAnimating pseudo-conformance
/**
- Adds a single animation to the layer with the given timing structure.
+ The current state of the animation.
- @param timing The timing to be used for the animation.
- @param layer The layer to be animated.
- @param values The values to be used in the animation. Must contain exactly two values. Supported
- UIKit types will be coerced to their Core Animation equivalent. Supported UIKit values include
- UIColor and UIBezierPath.
- @param keyPath The key path of the property to be animated.
- @param completion The completion handler will be executed once this animation has come to rest.
+ This property reflects the current state of the animation. An animator object starts in the
+ MDMTimelineAnimatorStateInactive state. Calling the startAnimation or pauseAnimation method changes
+ the state to MDMTimelineAnimatorStateActive. Changing the fractionComplete property also moves the
+ animator to the active state. The animator remains in the active state until its animations finish,
+ at which point it moves back to the inactive state.
+
+ Calling the stopAnimation: method changes the state of the animator to
+ MDMTimelineAnimatorStateStopped. When in this state, the animations are stopped and cannot be
+ restarted until you call the finishAnimationAtPosition: method, which returns the animator to the
+ inactive state.
*/
-- (void)animateWithTiming:(MDMMotionTiming)timing
- toLayer:(nonnull CALayer *)layer
- withValues:(nonnull NSArray *)values
- keyPath:(nonnull MDMAnimatableKeyPath)keyPath
- completion:(nullable void(^)(void))completion;
-
-#pragma mark - UIViewAnimating
+@property(nonatomic, readonly) MDMTimelineAnimatorState state;
/**
- Reversed indicates that the animation will animate in the reversed direction.
+ A Boolean value indicating whether the animation is currently running.
+
+ This property reflects whether the animation is running either in the forward or reverse direction.
+ The value of this property is YES only after a call to the startAnimation method. The value is NO
+ when the animator is paused or stopped.
+ */
+@property(nonatomic, readonly, getter=isRunning) BOOL running;
+
+/**
+ A Boolean value indicating whether the animation is running in the reverse direction.
+
+ When the value of this property is YES, animations run in the reverse direction—that is, view
+ properties animate back to their original values. When the value is NO, view properties animate to
+ their intended final values.
+
+ When implementing this property, changes should cause the animation to reverse direction. If you
+ allow changes while the animation is running, it is best to pause the animation briefly and then
+ start it again in the opposite direction. Once the animation transitions to the
+ MDMTimelineAnimatorStateStopped state, you can ignore changes to this property.
*/
@property(nonatomic, getter=isReversed) BOOL reversed;
/**
- A value between 0 and 1 indicating overall progress of the animation.
+ The completion percentage of the animation.
+
+ The value of this property is 0.0 at the beginning of the animation and 1.0 at the end of the
+ animation. Intermediate values represent progress in the execution of the animation. For example,
+ the value 0.5 indicates that the animation is exactly half way complete.
+
+ Assigning a new value to this property causes the animator to update the animation progress to the
+ value you specify. You can use this capability to create interactive animations. For example, you
+ might use a pan gesture recognizer to update the value based on the completion progress of that
+ gesture. You can update the value of this property only while the animator is paused. Changing the
+ value of this property on an inactive animator moves it to the active state.
*/
@property(nonatomic) CGFloat fractionComplete;
/**
- Starts the animation if the animation was paused.
+ Starts the animation from its current position.
+
+ Call this method to start the animations or to resume the animation after they were paused. This
+ method sets the state of the animator to MDMTimelineAnimatorStateActive, if it is not already
+ there. It is a programmer error to call this method while the state of the animator is set to
+ UIViewAnimatingStateStopped.
*/
- (void)startAnimation;
/**
- Pauses the animation if the animation was started.
+ Starts the animation after the specified delay.
+
+ Call this method to start the animations or to resume a set of paused animations after the
+ specified time delay. This method sets the state of the animator to MDMTimelineAnimatorStateActive,
+ if it is not already there. It is a programmer error to call this method while the state of the
+ animator is set to MDMTimelineAnimatorStateStopped.
+ */
+- (void)startAnimationAfterDelay:(NSTimeInterval)delay;
+
+/**
+ Pauses a running animation at its current position.
+
+ This method pauses running animations at their current values. Calling this method on an inactive
+ animator moves its state to MDMTimelineAnimatorStateActive and puts its animations in a paused
+ state right away. To resume the animations, call the startAnimation method. If the animation is
+ already paused, this method should do nothing. It is a programmer error to call this method while
+ the state of the animator is set to UIViewAnimatingStateStopped.
*/
- (void)pauseAnimation;
+/**
+ Stops the animations at their current positions.
+
+ Call this method when you want to end the animations at their current position. This method removes
+ all of the associated animations from the execution stack and sets the values of any animatable
+ properties to their current values. This method also updates the state of the animator object based
+ on the value of the withoutFinishing parameter.
+
+ If you specify NO for the withoutFinishing parameter, you can subsequently call the
+ finishAnimationAtPosition: method to perform the animator’s final actions. For example, a
+ UIViewPropertyAnimator object executes its completion blocks when you call this method. You do not
+ have to call the finishAnimationAtPosition: method right away, or at all, and you can perform other
+ animations before calling that method.
+ */
+- (void)stopAnimation:(BOOL)withoutFinishing;
+
+/**
+ Finishes the animations and returns the animator to the inactive state.
+
+ After putting the animator object into the MDMTimelineAnimatorStateStopped state, call this method
+ to perform any final cleanup tasks. It is a programmer error to call this method at any time except
+ after a call to the stopAnimation: method where you pass NO for the withoutFinishing parameter.
+ Calling this method is not required, but is recommended in cases where you want to ensure that
+ completion blocks or other final tasks are performed.
+ */
+- (void)finishAnimationAtPosition:(MDMTimelineAnimatorPosition)finalPosition;
+
+- (nonnull instancetype)init NS_UNAVAILABLE;
+
@end
diff --git a/src/MDMTimelineAnimator.m b/src/MDMTimelineAnimator.m
index 354bb88..963f49b 100644
--- a/src/MDMTimelineAnimator.m
+++ b/src/MDMTimelineAnimator.m
@@ -16,102 +16,46 @@
#import "MDMTimelineAnimator.h"
-#import "private/CABasicAnimation+MotionAnimator.h"
-#import "private/MDMAnimationRegistrar.h"
-#import "private/MDMDragCoefficient.h"
-#import "private/MDMUIKitValueCoercion.h"
+#import "MDMMotionAnimator.h"
#import <UIKit/UIKit.h>
@implementation MDMTimelineAnimator {
NSMutableArray *_tracers;
- MDMAnimationRegistrar *_registrar;
- BOOL _isPaused;
+ MDMMotionAnimator *_motionAnimator;
}
-- (instancetype)init {
+@synthesize running = _running;
+
+- (nonnull instancetype)initWithDuration:(NSTimeInterval)duration {
self = [super init];
if (self) {
- _timeScaleFactor = 1;
- _registrar = [[MDMAnimationRegistrar alloc] init];
+ _duration = duration;
+ _motionAnimator = [[MDMMotionAnimator alloc] init];
+ _motionAnimator.additive = NO;
}
return self;
}
-- (void)animateWithTiming:(MDMMotionTiming)timing
- toLayer:(CALayer *)layer
- withValues:(NSArray *)values
- keyPath:(MDMAnimatableKeyPath)keyPath {
- [self animateWithTiming:timing toLayer:layer withValues:values keyPath:keyPath completion:nil];
-}
-
-- (void)animateWithTiming:(MDMMotionTiming)timing
- toLayer:(CALayer *)layer
- withValues:(NSArray *)values
- keyPath:(MDMAnimatableKeyPath)keyPath
- completion:(void(^)(void))completion {
- NSAssert([values count] == 2, @"The values array must contain exactly two values.");
-
- if (_shouldReverseValues) {
- values = [[values reverseObjectEnumerator] allObjects];
- }
- values = MDMCoerceUIKitValuesToCoreAnimationValues(values);
-
- if (timing.duration == 0 || timing.curve.type == MDMMotionCurveTypeInstant) {
- [layer setValue:[values lastObject] forKeyPath:keyPath];
- if (completion) {
- completion();
- }
- return;
- }
-
- CGFloat timeScaleFactor = MDMSimulatorAnimationDragCoefficient() * _timeScaleFactor;
- CABasicAnimation *animation = MDMAnimationFromTiming(timing, timeScaleFactor);
-
- if (animation) {
- animation.keyPath = keyPath;
-
- id initialValue = [values firstObject];
-
- animation.fromValue = initialValue;
- animation.toValue = [values lastObject];
-
- if (![animation.fromValue isEqual:animation.toValue]) {
- animation.beginTime = ([layer convertTime:CACurrentMediaTime() fromLayer:nil]
- + timing.delay * timeScaleFactor);
- if (timing.delay != 0) {
- animation.fillMode = kCAFillModeBackwards;
- }
- animation.speed = _isPaused ? 0 : 1;
-
- NSString *key = animation.keyPath;
- [_registrar addAnimation:animation toLayer:layer forKey:key completion:completion];
-
- [layer addAnimation:animation forKey:animation.keyPath];
-
- for (void (^tracer)(CALayer *, CAAnimation *) in _tracers) {
- tracer(layer, animation);
- }
- }
- }
-
- [layer setValue:[values lastObject] forKeyPath:keyPath];
-}
-
- (void)addCoreAnimationTracer:(void (^)(CALayer *, CAAnimation *))tracer {
- if (!_tracers) {
- _tracers = [NSMutableArray array];
- }
- [_tracers addObject:[tracer copy]];
+ [_motionAnimator addCoreAnimationTracer:tracer];
}
+- (void)animateWithTiming:(MDMMotionTiming)timing animations:(void (^)(void))animations {
+
+}
+
+#pragma mark - UIViewAnimating
+
- (void)pauseAnimation {
if (_isPaused) {
return;
}
_isPaused = YES;
- [_registrar pauseAllAnimations];
+ self.state =
+
+// [_registrar pauseAllAnimations];
}
- (void)setFractionComplete:(CGFloat)fractionComplete {
@@ -121,14 +65,28 @@
return;
}
- [_registrar setFractionComplete:fractionComplete];
+// [_registrar setFractionComplete:fractionComplete];
}
- (void)startAnimation {
if (!_isPaused) {
return;
}
- [_registrar startAllAnimationsReversed:self.isReversed];
+ _isPaused = NO;
+
+// [_registrar startAllAnimationsReversed:self.isReversed];
+}
+
+- (void)finishAnimationAtPosition:(UIViewAnimatingPosition)finalPosition NS_AVAILABLE_IOS(10_0) {
+
+}
+
+- (void)startAnimationAfterDelay:(NSTimeInterval)delay {
+
+}
+
+- (void)stopAnimation:(BOOL)withoutFinishing {
+
}
@end
diff --git a/tests/unit/TimelineAnimatorTests.swift b/tests/unit/TimelineAnimatorTests.swift
new file mode 100644
index 0000000..6390615
--- /dev/null
+++ b/tests/unit/TimelineAnimatorTests.swift
@@ -0,0 +1,61 @@
+/*
+ Copyright 2017-present The Material Motion Authors. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+import XCTest
+#if IS_BAZEL_BUILD
+import _MotionAnimator
+#else
+import MotionAnimator
+#endif
+
+class TimelineAnimatorTests: XCTestCase {
+ var animator: TimelineAnimator!
+ var timing: MotionTiming!
+ var view: UIView!
+
+ var originalImplementation: IMP?
+ override func setUp() {
+ super.setUp()
+
+ animator = TimelineAnimator()
+ timing = MotionTiming(delay: 0,
+ duration: 1,
+ curve: MotionCurveMakeBezier(p1x: 0, p1y: 0, p2x: 0, p2y: 0),
+ repetition: .init(type: .none, amount: 0, autoreverses: false))
+
+ let window = UIWindow()
+ window.makeKeyAndVisible()
+ view = UIView() // Need to animate a view's layer to get implicit animations.
+ window.addSubview(view)
+ }
+
+ override func tearDown() {
+ animator = nil
+ timing = nil
+ view = nil
+
+ super.tearDown()
+ }
+
+ func testExample() {
+ animator.animate(with: timing, to: view.layer, withValues: [1, 0], keyPath: .opacity)
+
+ animator.pauseAnimation()
+
+ print(view.layer)
+ }
+}
+