diff --git a/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj b/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj index 7b2557b..fef005f 100644 --- a/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj +++ b/examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 667A3F541DEE273000CB3A99 /* TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667A3F531DEE273000CB3A99 /* TableOfContents.swift */; }; 6687264A1EF04B4C00113675 /* MotionAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668726491EF04B4C00113675 /* MotionAnimatorTests.swift */; }; 668819FA1FE2EB36003A9420 /* UIKitEquivalencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668819F91FE2EB36003A9420 /* UIKitEquivalencyTests.swift */; }; + 668819F81FE2E5C6003A9420 /* SpringTimingCurveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668819F71FE2E5C6003A9420 /* SpringTimingCurveTests.swift */; }; 669B6CA91FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669B6CA81FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift */; }; 66A6A6681FBA158000DE54CB /* AnimationRemovalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */; }; 66BF5A8F1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */; }; @@ -80,6 +81,7 @@ 667A3F531DEE273000CB3A99 /* TableOfContents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableOfContents.swift; sourceTree = ""; }; 668726491EF04B4C00113675 /* MotionAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionAnimatorTests.swift; sourceTree = ""; }; 668819F91FE2EB36003A9420 /* UIKitEquivalencyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitEquivalencyTests.swift; sourceTree = ""; }; + 668819F71FE2E5C6003A9420 /* SpringTimingCurveTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringTimingCurveTests.swift; sourceTree = ""; }; 669B6CA81FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionAnimatorBehavioralTests.swift; sourceTree = ""; }; 66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationRemovalTests.swift; sourceTree = ""; }; 66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImplicitAnimationTests.swift; sourceTree = ""; }; @@ -241,6 +243,7 @@ 669B6CA81FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift */, 66FD99F91EE9FBBE00C53A82 /* MotionAnimatorTests.m */, 668726491EF04B4C00113675 /* MotionAnimatorTests.swift */, + 668819F71FE2E5C6003A9420 /* SpringTimingCurveTests.swift */, 664F59991FCE6661002EC56D /* NonAdditiveAnimatorTests.swift */, 664F59951FCDB2E5002EC56D /* QuartzCoreBehavioralTests.swift */, 660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */, @@ -522,6 +525,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 668819F81FE2E5C6003A9420 /* SpringTimingCurveTests.swift in Sources */, 6625876C1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift in Sources */, 66EF6F281FC33C4800C83A63 /* HeadlessLayerImplicitAnimationTests.swift in Sources */, 664F599A1FCE6661002EC56D /* NonAdditiveAnimatorTests.swift in Sources */, diff --git a/src/private/CABasicAnimation+MotionAnimator.m b/src/private/CABasicAnimation+MotionAnimator.m index 17f05ae..087f97f 100644 --- a/src/private/CABasicAnimation+MotionAnimator.m +++ b/src/private/CABasicAnimation+MotionAnimator.m @@ -81,20 +81,29 @@ static BOOL IsAnimationKeyPathAlwaysNonAdditive(NSString *keyPath) { return animation; } - if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]) { - MDMSpringTimingCurve *springTiming = (MDMSpringTimingCurve *)traits.timingCurve; - + CABasicAnimation *(^animationFromSpring)(MDMSpringTimingCurve *) = + ^(MDMSpringTimingCurve *springTiming) { #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 *animation = [CASpringAnimation animation]; #pragma clang diagnostic pop - animation.mass = springTiming.mass; - animation.stiffness = springTiming.tension; - animation.damping = springTiming.friction; - animation.duration = traits.duration; - return animation; + animation.mass = springTiming.mass; + animation.stiffness = springTiming.tension; + animation.damping = springTiming.friction; + animation.duration = traits.duration; + return animation; + }; + + if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurveGenerator class]]) { + MDMSpringTimingCurveGenerator *springTimingGenerator = + (MDMSpringTimingCurveGenerator *)traits.timingCurve; + return animationFromSpring(springTimingGenerator.springTimingCurve); + } + + if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]) { + return animationFromSpring((MDMSpringTimingCurve *)traits.timingCurve); } NSCAssert(NO, @"Unsupported animation trait: %@", traits); diff --git a/tests/unit/SpringTimingCurveTests.swift b/tests/unit/SpringTimingCurveTests.swift new file mode 100644 index 0000000..92ebed2 --- /dev/null +++ b/tests/unit/SpringTimingCurveTests.swift @@ -0,0 +1,76 @@ +/* + 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 SpringTimingCurveTests: XCTestCase { + var animator: MotionAnimator! + var traits: MDMAnimationTraits! + var view: UIView! + + var originalImplementation: IMP? + override func setUp() { + super.setUp() + + animator = MotionAnimator() + traits = MDMAnimationTraits(duration: 1) + + let window = UIWindow() + window.makeKeyAndVisible() + view = UIView() // Need to animate a view's layer to get implicit animations. + window.addSubview(view) + + // Connect our layers to the render server. + CATransaction.flush() + } + + override func tearDown() { + animator = nil + traits = nil + view = nil + + super.tearDown() + } + + @available(iOS 9.0, *) + func testGeneratorCanBeUsedAsATimingCurve() { + traits.timingCurve = MDMSpringTimingCurveGenerator(duration: traits.duration, dampingRatio: 0.5) + + animator.animate(with: traits, between: [1, 0], layer: view.layer, keyPath: .cornerRadius) + + XCTAssertNotNil(view.layer.animationKeys(), + "Expected an animation to be added, but none were found.") + guard let animationKeys = view.layer.animationKeys() else { + return + } + XCTAssertEqual(animationKeys.count, 1, + "Expected only one animation to be added, but the following were found: " + + "\(animationKeys).") + guard let key = animationKeys.first else { + return + } + XCTAssertTrue(view.layer.animation(forKey: key) is CASpringAnimation, + "Expected the animation to be a spring, but it was not: " + + " \(view.layer.animation(forKey: key).debugDescription)") + + } + +}