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)
+  }
+}
+