Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.

Commit 46fd517

Browse files
authored
Add support for using a spring generator as a timing curve. (#91)
* Add support for using a spring generator as a timing curve. * Fix method name.
1 parent 1e76e2b commit 46fd517

File tree

3 files changed

+100
-11
lines changed

3 files changed

+100
-11
lines changed

examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
667A3F541DEE273000CB3A99 /* TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667A3F531DEE273000CB3A99 /* TableOfContents.swift */; };
2626
6687264A1EF04B4C00113675 /* MotionAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668726491EF04B4C00113675 /* MotionAnimatorTests.swift */; };
2727
668819FA1FE2EB36003A9420 /* UIKitEquivalencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668819F91FE2EB36003A9420 /* UIKitEquivalencyTests.swift */; };
28+
668819F81FE2E5C6003A9420 /* SpringTimingCurveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668819F71FE2E5C6003A9420 /* SpringTimingCurveTests.swift */; };
2829
669B6CA91FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669B6CA81FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift */; };
2930
66A6A6681FBA158000DE54CB /* AnimationRemovalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */; };
3031
66BF5A8F1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */; };
@@ -80,6 +81,7 @@
8081
667A3F531DEE273000CB3A99 /* TableOfContents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableOfContents.swift; sourceTree = "<group>"; };
8182
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionAnimatorTests.swift; sourceTree = "<group>"; };
8283
668819F91FE2EB36003A9420 /* UIKitEquivalencyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitEquivalencyTests.swift; sourceTree = "<group>"; };
84+
668819F71FE2E5C6003A9420 /* SpringTimingCurveTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringTimingCurveTests.swift; sourceTree = "<group>"; };
8385
669B6CA81FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionAnimatorBehavioralTests.swift; sourceTree = "<group>"; };
8486
66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationRemovalTests.swift; sourceTree = "<group>"; };
8587
66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImplicitAnimationTests.swift; sourceTree = "<group>"; };
@@ -241,6 +243,7 @@
241243
669B6CA81FD0547100B80B76 /* MotionAnimatorBehavioralTests.swift */,
242244
66FD99F91EE9FBBE00C53A82 /* MotionAnimatorTests.m */,
243245
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */,
246+
668819F71FE2E5C6003A9420 /* SpringTimingCurveTests.swift */,
244247
664F59991FCE6661002EC56D /* NonAdditiveAnimatorTests.swift */,
245248
664F59951FCDB2E5002EC56D /* QuartzCoreBehavioralTests.swift */,
246249
660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */,
@@ -522,6 +525,7 @@
522525
isa = PBXSourcesBuildPhase;
523526
buildActionMask = 2147483647;
524527
files = (
528+
668819F81FE2E5C6003A9420 /* SpringTimingCurveTests.swift in Sources */,
525529
6625876C1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift in Sources */,
526530
66EF6F281FC33C4800C83A63 /* HeadlessLayerImplicitAnimationTests.swift in Sources */,
527531
664F599A1FCE6661002EC56D /* NonAdditiveAnimatorTests.swift in Sources */,

src/private/CABasicAnimation+MotionAnimator.m

+20-11
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,29 @@ static BOOL IsAnimationKeyPathAlwaysNonAdditive(NSString *keyPath) {
8181
return animation;
8282
}
8383

84-
if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]) {
85-
MDMSpringTimingCurve *springTiming = (MDMSpringTimingCurve *)traits.timingCurve;
86-
84+
CABasicAnimation *(^animationFromSpring)(MDMSpringTimingCurve *) =
85+
^(MDMSpringTimingCurve *springTiming) {
8786
#pragma clang diagnostic push
88-
// CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're
89-
// linking against the public API on iOS 9+.
87+
// CASpringAnimation is a private API on iOS 8 - we're able to make use of it because we're
88+
// linking against the public API on iOS 9+.
9089
#pragma clang diagnostic ignored "-Wpartial-availability"
91-
CASpringAnimation *animation = [CASpringAnimation animation];
90+
CASpringAnimation *animation = [CASpringAnimation animation];
9291
#pragma clang diagnostic pop
93-
animation.mass = springTiming.mass;
94-
animation.stiffness = springTiming.tension;
95-
animation.damping = springTiming.friction;
96-
animation.duration = traits.duration;
97-
return animation;
92+
animation.mass = springTiming.mass;
93+
animation.stiffness = springTiming.tension;
94+
animation.damping = springTiming.friction;
95+
animation.duration = traits.duration;
96+
return animation;
97+
};
98+
99+
if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurveGenerator class]]) {
100+
MDMSpringTimingCurveGenerator *springTimingGenerator =
101+
(MDMSpringTimingCurveGenerator *)traits.timingCurve;
102+
return animationFromSpring(springTimingGenerator.springTimingCurve);
103+
}
104+
105+
if ([traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]) {
106+
return animationFromSpring((MDMSpringTimingCurve *)traits.timingCurve);
98107
}
99108

100109
NSCAssert(NO, @"Unsupported animation trait: %@", traits);
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import XCTest
18+
#if IS_BAZEL_BUILD
19+
import _MotionAnimator
20+
#else
21+
import MotionAnimator
22+
#endif
23+
24+
class SpringTimingCurveTests: XCTestCase {
25+
var animator: MotionAnimator!
26+
var traits: MDMAnimationTraits!
27+
var view: UIView!
28+
29+
var originalImplementation: IMP?
30+
override func setUp() {
31+
super.setUp()
32+
33+
animator = MotionAnimator()
34+
traits = MDMAnimationTraits(duration: 1)
35+
36+
let window = UIWindow()
37+
window.makeKeyAndVisible()
38+
view = UIView() // Need to animate a view's layer to get implicit animations.
39+
window.addSubview(view)
40+
41+
// Connect our layers to the render server.
42+
CATransaction.flush()
43+
}
44+
45+
override func tearDown() {
46+
animator = nil
47+
traits = nil
48+
view = nil
49+
50+
super.tearDown()
51+
}
52+
53+
@available(iOS 9.0, *)
54+
func testGeneratorCanBeUsedAsATimingCurve() {
55+
traits.timingCurve = MDMSpringTimingCurveGenerator(duration: traits.duration, dampingRatio: 0.5)
56+
57+
animator.animate(with: traits, between: [1, 0], layer: view.layer, keyPath: .cornerRadius)
58+
59+
XCTAssertNotNil(view.layer.animationKeys(),
60+
"Expected an animation to be added, but none were found.")
61+
guard let animationKeys = view.layer.animationKeys() else {
62+
return
63+
}
64+
XCTAssertEqual(animationKeys.count, 1,
65+
"Expected only one animation to be added, but the following were found: "
66+
+ "\(animationKeys).")
67+
guard let key = animationKeys.first else {
68+
return
69+
}
70+
XCTAssertTrue(view.layer.animation(forKey: key) is CASpringAnimation,
71+
"Expected the animation to be a spring, but it was not: "
72+
+ " \(view.layer.animation(forKey: key).debugDescription)")
73+
74+
}
75+
76+
}

0 commit comments

Comments
 (0)