diff --git a/Podfile b/Podfile index 661aa41..7936bb7 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,6 @@ target "MotionAnimatorCatalog" do pod 'CatalogByConvention' pod 'MotionAnimator', :path => './' project 'examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj' - pod 'MotionInterchange', :path => '../motion-interchange-objc/' end target "UnitTests" do diff --git a/Podfile.lock b/Podfile.lock index 21fa606..2bbf1ff 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -2,24 +2,21 @@ PODS: - CatalogByConvention (2.2.0) - MotionAnimator (2.6.0): - MotionInterchange (~> 1.3) - - MotionInterchange (1.4.0) + - MotionInterchange (1.3.0) DEPENDENCIES: - CatalogByConvention - MotionAnimator (from `./`) - - MotionInterchange (from `../motion-interchange-objc/`) EXTERNAL SOURCES: MotionAnimator: :path: ./ - MotionInterchange: - :path: ../motion-interchange-objc/ SPEC CHECKSUMS: CatalogByConvention: 5df5831e48b8083b18570dcb804f20fd1c90694f MotionAnimator: a4b0ba87a674bb3e89e25f0530b7e80a204ac1c1 - MotionInterchange: 35e0fd286ceab53dd4ee03494b3fcafa6a70637a + MotionInterchange: 988fc0011e4b806cc33f2fb4f9566f5eeb4159e8 -PODFILE CHECKSUM: f354f45cd3f9eb0e6ac9a2bfd9429945eae8c0ad +PODFILE CHECKSUM: 3537bf01c11174928ac008c20fec4738722e96f3 COCOAPODS: 1.3.1 diff --git a/examples/CalendarCardExpansionExample.m b/examples/CalendarCardExpansionExample.m index d4826fb..f424c33 100644 --- a/examples/CalendarCardExpansionExample.m +++ b/examples/CalendarCardExpansionExample.m @@ -20,12 +20,12 @@ #import "MotionAnimator.h" -// This example demonstrates how to use a motion traits specification to build a complex +// This example demonstrates how to use a motion timing specification to build a complex // bi-directional animation using the MDMMotionAnimator object. MDMMotionAnimator is designed for // building fine-tuned explicit animations. Unlike UIView's implicit animation API, which can be // used to cause cascading animations on a variety of properties, MDMMotionAnimator will always add // exactly one animation per key path to the layer. This means you don't get as much for "free", but -// you do gain more control over the traits and motion of the animation. +// you do gain more control over the timing and motion of the animation. @implementation CalendarCardExpansionExampleViewController { // In a real-world scenario we'd likely create a separate view to manage all of these subviews so @@ -40,15 +40,15 @@ @implementation CalendarCardExpansionExampleViewController { - (void)didTap { _expanded = !_expanded; - id traits = (_expanded - ? CalendarChipMotionSpec.expansion - : CalendarChipMotionSpec.collapse); + CalendarChipTiming timing = (_expanded + ? CalendarChipMotionSpec.expansion + : CalendarChipMotionSpec.collapse); MDMMotionAnimator *animator = [[MDMMotionAnimator alloc] init]; animator.shouldReverseValues = !_expanded; animator.beginFromCurrentState = YES; - [animator animateWithTraits:traits.navigationBarY animations:^{ + [animator animateWithTiming:timing.navigationBarY animations:^{ [self.navigationController setNavigationBarHidden:_expanded animated:YES]; }]; @@ -56,33 +56,33 @@ - (void)didTap { CGRect headerFrame = [self frameForHeader]; // Animate the chip itself. - [animator animateWithTraits:traits.chipHeight + [animator animateWithTiming:timing.chipHeight toLayer:_chipView.layer withValues:@[ @(chipFrame.size.height), @(headerFrame.size.height) ] keyPath:MDMKeyPathHeight]; - [animator animateWithTraits:traits.chipWidth + [animator animateWithTiming:timing.chipWidth toLayer:_chipView.layer withValues:@[ @(chipFrame.size.width), @(headerFrame.size.width) ] keyPath:MDMKeyPathWidth]; - [animator animateWithTraits:traits.chipWidth + [animator animateWithTiming:timing.chipWidth toLayer:_chipView.layer withValues:@[ @(CGRectGetMidX(chipFrame)), @(CGRectGetMidX(headerFrame)) ] keyPath:MDMKeyPathX]; - [animator animateWithTraits:traits.chipY + [animator animateWithTiming:timing.chipY toLayer:_chipView.layer withValues:@[ @(CGRectGetMidY(chipFrame)), @(CGRectGetMidY(headerFrame)) ] keyPath:MDMKeyPathY]; - [animator animateWithTraits:traits.chipHeight + [animator animateWithTiming:timing.chipHeight toLayer:_chipView.layer withValues:@[ @([self chipCornerRadius]), @0 ] keyPath:MDMKeyPathCornerRadius]; // Cross-fade the chip's contents. - [animator animateWithTraits:traits.chipContentOpacity + [animator animateWithTiming:timing.chipContentOpacity toLayer:_collapsedContent.layer withValues:@[ @1, @0 ] keyPath:MDMKeyPathOpacity]; - [animator animateWithTraits:traits.headerContentOpacity + [animator animateWithTiming:timing.headerContentOpacity toLayer:_expandedContent.layer withValues:@[ @0, @1 ] keyPath:MDMKeyPathOpacity]; @@ -90,7 +90,7 @@ - (void)didTap { // Keeps the expandec content aligned to the bottom of the card by taking into consideration the // extra height. CGFloat excessTopMargin = chipFrame.size.height - headerFrame.size.height; - [animator animateWithTraits:traits.chipHeight + [animator animateWithTiming:timing.chipHeight toLayer:_expandedContent.layer withValues:@[ @(CGRectGetMidY([self expandedContentFrame]) + excessTopMargin), @(CGRectGetMidY([self expandedContentFrame])) ] @@ -99,7 +99,7 @@ - (void)didTap { // Keeps the collapsed content aligned to its position on screen by taking into consideration the // excess left margin. CGFloat excessLeftMargin = chipFrame.origin.x - headerFrame.origin.x; - [animator animateWithTraits:traits.chipWidth + [animator animateWithTiming:timing.chipWidth toLayer:_collapsedContent.layer withValues:@[ @(CGRectGetMidX([self collapsedContentFrame])), @(CGRectGetMidX([self collapsedContentFrame]) + excessLeftMargin) ] @@ -108,11 +108,11 @@ - (void)didTap { // Keeps the shape anchored to the bottom right of the chip. CGRect shapeFrameInChip = [self shapeFrameInRect:chipFrame]; CGRect shapeFrameInHeader = [self shapeFrameInRect:headerFrame]; - [animator animateWithTraits:traits.chipWidth + [animator animateWithTiming:timing.chipWidth toLayer:_shapeView.layer withValues:@[ @(CGRectGetMidX(shapeFrameInChip)), @(CGRectGetMidX(shapeFrameInHeader)) ] keyPath:MDMKeyPathX]; - [animator animateWithTraits:traits.chipHeight + [animator animateWithTiming:timing.chipHeight toLayer:_shapeView.layer withValues:@[ @(CGRectGetMidY(shapeFrameInChip)), @(CGRectGetMidY(shapeFrameInHeader)) ] keyPath:MDMKeyPathY]; diff --git a/examples/CalendarChipMotionSpec.h b/examples/CalendarChipMotionSpec.h index d7989f2..019160b 100644 --- a/examples/CalendarChipMotionSpec.h +++ b/examples/CalendarChipMotionSpec.h @@ -17,26 +17,24 @@ #import #import -@protocol CalendarChipTiming +typedef struct CalendarChipTiming { + MDMMotionTiming chipWidth; + MDMMotionTiming chipHeight; + MDMMotionTiming chipY; -@property(nonatomic, strong, nonnull, readonly) MDMAnimationTraits *chipWidth; -@property(nonatomic, strong, nonnull, readonly) MDMAnimationTraits *chipHeight; -@property(nonatomic, strong, nonnull, readonly) MDMAnimationTraits *chipY; + MDMMotionTiming chipContentOpacity; + MDMMotionTiming headerContentOpacity; -@property(nonatomic, strong, nonnull, readonly) MDMAnimationTraits *chipContentOpacity; -@property(nonatomic, strong, nonnull, readonly) MDMAnimationTraits *headerContentOpacity; - -@property(nonatomic, strong, nonnull, readonly) MDMAnimationTraits *navigationBarY; - -@end + MDMMotionTiming navigationBarY; +} CalendarChipTiming; @interface CalendarChipMotionSpec: NSObject -@property(nonatomic, class, strong, nonnull, readonly) id expansion; -@property(nonatomic, class, strong, nonnull, readonly) id collapse; +@property(nonatomic, class, readonly) CalendarChipTiming expansion; +@property(nonatomic, class, readonly) CalendarChipTiming collapse; // This object is not meant to be instantiated. -- (nonnull instancetype)init NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; @end diff --git a/examples/CalendarChipMotionSpec.m b/examples/CalendarChipMotionSpec.m index 51bffc3..b0ef2b8 100644 --- a/examples/CalendarChipMotionSpec.m +++ b/examples/CalendarChipMotionSpec.m @@ -16,84 +16,58 @@ #import "CalendarChipMotionSpec.h" -static id StandardTimingCurve(void) { - return [CAMediaTimingFunction functionWithControlPoints:0.4f :0.0f :0.2f :1.0f]; -} - -static id LinearTimingCurve(void) { - return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; -} - -@interface CalendarChipExpansionTiming: NSObject -@end - -@implementation CalendarChipExpansionTiming - -- (MDMAnimationTraits *)chipWidth { - return [[MDMAnimationTraits alloc] initWithDelay:0.000 duration:0.285 timingCurve:StandardTimingCurve()]; -} - -- (MDMAnimationTraits *)chipHeight { - return [[MDMAnimationTraits alloc] initWithDelay:0.015 duration:0.360 timingCurve:StandardTimingCurve()]; -} - -- (MDMAnimationTraits *)chipY { - return [[MDMAnimationTraits alloc] initWithDelay:0.015 duration:0.360 timingCurve:StandardTimingCurve()]; -} - -- (MDMAnimationTraits *)chipContentOpacity { - return [[MDMAnimationTraits alloc] initWithDelay:0.000 duration:0.075 timingCurve:LinearTimingCurve()]; -} - -- (MDMAnimationTraits *)headerContentOpacity { - return [[MDMAnimationTraits alloc] initWithDelay:0.075 duration:0.150 timingCurve:LinearTimingCurve()]; -} - -- (MDMAnimationTraits *)navigationBarY { - return [[MDMAnimationTraits alloc] initWithDelay:0.015 duration:0.360 timingCurve:StandardTimingCurve()]; -} - -@end - -@interface CalendarChipCollapseTiming: NSObject -@end - -@implementation CalendarChipCollapseTiming - -- (MDMAnimationTraits *)chipWidth { - return [[MDMAnimationTraits alloc] initWithDelay:0.045 duration:0.330 timingCurve:StandardTimingCurve()]; -} - -- (MDMAnimationTraits *)chipHeight { - return [[MDMAnimationTraits alloc] initWithDelay:0.000 duration:0.330 timingCurve:StandardTimingCurve()]; -} - -- (MDMAnimationTraits *)chipY { - return [[MDMAnimationTraits alloc] initWithDelay:0.015 duration:0.330 timingCurve:StandardTimingCurve()]; -} - -- (MDMAnimationTraits *)chipContentOpacity { - return [[MDMAnimationTraits alloc] initWithDelay:0.150 duration:0.150 timingCurve:LinearTimingCurve()]; -} - -- (MDMAnimationTraits *)headerContentOpacity { - return [[MDMAnimationTraits alloc] initWithDelay:0.000 duration:0.075 timingCurve:LinearTimingCurve()]; -} - -- (MDMAnimationTraits *)navigationBarY { - return [[MDMAnimationTraits alloc] initWithDelay:0.045 duration:0.150 timingCurve:StandardTimingCurve()]; -} - -@end - @implementation CalendarChipMotionSpec -+ (id)expansion { - return [[CalendarChipExpansionTiming alloc] init]; -} - -+ (id)collapse { - return [[CalendarChipCollapseTiming alloc] init]; ++ (MDMMotionCurve)eightyForty { + return MDMMotionCurveMakeBezier(0.4f, 0.0f, 0.2f, 1.0f); +} + ++ (CalendarChipTiming)expansion { + MDMMotionCurve eightyForty = [self eightyForty]; + return (CalendarChipTiming){ + .chipWidth = { + .delay = 0.000, .duration = 0.285, .curve = eightyForty, + }, + .chipHeight = { + .delay = 0.015, .duration = 0.360, .curve = eightyForty, + }, + .chipY = { + .delay = 0.015, .duration = 0.360, .curve = eightyForty, + }, + .chipContentOpacity = { + .delay = 0.000, .duration = 0.075, .curve = MDMLinearMotionCurve, + }, + .headerContentOpacity = { + .delay = 0.075, .duration = 0.150, .curve = MDMLinearMotionCurve, + }, + .navigationBarY = { + .delay = 0.015, .duration = 0.360, .curve = eightyForty, + }, + }; +} + ++ (CalendarChipTiming)collapse { + MDMMotionCurve eightyForty = [self eightyForty]; + return (CalendarChipTiming){ + .chipWidth = { + .delay = 0.045, .duration = 0.330, .curve = eightyForty, + }, + .chipHeight = { + .delay = 0.000, .duration = 0.330, .curve = eightyForty, + }, + .chipY = { + .delay = 0.015, .duration = 0.330, .curve = eightyForty, + }, + .chipContentOpacity = { + .delay = 0.150, .duration = 0.150, .curve = MDMLinearMotionCurve, + }, + .headerContentOpacity = { + .delay = 0.000, .duration = 0.075, .curve = MDMLinearMotionCurve, + }, + .navigationBarY = { + .delay = 0.045, .duration = 0.150, .curve = eightyForty, + } + }; } @end diff --git a/examples/TapToBounceExample.swift b/examples/TapToBounceExample.swift index 0974145..c4db306 100644 --- a/examples/TapToBounceExample.swift +++ b/examples/TapToBounceExample.swift @@ -40,22 +40,21 @@ class TapToBounceExampleViewController: UIViewController { for: [.touchUpInside, .touchUpOutside, .touchDragExit]) } - let traits = MDMAnimationTraits(delay: 0, - duration: 0.5, - timingCurve: MDMSpringTimingCurve(mass: 1, - tension: 100, - friction: 10)) + let timing = MotionTiming(delay: 0, + duration: 0.5, + curve: MotionCurveMakeSpring(mass: 1, tension: 100, friction: 10), + repetition: .init()) func didFocus(_ sender: UIButton) { let animator = MotionAnimator() - animator.animate(with: traits) { + animator.animate(with: timing) { sender.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) } } func didUnfocus(_ sender: UIButton) { let animator = MotionAnimator() - animator.animate(with: traits) { + animator.animate(with: timing) { sender.transform = .identity } } diff --git a/src/MDMMotionAnimator.h b/src/MDMMotionAnimator.h index 151ce7b..1ac537d 100644 --- a/src/MDMMotionAnimator.h +++ b/src/MDMMotionAnimator.h @@ -27,7 +27,7 @@ #import "MDMCoreAnimationTraceable.h" /** - An animator adds Core Animation animations to a layer based on a provided motion traits. + An animator adds Core Animation animations to a layer based on a provided motion timing. */ NS_SWIFT_NAME(MotionAnimator) @interface MDMMotionAnimator : NSObject @@ -44,7 +44,7 @@ NS_SWIFT_NAME(MotionAnimator) /** If enabled, explicitly-provided values will be reversed before animating. - This property does not affect the animateWithTraits:animations: family of methods. + This property does not affect the animateWithTiming:animations: family of methods. Disabled by default. */ @@ -70,32 +70,32 @@ NS_SWIFT_NAME(MotionAnimator) @property(nonatomic, assign) BOOL additive; /** - Adds a single animation to the layer with the given traits structure. + Adds a single animation to the layer with the given timing structure. If `additive` is disabled, the animation will be added to the layer with the keyPath as its key. In this case, multiple invocations of this function on the same key path will remove the animations added from prior invocations. - @param traits The traits to be used for 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. */ -- (void)animateWithTraits:(nonnull MDMAnimationTraits *)traits +- (void)animateWithTiming:(MDMMotionTiming)timing toLayer:(nonnull CALayer *)layer withValues:(nonnull NSArray *)values keyPath:(nonnull MDMAnimatableKeyPath)keyPath; /** - Adds a single animation to the layer with the given traits structure. + Adds a single animation to the layer with the given timing structure. If `additive` is disabled, the animation will be added to the layer with the keyPath as its key. In this case, multiple invocations of this function on the same key path will remove the animations added from prior invocations. - @param traits The traits to be used for 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 @@ -105,36 +105,35 @@ NS_SWIFT_NAME(MotionAnimator) animation hierarchy. If the duration of the animation is 0, this block is executed immediately. The block is escaping and will be released once the animations have completed. */ -- (void)animateWithTraits:(nonnull MDMAnimationTraits *)traits +- (void)animateWithTiming:(MDMMotionTiming)timing toLayer:(nonnull CALayer *)layer withValues:(nonnull NSArray *)values keyPath:(nonnull MDMAnimatableKeyPath)keyPath completion:(nullable void(^)(void))completion; /** - Performs `animations` using the traits provided. + Performs `animations` using the timing provided. - @param traits The traits to be used for the animation. + @param timing The timing to be used for the animation. @param animations The block to be executed. Any animatable properties changed within this block - will result in animations being added to the view's layer with the provided traits. The block is + will result in animations being added to the view's layer with the provided timing. The block is non-escaping. */ -- (void)animateWithTraits:(nonnull MDMAnimationTraits *)traits - animations:(nonnull void(^)(void))animations; +- (void)animateWithTiming:(MDMMotionTiming)timing animations:(nonnull void(^)(void))animations; /** - Performs `animations` using the traits provided and executes the completion handler once all added + Performs `animations` using the timing provided and executes the completion handler once all added animations have completed. - @param traits The traits to be used for the animation. + @param timing The timing to be used for the animation. @param animations The block to be executed. Any animatable properties changed within this block - will result in animations being added to the view's layer with the provided traits. The block is + will result in animations being added to the view's layer with the provided timing. The block is non-escaping. @param completion A block object to be executed once the animation sequence ends or it has been removed from the animation hierarchy. If the duration of the animation is 0, this block is executed immediately. The block is escaping and will be released once the animation sequence has completed. */ -- (void)animateWithTraits:(nonnull MDMAnimationTraits *)traits +- (void)animateWithTiming:(MDMMotionTiming)timing animations:(nonnull void (^)(void))animations completion:(nullable void(^)(void))completion; diff --git a/src/MDMMotionAnimator.m b/src/MDMMotionAnimator.m index a2709d7..f4a4c0f 100644 --- a/src/MDMMotionAnimator.m +++ b/src/MDMMotionAnimator.m @@ -40,14 +40,14 @@ - (instancetype)init { return self; } -- (void)animateWithTraits:(MDMAnimationTraits *)traits +- (void)animateWithTiming:(MDMMotionTiming)timing toLayer:(CALayer *)layer withValues:(NSArray *)values keyPath:(MDMAnimatableKeyPath)keyPath { - [self animateWithTraits:traits toLayer:layer withValues:values keyPath:keyPath completion:nil]; + [self animateWithTiming:timing toLayer:layer withValues:values keyPath:keyPath completion:nil]; } -- (void)animateWithTraits:(MDMAnimationTraits *)traits +- (void)animateWithTiming:(MDMMotionTiming)timing toLayer:(CALayer *)layer withValues:(NSArray *)values keyPath:(MDMAnimatableKeyPath)keyPath @@ -80,7 +80,7 @@ - (void)animateWithTraits:(MDMAnimationTraits *)traits return; } - CABasicAnimation *animation = MDMAnimationFromTiming(traits, timeScaleFactor); + CABasicAnimation *animation = MDMAnimationFromTiming(timing, timeScaleFactor); if (animation == nil) { exitEarly(); @@ -92,7 +92,7 @@ - (void)animateWithTraits:(MDMAnimationTraits *)traits [self addAnimation:animation toLayer:layer withKeyPath:keyPath - traits:traits + timing:timing timeScaleFactor:timeScaleFactor destination:[values lastObject] initialValue:^(BOOL wantsPresentationValue) { @@ -115,11 +115,11 @@ - (void)animateWithTraits:(MDMAnimationTraits *)traits } } -- (void)animateWithTraits:(MDMAnimationTraits *)traits animations:(void (^)(void))animations { - [self animateWithTraits:traits animations:animations completion:nil]; +- (void)animateWithTiming:(MDMMotionTiming)timing animations:(void (^)(void))animations { + [self animateWithTiming:timing animations:animations completion:nil]; } -- (void)animateWithTraits:(MDMAnimationTraits *)traits +- (void)animateWithTiming:(MDMMotionTiming)timing animations:(void (^)(void))animations completion:(void(^)(void))completion { NSArray *actions = MDMAnimateImplicitly(animations); @@ -142,7 +142,7 @@ - (void)animateWithTraits:(MDMAnimationTraits *)traits } // We'll reuse this animation template for each action. - CABasicAnimation *animationTemplate = MDMAnimationFromTiming(traits, timeScaleFactor); + CABasicAnimation *animationTemplate = MDMAnimationFromTiming(timing, timeScaleFactor); if (animationTemplate == nil) { exitEarly(); return; @@ -157,7 +157,7 @@ - (void)animateWithTraits:(MDMAnimationTraits *)traits [self addAnimation:animation toLayer:action.layer withKeyPath:action.keyPath - traits:traits + timing:timing timeScaleFactor:timeScaleFactor destination:[action.layer valueForKeyPath:action.keyPath] initialValue:^(BOOL wantsPresentationValue) { @@ -214,7 +214,7 @@ - (CGFloat)computedTimeScaleFactor { - (void)addAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer withKeyPath:(NSString *)keyPath - traits:(MDMAnimationTraits *)traits + timing:(MDMMotionTiming)timing timeScaleFactor:(CGFloat)timeScaleFactor destination:(id)destination initialValue:(id(^)(BOOL wantsPresentationValue))initialValueBlock @@ -237,11 +237,11 @@ - (void)addAnimation:(CABasicAnimation *)animation NSString *key = animation.additive ? nil : keyPath; - MDMConfigureAnimation(animation, traits); + MDMConfigureAnimation(animation, timing); - if (traits.delay != 0) { + if (timing.delay != 0) { animation.beginTime = ([layer convertTime:CACurrentMediaTime() fromLayer:nil] - + traits.delay * timeScaleFactor); + + timing.delay * timeScaleFactor); animation.fillMode = kCAFillModeBackwards; } diff --git a/src/private/CABasicAnimation+MotionAnimator.h b/src/private/CABasicAnimation+MotionAnimator.h index 4e4fd9a..b3e0ce6 100644 --- a/src/private/CABasicAnimation+MotionAnimator.h +++ b/src/private/CABasicAnimation+MotionAnimator.h @@ -20,9 +20,9 @@ #import #import -// Returns a basic animation configured with the provided traits and scale factor. +// Returns a basic animation configured with the provided timing and scale factor. FOUNDATION_EXPORT -CABasicAnimation *MDMAnimationFromTiming(MDMAnimationTraits * traits, CGFloat timeScaleFactor); +CABasicAnimation *MDMAnimationFromTiming(MDMMotionTiming timing, CGFloat timeScaleFactor); // Returns a Boolean indicating whether or not an animation with the given key path and toValue // can be animated additively. @@ -33,4 +33,4 @@ FOUNDATION_EXPORT BOOL MDMCanAnimationBeAdditive(NSString *keyPath, id toValue); // // Not all animation value types support being additive. If an animation's value type was not // supported, the animation's values will not be modified. -FOUNDATION_EXPORT void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * traits); +FOUNDATION_EXPORT void MDMConfigureAnimation(CABasicAnimation *animation, MDMMotionTiming timing); diff --git a/src/private/CABasicAnimation+MotionAnimator.m b/src/private/CABasicAnimation+MotionAnimator.m index 6d78830..e3cf2e7 100644 --- a/src/private/CABasicAnimation+MotionAnimator.m +++ b/src/private/CABasicAnimation+MotionAnimator.m @@ -65,39 +65,44 @@ static BOOL IsAnimationKeyPathAlwaysNonAdditive(NSString *keyPath) { #pragma mark - Public -CABasicAnimation *MDMAnimationFromTiming(MDMAnimationTraits * traits, CGFloat timeScaleFactor) { - if (traits.timingCurve == nil) { - return nil; - } +CABasicAnimation *MDMAnimationFromTiming(MDMMotionTiming timing, CGFloat timeScaleFactor) { + CABasicAnimation *animation; + switch (timing.curve.type) { + case MDMMotionCurveTypeInstant: + animation = nil; + break; - if ([traits.timingCurve isKindOfClass:[CAMediaTimingFunction class]]) { - CFTimeInterval duration = traits.duration * timeScaleFactor; - if (duration == 0) { - return nil; - } - CABasicAnimation *animation = [CABasicAnimation animation]; - animation.timingFunction = (CAMediaTimingFunction *)traits.timingCurve; - animation.duration = duration; - return animation; - } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + case MDMMotionCurveTypeDefault: +#pragma clang diagnostic pop + case MDMMotionCurveTypeBezier: + animation = [CABasicAnimation animation]; + animation.timingFunction = MDMTimingFunctionWithControlPoints(timing.curve.data); + animation.duration = timing.duration * timeScaleFactor; - if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]) { - MDMSpringTimingCurve *springTiming = (MDMSpringTimingCurve *)traits.timingCurve; + if (animation.duration == 0) { + return nil; + } + break; + case MDMMotionCurveTypeSpring: { #pragma clang diagnostic push - // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're - // linking against the public API on iOS 9+. + // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're + // linking against the public API on iOS 9+. #pragma clang diagnostic ignored "-Wpartial-availability" - CASpringAnimation *animation = [CASpringAnimation animation]; + CASpringAnimation *spring = [CASpringAnimation animation]; #pragma clang diagnostic pop - animation.mass = springTiming.mass; - animation.stiffness = springTiming.tension; - animation.damping = springTiming.friction; - animation.duration = traits.duration; - return animation; - } + spring.mass = timing.curve.data[MDMSpringMotionCurveDataIndexMass]; + spring.stiffness = timing.curve.data[MDMSpringMotionCurveDataIndexTension]; + spring.damping = timing.curve.data[MDMSpringMotionCurveDataIndexFriction]; + spring.duration = timing.duration; - return nil; + animation = spring; + break; + } + } + return animation; } BOOL MDMCanAnimationBeAdditive(NSString *keyPath, id toValue) { @@ -110,18 +115,8 @@ BOOL MDMCanAnimationBeAdditive(NSString *keyPath, id toValue) { || IsCATransform3DType(toValue)); } -void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * traits) { -#pragma clang diagnostic push - // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're - // linking against the public API on iOS 9+. -#pragma clang diagnostic ignored "-Wpartial-availability" - BOOL isSpringAnimation = ([animation isKindOfClass:[CASpringAnimation class]] - && [traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]); - MDMSpringTimingCurve *springTimingCurve = (MDMSpringTimingCurve *)traits.timingCurve; - CASpringAnimation *springAnimation = (CASpringAnimation *)animation; -#pragma clang diagnostic pop - - if (!animation.additive && !isSpringAnimation) { +void MDMConfigureAnimation(CABasicAnimation *animation, MDMMotionTiming timing) { + if (!animation.additive && timing.curve.type != MDMMotionCurveTypeSpring) { return; // Nothing to do here. } @@ -163,10 +158,17 @@ void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * tra animation.toValue = @0; } - if (isSpringAnimation) { - CGFloat absoluteInitialVelocity = springTimingCurve.initialVelocity; +#pragma clang diagnostic push + // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're + // linking against the public API on iOS 9+. +#pragma clang diagnostic ignored "-Wpartial-availability" + if ([animation isKindOfClass:[CASpringAnimation class]]) { + CASpringAnimation *springAnimation = (CASpringAnimation *)animation; +#pragma clang diagnostic pop + + CGFloat absoluteInitialVelocity = timing.curve.data[MDMSpringMotionCurveDataIndexInitialVelocity]; - // Our traits's initialVelocity is in points per second, but Core Animation expects initial + // Our timing's initialVelocity is in points per second, but Core Animation expects initial // velocity to be in terms of displacement per second. // // From the UIView animateWithDuration header docs: @@ -218,7 +220,13 @@ void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * tra animation.toValue = [NSValue valueWithCGSize:CGSizeZero]; } - if (isSpringAnimation) { +#pragma clang diagnostic push + // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're + // linking against the public API on iOS 9+. +#pragma clang diagnostic ignored "-Wpartial-availability" + if ([animation isKindOfClass:[CASpringAnimation class]]) { + CASpringAnimation *springAnimation = (CASpringAnimation *)animation; +#pragma clang diagnostic pop // Core Animation's velocity system is single dimensional, so we pick the dominant direction // of movement and normalize accordingly. CGFloat biggestDelta; @@ -228,7 +236,8 @@ void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * tra biggestDelta = additiveDisplacement.height; } CGFloat displacement = -biggestDelta; - CGFloat absoluteInitialVelocity = springTimingCurve.initialVelocity; + CGFloat absoluteInitialVelocity = + timing.curve.data[MDMSpringMotionCurveDataIndexInitialVelocity]; if (fabs(displacement) > 0.00001) { springAnimation.initialVelocity = absoluteInitialVelocity / displacement; } @@ -244,7 +253,13 @@ void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * tra animation.toValue = [NSValue valueWithCGPoint:CGPointZero]; } - if (isSpringAnimation) { +#pragma clang diagnostic push + // CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're + // linking against the public API on iOS 9+. +#pragma clang diagnostic ignored "-Wpartial-availability" + if ([animation isKindOfClass:[CASpringAnimation class]]) { + CASpringAnimation *springAnimation = (CASpringAnimation *)animation; +#pragma clang diagnostic pop // Core Animation's velocity system is single dimensional, so we pick the dominant direction // of movement and normalize accordingly. CGFloat biggestDelta; @@ -254,7 +269,8 @@ void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * tra biggestDelta = additiveDisplacement.y; } CGFloat displacement = -biggestDelta; - CGFloat absoluteInitialVelocity = springTimingCurve.initialVelocity; + CGFloat absoluteInitialVelocity = + timing.curve.data[MDMSpringMotionCurveDataIndexInitialVelocity]; if (fabs(displacement) > 0.00001) { springAnimation.initialVelocity = absoluteInitialVelocity / displacement; } @@ -271,7 +287,13 @@ void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * tra } } - if (isSpringAnimation) { + // Update the animation's duration to match the proposed settling duration. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + if ([animation isKindOfClass:[CASpringAnimation class]]) { + CASpringAnimation *springAnimation = (CASpringAnimation *)animation; +#pragma clang diagnostic pop + // This API is only available on iOS 9+ if ([springAnimation respondsToSelector:@selector(settlingDuration)]) { animation.duration = springAnimation.settlingDuration; diff --git a/src/private/CAMediaTimingFunction+MotionAnimator.h b/src/private/CAMediaTimingFunction+MotionAnimator.h index e1e1803..8790721 100644 --- a/src/private/CAMediaTimingFunction+MotionAnimator.h +++ b/src/private/CAMediaTimingFunction+MotionAnimator.h @@ -18,6 +18,6 @@ #import #import -// Returns a traits function with the given control points. +// Returns a timing function with the given control points. FOUNDATION_EXPORT CAMediaTimingFunction* MDMTimingFunctionWithControlPoints(CGFloat controlPoints[4]); diff --git a/tests/unit/AdditiveAnimatorTests.swift b/tests/unit/AdditiveAnimatorTests.swift index c5855ba..f4e60ea 100644 --- a/tests/unit/AdditiveAnimatorTests.swift +++ b/tests/unit/AdditiveAnimatorTests.swift @@ -23,7 +23,7 @@ import MotionAnimator class AdditiveAnimationTests: XCTestCase { var animator: MotionAnimator! - var traits: MDMAnimationTraits! + var timing: MotionTiming! var view: UIView! override func setUp() { @@ -33,7 +33,10 @@ class AdditiveAnimationTests: XCTestCase { animator.additive = true - traits = MDMAnimationTraits(duration: 1) + timing = MotionTiming(delay: 0, + duration: 1, + curve: MotionCurveMakeBezier(p1x: 0, p1y: 0, p2x: 1, p2y: 1), + repetition: .init(type: .none, amount: 0, autoreverses: false)) let window = UIWindow() window.makeKeyAndVisible() @@ -46,14 +49,14 @@ class AdditiveAnimationTests: XCTestCase { override func tearDown() { animator = nil - traits = nil + timing = nil view = nil super.tearDown() } func testNumericKeyPathsAnimateAdditively() { - animator.animate(with: traits, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) + animator.animate(with: timing, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) XCTAssertNotNil(view.layer.animationKeys(), "Expected an animation to be added, but none were found.") @@ -72,7 +75,7 @@ class AdditiveAnimationTests: XCTestCase { } func testCGSizeKeyPathsAnimateAdditively() { - animator.animate(with: traits, to: view.layer, + animator.animate(with: timing, to: view.layer, withValues: [CGSize(width: 0, height: 0), CGSize(width: 1, height: 2)], keyPath: .shadowOffset) @@ -93,7 +96,7 @@ class AdditiveAnimationTests: XCTestCase { } func testCGPointKeyPathsAnimateAdditively() { - animator.animate(with: traits, to: view.layer, + animator.animate(with: timing, to: view.layer, withValues: [CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 2)], keyPath: .position) diff --git a/tests/unit/AnimationRemovalTests.swift b/tests/unit/AnimationRemovalTests.swift index 70aa766..197c8d2 100644 --- a/tests/unit/AnimationRemovalTests.swift +++ b/tests/unit/AnimationRemovalTests.swift @@ -24,7 +24,7 @@ import MotionAnimator class AnimationRemovalTests: XCTestCase { var animator: MotionAnimator! - var traits: MDMAnimationTraits! + var timing: MotionTiming! var view: UIView! var originalImplementation: IMP? @@ -32,7 +32,10 @@ class AnimationRemovalTests: XCTestCase { super.setUp() animator = MotionAnimator() - traits = MDMAnimationTraits(duration: 1) + 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() @@ -45,15 +48,15 @@ class AnimationRemovalTests: XCTestCase { override func tearDown() { animator = nil - traits = nil + timing = nil view = nil super.tearDown() } func testAllAdditiveAnimationsGetsRemoved() { - animator.animate(with: traits, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) - animator.animate(with: traits, to: view.layer, withValues: [0, 0.5], keyPath: .cornerRadius) + animator.animate(with: timing, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) + animator.animate(with: timing, to: view.layer, withValues: [0, 0.5], keyPath: .cornerRadius) XCTAssertEqual(view.layer.animationKeys()!.count, 2) @@ -70,8 +73,8 @@ class AnimationRemovalTests: XCTestCase { didComplete = true } - animator.animate(with: traits, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) - animator.animate(with: traits, to: view.layer, withValues: [0, 0.5], keyPath: .cornerRadius) + animator.animate(with: timing, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) + animator.animate(with: timing, to: view.layer, withValues: [0, 0.5], keyPath: .cornerRadius) CATransaction.commit() diff --git a/tests/unit/BeginFromCurrentStateTests.swift b/tests/unit/BeginFromCurrentStateTests.swift index 20655cd..7117089 100644 --- a/tests/unit/BeginFromCurrentStateTests.swift +++ b/tests/unit/BeginFromCurrentStateTests.swift @@ -24,7 +24,7 @@ import MotionAnimator class BeginFromCurrentStateTests: XCTestCase { var animator: MotionAnimator! - var traits: MDMAnimationTraits! + var timing: MotionTiming! var view: UIView! var addedAnimations: [CAAnimation]! @@ -35,7 +35,10 @@ class BeginFromCurrentStateTests: XCTestCase { animator.beginFromCurrentState = true - traits = MDMAnimationTraits(duration: 1) + 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() @@ -53,7 +56,7 @@ class BeginFromCurrentStateTests: XCTestCase { override func tearDown() { animator = nil - traits = nil + timing = nil view = nil addedAnimations = nil @@ -65,7 +68,7 @@ class BeginFromCurrentStateTests: XCTestCase { animator.additive = false - animator.animate(with: traits, to: view.layer, withValues: [0, 0.5], keyPath: .opacity) + animator.animate(with: timing, to: view.layer, withValues: [0, 0.5], keyPath: .opacity) XCTAssertNotNil(view.layer.animationKeys(), "Expected an animation to be added, but none were found.") @@ -100,7 +103,7 @@ class BeginFromCurrentStateTests: XCTestCase { animator.additive = false - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0.5 } @@ -135,7 +138,7 @@ class BeginFromCurrentStateTests: XCTestCase { func testExplicitlyAnimatesFromPresentationValue() { animator.additive = false - animator.animate(with: traits, to: view.layer, withValues: [0, 0.5], keyPath: .opacity) + animator.animate(with: timing, to: view.layer, withValues: [0, 0.5], keyPath: .opacity) RunLoop.main.run(until: .init(timeIntervalSinceNow: 0.01)) XCTAssertNotNil(view.layer.presentation(), "No presentation layer found.") @@ -144,7 +147,7 @@ class BeginFromCurrentStateTests: XCTestCase { } let initialValue = presentation.opacity - animator.animate(with: traits, to: view.layer, withValues: [0, 0.2], keyPath: .opacity) + animator.animate(with: timing, to: view.layer, withValues: [0, 0.2], keyPath: .opacity) XCTAssertNotNil(view.layer.animationKeys(), "Expected an animation to be added, but none were found.") @@ -177,7 +180,7 @@ class BeginFromCurrentStateTests: XCTestCase { func testImplicitlyAnimatesFromPresentationValue() { animator.additive = false - animator.animate(with: traits, to: view.layer, withValues: [0, 0.5], keyPath: .opacity) + animator.animate(with: timing, to: view.layer, withValues: [0, 0.5], keyPath: .opacity) RunLoop.main.run(until: .init(timeIntervalSinceNow: 0.01)) @@ -187,7 +190,7 @@ class BeginFromCurrentStateTests: XCTestCase { } let initialValue = presentation.opacity - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0.2 } @@ -223,7 +226,7 @@ class BeginFromCurrentStateTests: XCTestCase { animator.beginFromCurrentState = true animator.additive = false - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0.5 } @@ -231,7 +234,7 @@ class BeginFromCurrentStateTests: XCTestCase { let initialValue = view.layer.presentation()!.opacity - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 1.0 } diff --git a/tests/unit/HeadlessLayerImplicitAnimationTests.swift b/tests/unit/HeadlessLayerImplicitAnimationTests.swift index bf6862b..7b0e4f6 100644 --- a/tests/unit/HeadlessLayerImplicitAnimationTests.swift +++ b/tests/unit/HeadlessLayerImplicitAnimationTests.swift @@ -97,7 +97,7 @@ class HeadlessLayerImplicitAnimationTests: XCTestCase { // Verifies the somewhat counter-intuitive fact that CATransaction's animation duration always // takes precedence over UIView's animation duration. This means that animating a headless layer - // using UIView animation APIs may not result in the expected traitss. + // using UIView animation APIs may not result in the expected timings. func testCATransactionTimingTakesPrecedenceOverUIViewTimingOutside() { CATransaction.begin() CATransaction.setAnimationDuration(0.2) @@ -146,19 +146,18 @@ class HeadlessLayerImplicitAnimationTests: XCTestCase { func testAnimatorTimingTakesPrecedenceOverCATransactionTiming() { let animator = MotionAnimator() animator.additive = false + let 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 traits = MDMAnimationTraits(duration: 1) - - CATransaction.begin() - CATransaction.setAnimationDuration(0.5) - animator.animate(with: traits) { + animator.animate(with: timing) { self.layer.opacity = 0.5 } - CATransaction.commit() let animation = layer.animation(forKey: "opacity") as! CABasicAnimation XCTAssertEqual(animation.keyPath, "opacity") - XCTAssertEqual(animation.duration, traits.duration) + XCTAssertEqual(animation.duration, timing.duration) } // MARK: Deprecated tests. @@ -205,9 +204,12 @@ class HeadlessLayerImplicitAnimationTests: XCTestCase { let animator = MotionAnimator() animator.additive = false - let traits = MDMAnimationTraits(duration: 1) + let timing = MotionTiming(delay: 0, + duration: 1, + curve: MotionCurveMakeBezier(p1x: 0, p1y: 0, p2x: 0, p2y: 0), + repetition: .init(type: .none, amount: 0, autoreverses: false)) - animator.animate(with: traits) { + animator.animate(with: timing) { self.layer.opacity = 0.5 } diff --git a/tests/unit/ImplicitAnimationTests.swift b/tests/unit/ImplicitAnimationTests.swift index 61cf5ec..80eab59 100644 --- a/tests/unit/ImplicitAnimationTests.swift +++ b/tests/unit/ImplicitAnimationTests.swift @@ -23,7 +23,7 @@ import MotionAnimator class ImplicitAnimationTests: XCTestCase { var animator: MotionAnimator! - var traits: MDMAnimationTraits! + var timing: MotionTiming! var view: UIView! var addedAnimations: [CAAnimation]! @@ -34,7 +34,10 @@ class ImplicitAnimationTests: XCTestCase { animator = MotionAnimator() animator.additive = false - traits = MDMAnimationTraits(duration: 1) + timing = MotionTiming(delay: 0, + duration: 0.7, + curve: MotionCurveMakeBezier(p1x: 0, p1y: 0, p2x: 1, p2y: 1), + repetition: .init(type: .none, amount: 0, autoreverses: false)) let window = UIWindow() window.makeKeyAndVisible() @@ -66,7 +69,7 @@ class ImplicitAnimationTests: XCTestCase { } func testNoActionAddsNoAnimations() { - animator.animate(with: traits) { + animator.animate(with: timing) { // No-op } @@ -74,7 +77,7 @@ class ImplicitAnimationTests: XCTestCase { } func testOneActionAddsOneAnimation() { - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0 } @@ -83,21 +86,18 @@ class ImplicitAnimationTests: XCTestCase { XCTAssertEqual(animation.keyPath, AnimatableKeyPath.opacity.rawValue) XCTAssertEqual(animation.fromValue as! CGFloat, 1) XCTAssertEqual(animation.toValue as! CGFloat, 0) - XCTAssertEqual(animation.duration, traits.duration) - - let timingCurve = traits.timingCurve as! CAMediaTimingFunction - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, animation.timingFunction!.mdm_point1.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, animation.timingFunction!.mdm_point1.y, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, animation.timingFunction!.mdm_point2.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, animation.timingFunction!.mdm_point2.y, - accuracy: 0.001) + XCTAssertEqual(animation.duration, timing.duration) + + let addedCurve = MotionCurve(fromTimingFunction: animation.timingFunction!) + XCTAssertEqual(addedCurve.type, timing.curve.type) + XCTAssertEqual(addedCurve.data.0, timing.curve.data.0) + XCTAssertEqual(addedCurve.data.1, timing.curve.data.1) + XCTAssertEqual(addedCurve.data.2, timing.curve.data.2) + XCTAssertEqual(addedCurve.data.3, timing.curve.data.3) } func testTwoActionsAddsTwoAnimations() { - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0 self.view.center = .init(x: 50, y: 50) } @@ -110,17 +110,14 @@ class ImplicitAnimationTests: XCTestCase { XCTAssertEqual(animation.keyPath, AnimatableKeyPath.opacity.rawValue) XCTAssertEqual(animation.fromValue as! CGFloat, 1) XCTAssertEqual(animation.toValue as! CGFloat, 0) - XCTAssertEqual(animation.duration, traits.duration) - - let timingCurve = traits.timingCurve as! CAMediaTimingFunction - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, animation.timingFunction!.mdm_point1.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, animation.timingFunction!.mdm_point1.y, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, animation.timingFunction!.mdm_point2.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, animation.timingFunction!.mdm_point2.y, - accuracy: 0.001) + XCTAssertEqual(animation.duration, timing.duration) + + let addedCurve = MotionCurve(fromTimingFunction: animation.timingFunction!) + XCTAssertEqual(addedCurve.type, timing.curve.type) + XCTAssertEqual(addedCurve.data.0, timing.curve.data.0) + XCTAssertEqual(addedCurve.data.1, timing.curve.data.1) + XCTAssertEqual(addedCurve.data.2, timing.curve.data.2) + XCTAssertEqual(addedCurve.data.3, timing.curve.data.3) } do { let animation = addedAnimations[1] as! CABasicAnimation @@ -128,22 +125,19 @@ class ImplicitAnimationTests: XCTestCase { XCTAssertEqual(animation.keyPath, AnimatableKeyPath.position.rawValue) XCTAssertEqual(animation.fromValue as! CGPoint, .init(x: 0, y: 0)) XCTAssertEqual(animation.toValue as! CGPoint, .init(x: 50, y: 50)) - XCTAssertEqual(animation.duration, traits.duration) - - let timingCurve = traits.timingCurve as! CAMediaTimingFunction - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, animation.timingFunction!.mdm_point1.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, animation.timingFunction!.mdm_point1.y, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, animation.timingFunction!.mdm_point2.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, animation.timingFunction!.mdm_point2.y, - accuracy: 0.001) + XCTAssertEqual(animation.duration, timing.duration) + + let addedCurve = MotionCurve(fromTimingFunction: animation.timingFunction!) + XCTAssertEqual(addedCurve.type, timing.curve.type) + XCTAssertEqual(addedCurve.data.0, timing.curve.data.0) + XCTAssertEqual(addedCurve.data.1, timing.curve.data.1) + XCTAssertEqual(addedCurve.data.2, timing.curve.data.2) + XCTAssertEqual(addedCurve.data.3, timing.curve.data.3) } } func testFrameActionAddsTwoAnimations() { - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.frame = .init(x: 0, y: 0, width: 100, height: 100) } @@ -156,17 +150,14 @@ class ImplicitAnimationTests: XCTestCase { XCTAssertFalse(animation.isAdditive) XCTAssertEqual(animation.fromValue as! CGPoint, .init(x: 0, y: 0)) XCTAssertEqual(animation.toValue as! CGPoint, .init(x: 50, y: 50)) - XCTAssertEqual(animation.duration, traits.duration) - - let timingCurve = traits.timingCurve as! CAMediaTimingFunction - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, animation.timingFunction!.mdm_point1.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, animation.timingFunction!.mdm_point1.y, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, animation.timingFunction!.mdm_point2.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, animation.timingFunction!.mdm_point2.y, - accuracy: 0.001) + XCTAssertEqual(animation.duration, timing.duration) + + let addedCurve = MotionCurve(fromTimingFunction: animation.timingFunction!) + XCTAssertEqual(addedCurve.type, timing.curve.type) + XCTAssertEqual(addedCurve.data.0, timing.curve.data.0) + XCTAssertEqual(addedCurve.data.1, timing.curve.data.1) + XCTAssertEqual(addedCurve.data.2, timing.curve.data.2) + XCTAssertEqual(addedCurve.data.3, timing.curve.data.3) } do { let animation = addedAnimations @@ -175,17 +166,14 @@ class ImplicitAnimationTests: XCTestCase { XCTAssertFalse(animation.isAdditive) XCTAssertEqual(animation.fromValue as! CGRect, .init(x: 0, y: 0, width: 0, height: 0)) XCTAssertEqual(animation.toValue as! CGRect, .init(x: 0, y: 0, width: 100, height: 100)) - XCTAssertEqual(animation.duration, traits.duration) - - let timingCurve = traits.timingCurve as! CAMediaTimingFunction - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.x, animation.timingFunction!.mdm_point1.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point1.y, animation.timingFunction!.mdm_point1.y, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.x, animation.timingFunction!.mdm_point2.x, - accuracy: 0.001) - XCTAssertEqualWithAccuracy(timingCurve.mdm_point2.y, animation.timingFunction!.mdm_point2.y, - accuracy: 0.001) + XCTAssertEqual(animation.duration, timing.duration) + + let addedCurve = MotionCurve(fromTimingFunction: animation.timingFunction!) + XCTAssertEqual(addedCurve.type, timing.curve.type) + XCTAssertEqual(addedCurve.data.0, timing.curve.data.0) + XCTAssertEqual(addedCurve.data.1, timing.curve.data.1) + XCTAssertEqual(addedCurve.data.2, timing.curve.data.2) + XCTAssertEqual(addedCurve.data.3, timing.curve.data.3) } } @@ -193,7 +181,7 @@ class ImplicitAnimationTests: XCTestCase { CATransaction.begin() CATransaction.setDisableActions(true) - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0 } @@ -223,9 +211,9 @@ class ImplicitAnimationTests: XCTestCase { } func testDurationOfZeroRunsAnimationsBlockButGeneratesNoAnimations() { - let traits = MDMAnimationTraits(duration: 0) + timing.duration = 0 - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0 } @@ -236,7 +224,7 @@ class ImplicitAnimationTests: XCTestCase { func testTimeScaleFactorOfZeroRunsAnimationsBlockButGeneratesNoAnimations() { animator.timeScaleFactor = 0 - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.alpha = 0 } @@ -245,7 +233,7 @@ class ImplicitAnimationTests: XCTestCase { } func testUnsupportedAnimationKeyIsNotAnimated() { - animator.animate(with: traits) { + animator.animate(with: timing) { self.view.layer.sublayers = [] } diff --git a/tests/unit/InitialVelocityTests.swift b/tests/unit/InitialVelocityTests.swift index d7cc74f..06378f9 100644 --- a/tests/unit/InitialVelocityTests.swift +++ b/tests/unit/InitialVelocityTests.swift @@ -135,15 +135,16 @@ class InitialVelocityTests: XCTestCase { } private func animate(from: CGFloat, to: CGFloat, withVelocity velocity: CGFloat) { - let springCurve = MDMSpringTimingCurve(mass: 1, tension: 1, friction: 1, - initialVelocity: velocity) - let traits = MDMAnimationTraits(delay: 0, duration: 0.7, timingCurve: springCurve) - animator.animate(with: traits, to: CALayer(), withValues: [from, to], + let timing = MotionTiming(delay: 0, + duration: 0.7, + curve: .init(type: .spring, data: (1, 1, 1, velocity)), + repetition: .init(type: .none, amount: 0, autoreverses: false)) + animator.animate(with: timing, to: CALayer(), withValues: [from, to], keyPath: .opacity) - animator.animate(with: traits, to: CALayer(), withValues: [CGPoint(x: from, y: from), + animator.animate(with: timing, to: CALayer(), withValues: [CGPoint(x: from, y: from), CGPoint(x: to, y: to)], keyPath: .position) - animator.animate(with: traits, to: CALayer(), withValues: [CGSize(width: from, height: from), + animator.animate(with: timing, to: CALayer(), withValues: [CGSize(width: from, height: from), CGSize(width: to, height: to)], keyPath: .init(rawValue: "bounds.size")) } diff --git a/tests/unit/InstantAnimationTests.swift b/tests/unit/InstantAnimationTests.swift index 281778b..3a40426 100644 --- a/tests/unit/InstantAnimationTests.swift +++ b/tests/unit/InstantAnimationTests.swift @@ -24,6 +24,7 @@ import MotionAnimator class InstantAnimationTests: XCTestCase { var animator: MotionAnimator! + var timing: MotionTiming! var view: UIView! var addedAnimations: [CAAnimation]! @@ -32,6 +33,11 @@ class InstantAnimationTests: XCTestCase { animator = MotionAnimator() + timing = MotionTiming(delay: 0, + duration: 0, + curve: .init(type: .instant, data: (0, 0, 0, 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. @@ -55,42 +61,15 @@ class InstantAnimationTests: XCTestCase { } func testDoesNotGenerateImplicitAnimations() { - let traits = MDMAnimationTraits(duration: 0) - - animator.animate(with: traits, to: view.layer, withValues: [1, 0.5], keyPath: .opacity) + animator.animate(with: timing, to: view.layer, withValues: [1, 0.5], keyPath: .opacity) XCTAssertNil(view.layer.animationKeys()) XCTAssertEqual(addedAnimations.count, 0) } func testDoesNotGenerateImplicitAnimationsInUIViewAnimationBlock() { - let traits = MDMAnimationTraits(duration: 0) - - UIView.animate(withDuration: 0.5) { - self.animator.animate(with: traits, - to: self.view.layer, - withValues: [1, 0.5], - keyPath: .opacity) - } - - XCTAssertNil(view.layer.animationKeys()) - XCTAssertEqual(addedAnimations.count, 0) - } - - func testDoesNotGenerateImplicitAnimationsWithNilCurve() { - let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: nil) - - animator.animate(with: traits, to: view.layer, withValues: [1, 0.5], keyPath: .opacity) - - XCTAssertNil(view.layer.animationKeys()) - XCTAssertEqual(addedAnimations.count, 0) - } - - func testDoesNotGenerateImplicitAnimationsInUIViewAnimationBlockWithNilCurve() { - let traits = MDMAnimationTraits(delay: 0, duration: 0.5, timingCurve: nil) - UIView.animate(withDuration: 0.5) { - self.animator.animate(with: traits, + self.animator.animate(with: self.timing, to: self.view.layer, withValues: [1, 0.5], keyPath: .opacity) diff --git a/tests/unit/MotionAnimatorBehavioralTests.swift b/tests/unit/MotionAnimatorBehavioralTests.swift index fa220f5..5789f0c 100644 --- a/tests/unit/MotionAnimatorBehavioralTests.swift +++ b/tests/unit/MotionAnimatorBehavioralTests.swift @@ -23,7 +23,7 @@ import MotionAnimator class AnimatorBehavioralTests: XCTestCase { var window: UIWindow! - var traits: MDMAnimationTraits! + var timing: MotionTiming! var originalImplementation: IMP? override func setUp() { @@ -32,11 +32,14 @@ class AnimatorBehavioralTests: XCTestCase { window = UIWindow() window.makeKeyAndVisible() - traits = MDMAnimationTraits(duration: 1) + timing = MotionTiming(delay: 0, + duration: 1, + curve: MotionCurveMakeBezier(p1x: 0, p1y: 0, p2x: 0, p2y: 0), + repetition: .init(type: .none, amount: 0, autoreverses: false)) } override func tearDown() { - traits = nil + timing = nil window = nil super.tearDown() @@ -72,7 +75,7 @@ class AnimatorBehavioralTests: XCTestCase { let animator = MotionAnimator() let initialValue = view.layer.value(forKeyPath: keyPath.rawValue) ?? NSNull() - animator.animate(with: traits, + animator.animate(with: timing, to: view.layer, withValues: [initialValue, value], keyPath: keyPath) @@ -96,7 +99,7 @@ class AnimatorBehavioralTests: XCTestCase { CATransaction.flush() let animator = MotionAnimator() - animator.animate(with: traits) { + animator.animate(with: timing) { view.layer.setValue(value, forKeyPath: keyPath.rawValue) } @@ -120,7 +123,7 @@ class AnimatorBehavioralTests: XCTestCase { let animator = MotionAnimator() let initialValue = layer.value(forKeyPath: keyPath.rawValue) ?? NSNull() - animator.animate(with: traits, + animator.animate(with: timing, to: layer, withValues: [initialValue, value], keyPath: keyPath) @@ -144,7 +147,7 @@ class AnimatorBehavioralTests: XCTestCase { CATransaction.flush() let animator = MotionAnimator() - animator.animate(with: traits) { + animator.animate(with: timing) { layer.setValue(value, forKeyPath: keyPath.rawValue) } diff --git a/tests/unit/MotionAnimatorTests.m b/tests/unit/MotionAnimatorTests.m index 9407247..10a0eac 100644 --- a/tests/unit/MotionAnimatorTests.m +++ b/tests/unit/MotionAnimatorTests.m @@ -27,11 +27,13 @@ - (void)testNoDurationSetsValueInstantly { CALayer *layer = [[CALayer alloc] init]; - MDMAnimationTraits *traits = [[MDMAnimationTraits alloc] initWithDuration:0]; + MDMMotionTiming timing = { + .duration = 0, + }; layer.opacity = 0.5; - [animator animateWithTraits:traits toLayer:layer withValues:@[ @0, @1 ] keyPath:@"opacity"]; + [animator animateWithTiming:timing toLayer:layer withValues:@[ @0, @1 ] keyPath:@"opacity"]; XCTAssertEqual(layer.opacity, 1); } @@ -41,12 +43,14 @@ - (void)testNoDurationCallsCompletionHandler { CALayer *layer = [[CALayer alloc] init]; - MDMAnimationTraits *traits = [[MDMAnimationTraits alloc] initWithDuration:0]; + MDMMotionTiming timing = { + .duration = 0, + }; layer.opacity = 0.5; __block BOOL didInvokeCompletion = false; - [animator animateWithTraits:traits toLayer:layer withValues:@[ @0, @1 ] keyPath:@"opacity" completion:^{ + [animator animateWithTiming:timing toLayer:layer withValues:@[ @0, @1 ] keyPath:@"opacity" completion:^{ didInvokeCompletion = true; }]; @@ -60,11 +64,13 @@ - (void)testReversingSetsTheFirstValue { CALayer *layer = [[CALayer alloc] init]; - MDMAnimationTraits *traits = [[MDMAnimationTraits alloc] initWithDuration:0]; + MDMMotionTiming timing = { + .duration = 0, + }; layer.opacity = 0.5; - [animator animateWithTraits:traits toLayer:layer withValues:@[ @0, @1 ] keyPath:@"cornerRadius"]; + [animator animateWithTiming:timing toLayer:layer withValues:@[ @0, @1 ] keyPath:@"cornerRadius"]; XCTAssertEqual(layer.cornerRadius, 0); } @@ -79,11 +85,11 @@ - (void)testCubicBezierAnimationFloatValue { // Setting to some bogus value because it will be ignored with the default animator settings. layer.cornerRadius = 0.5; - CAMediaTimingFunction *timingFunction = - [CAMediaTimingFunction functionWithControlPoints:0.1 :0.2 :0.3 :0.4]; - MDMAnimationTraits *traits = [[MDMAnimationTraits alloc] initWithDelay:0.5 - duration:1 - timingCurve:timingFunction]; + MDMMotionTiming timing = { + .delay = 0.5, + .duration = 1, + .curve = MDMMotionCurveMakeBezier(0.1, 0.2, 0.3, 0.4), + }; __block BOOL didAddAnimation = false; [animator addCoreAnimationTracer:^(CALayer *layer, CAAnimation *animation) { @@ -92,7 +98,7 @@ - (void)testCubicBezierAnimationFloatValue { XCTAssertEqual(basicAnimation.keyPath, keyPath); - XCTAssertEqual(basicAnimation.duration, traits.duration); + XCTAssertEqual(basicAnimation.duration, timing.duration); XCTAssertGreaterThan(basicAnimation.beginTime, 0); XCTAssertTrue(basicAnimation.additive); @@ -103,15 +109,15 @@ - (void)testCubicBezierAnimationFloatValue { float point2[2]; [basicAnimation.timingFunction getControlPointAtIndex:1 values:point1]; [basicAnimation.timingFunction getControlPointAtIndex:2 values:point2]; - XCTAssertEqualWithAccuracy(timingFunction.mdm_point1.x, point1[0], 0.00001); - XCTAssertEqualWithAccuracy(timingFunction.mdm_point1.y, point1[1], 0.00001); - XCTAssertEqualWithAccuracy(timingFunction.mdm_point2.x, point2[0], 0.00001); - XCTAssertEqualWithAccuracy(timingFunction.mdm_point2.y, point2[1], 0.00001); + XCTAssertEqualWithAccuracy(timing.curve.data[0], point1[0], 0.00001); + XCTAssertEqualWithAccuracy(timing.curve.data[1], point1[1], 0.00001); + XCTAssertEqualWithAccuracy(timing.curve.data[2], point2[0], 0.00001); + XCTAssertEqualWithAccuracy(timing.curve.data[3], point2[1], 0.00001); didAddAnimation = true; }]; - [animator animateWithTraits:traits toLayer:layer withValues:@[ @0, @1 ] keyPath:keyPath]; + [animator animateWithTiming:timing toLayer:layer withValues:@[ @0, @1 ] keyPath:keyPath]; XCTAssertEqual(layer.cornerRadius, 1); XCTAssertTrue(didAddAnimation); @@ -127,11 +133,11 @@ - (void)testSpringAnimationFloatValue { // Setting to some bogus value because it will be ignored with the default animator settings. layer.cornerRadius = 0.5; - MDMSpringTimingCurve *springCurve = - [[MDMSpringTimingCurve alloc] initWithMass:0.1 tension:0.2 friction:0.3]; - MDMAnimationTraits *traits = [[MDMAnimationTraits alloc] initWithDelay:0.5 - duration:1 - timingCurve:springCurve]; + MDMMotionTiming timing = { + .delay = 0.5, + .duration = 1, + .curve = MDMMotionCurveMakeSpring(0.1, 0.2, 0.3), + }; __block BOOL didAddAnimation = false; [animator addCoreAnimationTracer:^(CALayer *layer, CAAnimation *animation) { @@ -143,7 +149,7 @@ - (void)testSpringAnimationFloatValue { if ([springAnimation respondsToSelector:@selector(settlingDuration)]) { XCTAssertEqual(springAnimation.duration, springAnimation.settlingDuration); } else { - XCTAssertEqual(springAnimation.duration, traits.duration); + XCTAssertEqual(springAnimation.duration, timing.duration); } XCTAssertGreaterThan(springAnimation.beginTime, 0); @@ -151,14 +157,14 @@ - (void)testSpringAnimationFloatValue { XCTAssertEqual([springAnimation.fromValue doubleValue], -1); XCTAssertEqual([springAnimation.toValue doubleValue], 0); - XCTAssertEqualWithAccuracy(springCurve.mass, springAnimation.mass, 0.00001); - XCTAssertEqualWithAccuracy(springCurve.tension, springAnimation.stiffness, 0.00001); - XCTAssertEqualWithAccuracy(springCurve.friction, springAnimation.damping, 0.00001); + XCTAssertEqualWithAccuracy(timing.curve.data[0], springAnimation.mass, 0.00001); + XCTAssertEqualWithAccuracy(timing.curve.data[1], springAnimation.stiffness, 0.00001); + XCTAssertEqualWithAccuracy(timing.curve.data[2], springAnimation.damping, 0.00001); didAddAnimation = true; }]; - [animator animateWithTraits:traits toLayer:layer withValues:@[ @0, @1 ] keyPath:keyPath]; + [animator animateWithTiming:timing toLayer:layer withValues:@[ @0, @1 ] keyPath:keyPath]; XCTAssertEqual(layer.cornerRadius, 1); XCTAssertTrue(didAddAnimation); diff --git a/tests/unit/MotionAnimatorTests.swift b/tests/unit/MotionAnimatorTests.swift index 7a221aa..c6649d6 100644 --- a/tests/unit/MotionAnimatorTests.swift +++ b/tests/unit/MotionAnimatorTests.swift @@ -26,35 +26,38 @@ class MotionAnimatorTests: XCTestCase { func testAnimatorAPIsCompile() { let animator = MotionAnimator() - let traits = MDMAnimationTraits(duration: 1) + let 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 layer = CALayer() - animator.animate(with: traits, to: layer, + animator.animate(with: timing, to: layer, withValues: [UIColor.blue, UIColor.red], keyPath: .backgroundColor) - animator.animate(with: traits, to: layer, + animator.animate(with: timing, to: layer, withValues: [CGRect.zero, CGRect(x: 0, y: 0, width: 100, height: 50)], keyPath: .bounds) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .cornerRadius) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .height) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .opacity) - animator.animate(with: traits, to: layer, + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .cornerRadius) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .height) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .opacity) + animator.animate(with: timing, to: layer, withValues: [CGPoint.zero, CGPoint(x: 1, y: 1)], keyPath: .position) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .rotation) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .scale) - animator.animate(with: traits, to: layer, + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .scale) + animator.animate(with: timing, to: layer, withValues: [CGSize.zero, CGSize(width: 1, height: 1)], keyPath: .shadowOffset) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .shadowOpacity) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .shadowRadius) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .strokeStart) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .strokeEnd) - animator.animate(with: traits, to: layer, + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .shadowOpacity) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .shadowRadius) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .strokeStart) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .strokeEnd) + animator.animate(with: timing, to: layer, withValues: [CGAffineTransform(rotationAngle: 12), CGAffineTransform(rotationAngle: 50)], keyPath: .transform) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .width) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .x) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .y) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .width) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .x) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .y) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .init(rawValue: "bounds.size.width")) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .init(rawValue: "bounds.size.width")) XCTAssertTrue(true) } @@ -62,7 +65,11 @@ class MotionAnimatorTests: XCTestCase { func testAnimatorOnlyUsesSingleNonAdditiveAnimationForKeyPath() { let animator = MotionAnimator() animator.additive = false - let traits = MDMAnimationTraits(duration: 1) + + let 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() @@ -72,7 +79,7 @@ class MotionAnimatorTests: XCTestCase { XCTAssertEqual(view.layer.delegate as? UIView, view) UIView.animate(withDuration: 0.5) { - animator.animate(with: traits, to: view.layer, withValues: [0, 1], keyPath: .rotation) + animator.animate(with: timing, to: view.layer, withValues: [0, 1], keyPath: .rotation) XCTAssertEqual(view.layer.animationKeys()?.count, 1) } @@ -80,7 +87,11 @@ class MotionAnimatorTests: XCTestCase { func testCompletionCallbackIsExecutedWithZeroDuration() { let animator = MotionAnimator() - let traits = MDMAnimationTraits(duration: 1) + + let timing = MotionTiming(delay: 0, + duration: 0, + curve: MotionCurveMakeBezier(p1x: 0, p1y: 0, p2x: 0, p2y: 0), + repetition: .init(type: .none, amount: 0, autoreverses: false)) let window = UIWindow() window.makeKeyAndVisible() @@ -90,7 +101,7 @@ class MotionAnimatorTests: XCTestCase { XCTAssertEqual(view.layer.delegate as? UIView, view) let didComplete = expectation(description: "Did complete") - animator.animate(with: traits, to: view.layer, withValues: [0, 1], keyPath: .rotation) { + animator.animate(with: timing, to: view.layer, withValues: [0, 1], keyPath: .rotation) { didComplete.fulfill() } diff --git a/tests/unit/NonAdditiveAnimatorTests.swift b/tests/unit/NonAdditiveAnimatorTests.swift index 8a17617..8fa5fdf 100644 --- a/tests/unit/NonAdditiveAnimatorTests.swift +++ b/tests/unit/NonAdditiveAnimatorTests.swift @@ -23,7 +23,7 @@ import MotionAnimator class NonAdditiveAnimationTests: XCTestCase { var animator: MotionAnimator! - var traits: MDMAnimationTraits! + var timing: MotionTiming! var view: UIView! override func setUp() { @@ -33,7 +33,10 @@ class NonAdditiveAnimationTests: XCTestCase { animator.additive = false - traits = MDMAnimationTraits(duration: 1) + 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() @@ -46,14 +49,14 @@ class NonAdditiveAnimationTests: XCTestCase { override func tearDown() { animator = nil - traits = nil + timing = nil view = nil super.tearDown() } func testNumericKeyPathsDontAnimateAdditively() { - animator.animate(with: traits, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) + animator.animate(with: timing, to: view.layer, withValues: [1, 0], keyPath: .cornerRadius) XCTAssertNotNil(view.layer.animationKeys(), "Expected an animation to be added, but none were found.") @@ -72,7 +75,7 @@ class NonAdditiveAnimationTests: XCTestCase { } func testSizeKeyPathsDontAnimateAdditively() { - animator.animate(with: traits, to: view.layer, + animator.animate(with: timing, to: view.layer, withValues: [CGSize(width: 0, height: 0), CGSize(width: 1, height: 2)], keyPath: .shadowOffset) @@ -93,7 +96,7 @@ class NonAdditiveAnimationTests: XCTestCase { } func testPositionKeyPathsDontAnimateAdditively() { - animator.animate(with: traits, to: view.layer, + animator.animate(with: timing, to: view.layer, withValues: [CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 2)], keyPath: .position) @@ -114,7 +117,7 @@ class NonAdditiveAnimationTests: XCTestCase { } func testRectKeyPathsDontAnimateAdditively() { - animator.animate(with: traits, to: view.layer, + animator.animate(with: timing, to: view.layer, withValues: [CGRect(x: 0, y: 0, width: 0, height: 0), CGRect(x: 0, y: 0, width: 100, height: 50)], keyPath: .bounds) diff --git a/tests/unit/TimeScaleFactorTests.swift b/tests/unit/TimeScaleFactorTests.swift index c5695d0..9e89ad4 100644 --- a/tests/unit/TimeScaleFactorTests.swift +++ b/tests/unit/TimeScaleFactorTests.swift @@ -23,7 +23,10 @@ import MotionAnimator class TimeScaleFactorTests: XCTestCase { - let traits = MDMAnimationTraits(duration: 1) + let timing = MotionTiming(delay: 0, + duration: 1, + curve: MotionCurveMakeBezier(p1x: 0, p1y: 0, p2x: 0, p2y: 0), + repetition: .init(type: .none, amount: 0, autoreverses: false)) var layer: CALayer! var addedAnimations: [CAAnimation]! var animator: MotionAnimator! @@ -49,7 +52,7 @@ class TimeScaleFactorTests: XCTestCase { } func testDefaultTimeScaleFactorDoesNotModifyDuration() { - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .rotation) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation) XCTAssertEqual(addedAnimations.count, 1) let animation = addedAnimations.last! @@ -59,23 +62,23 @@ class TimeScaleFactorTests: XCTestCase { func testExplicitTimeScaleFactorChangesDuration() { animator.timeScaleFactor = 0.5 - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .rotation) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation) XCTAssertEqual(addedAnimations.count, 1) let animation = addedAnimations.last! - XCTAssertEqual(animation.duration, traits.duration * 0.5) + XCTAssertEqual(animation.duration, timing.duration * 0.5) } func testTransactionTimeScaleFactorChangesDuration() { CATransaction.begin() CATransaction.mdm_setTimeScaleFactor(0.5) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .rotation) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation) CATransaction.commit() XCTAssertEqual(addedAnimations.count, 1) let animation = addedAnimations.last! - XCTAssertEqual(animation.duration, traits.duration * 0.5) + XCTAssertEqual(animation.duration, timing.duration * 0.5) } func testTransactionTimeScaleFactorOverridesAnimatorTimeScaleFactor() { @@ -84,13 +87,13 @@ class TimeScaleFactorTests: XCTestCase { CATransaction.begin() CATransaction.mdm_setTimeScaleFactor(0.5) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .rotation) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation) CATransaction.commit() XCTAssertEqual(addedAnimations.count, 1) let animation = addedAnimations.last! - XCTAssertEqual(animation.duration, traits.duration * 0.5) + XCTAssertEqual(animation.duration, timing.duration * 0.5) } func testNilTransactionTimeScaleFactorUsesAnimatorTimeScaleFactor() { @@ -100,12 +103,12 @@ class TimeScaleFactorTests: XCTestCase { CATransaction.mdm_setTimeScaleFactor(0.5) CATransaction.mdm_setTimeScaleFactor(nil) - animator.animate(with: traits, to: layer, withValues: [0, 1], keyPath: .rotation) + animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation) CATransaction.commit() XCTAssertEqual(addedAnimations.count, 1) let animation = addedAnimations.last! - XCTAssertEqual(animation.duration, traits.duration * 2) + XCTAssertEqual(animation.duration, timing.duration * 2) } }