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

Commit 17601b9

Browse files
authored
Add support for customizing the timeScaleFactor as part of a CATransaction (#25)
* Add support for customizing the timeScaleFactor as part of a CATransaction. If an animator is shared across a variety of animations, it can often be preferable to change the animator's behavior only within the context of a single transaction. For example, if all animations in a transaction should have their duration scaled by a certain amount a client would previously have had to cache the previous timeScaleFactror, set the new one on the animator, and then restore the timeScaleFactor once the animations were added. The CATransaction API introduced by this change makes it easier for clients to make per-transaction timeScaleFactor changes like so: ```swift CATransaction.begin() CATransaction.mdm_setTimeScaleFactor(0.5) animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation) CATransaction.commit() ``` * Forgot an a.
1 parent bb42e61 commit 17601b9

File tree

7 files changed

+203
-2
lines changed

7 files changed

+203
-2
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
2AA864EDA683CEF5FAA721BE /* Pods_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DBE814C7B88BAD6337052DB /* Pods_UnitTests.framework */; };
11+
660636021FACC24300C3DFB8 /* TimeScaleFactorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */; };
1112
666FAA841D384A6B000363DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 666FAA831D384A6B000363DA /* AppDelegate.swift */; };
1213
666FAA8B1D384A6B000363DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 666FAA8A1D384A6B000363DA /* Assets.xcassets */; };
1314
666FAA8E1D384A6B000363DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 666FAA8C1D384A6B000363DA /* LaunchScreen.storyboard */; };
@@ -43,6 +44,7 @@
4344
2DBE814C7B88BAD6337052DB /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4445
50D808A6F9E944D54276D32F /* Pods_MotionAnimatorCatalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MotionAnimatorCatalog.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4546
52820916F8FAA40E942A7333 /* Pods-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.release.xcconfig"; sourceTree = "<group>"; };
47+
660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeScaleFactorTests.swift; sourceTree = "<group>"; };
4648
666FAA801D384A6B000363DA /* MotionAnimatorCatalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MotionAnimatorCatalog.app; sourceTree = BUILT_PRODUCTS_DIR; };
4749
666FAA831D384A6B000363DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Catalog/AppDelegate.swift; sourceTree = "<group>"; };
4850
666FAA8A1D384A6B000363DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -204,6 +206,7 @@
204206
children = (
205207
66FD99F91EE9FBBE00C53A82 /* MotionAnimatorTests.m */,
206208
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */,
209+
660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */,
207210
);
208211
path = unit;
209212
sourceTree = "<group>";
@@ -479,6 +482,7 @@
479482
isa = PBXSourcesBuildPhase;
480483
buildActionMask = 2147483647;
481484
files = (
485+
660636021FACC24300C3DFB8 /* TimeScaleFactorTests.swift in Sources */,
482486
6687264A1EF04B4C00113675 /* MotionAnimatorTests.swift in Sources */,
483487
66FD99FA1EE9FBBE00C53A82 /* MotionAnimatorTests.m in Sources */,
484488
);

src/CATransaction+MotionAnimator.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 <Foundation/Foundation.h>
18+
#import <QuartzCore/QuartzCore.h>
19+
20+
@interface CATransaction (MotionAnimator)
21+
22+
/**
23+
Accessor for the "timeScaleFactor" per-thread transaction property.
24+
25+
Returns the transaction-specific time scale factor to be applied to animator animations.
26+
*/
27+
+ (nullable NSNumber *)mdm_timeScaleFactor;
28+
29+
/**
30+
Setter for the "timeScaleFactor" per-thread transaction property.
31+
32+
Sets a transaction-specific time scale factor to be applied to animator animations.
33+
34+
@param timeScaleFactor If nil, the animator's `timeScaleFactor` will be used instead. Should be a
35+
CGFloat value type.
36+
*/
37+
+ (void)mdm_setTimeScaleFactor:(nullable NSNumber *)timeScaleFactor;
38+
39+
@end

src/CATransaction+MotionAnimator.m

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 "CATransaction+MotionAnimator.h"
18+
19+
static NSString *const kTimeScaleFactorKey = @"mdm_timeScaleFactor";
20+
21+
@implementation CATransaction (MotionAnimator)
22+
23+
+ (NSNumber *)mdm_timeScaleFactor {
24+
return [CATransaction valueForKey:kTimeScaleFactorKey];
25+
}
26+
27+
+ (void)mdm_setTimeScaleFactor:(nullable NSNumber *)timeScaleFactor {
28+
return [CATransaction setValue:timeScaleFactor forKey:kTimeScaleFactorKey];
29+
}
30+
31+
@end

src/MDMMotionAnimator.m

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#import <UIKit/UIKit.h>
2020

21+
#import "CATransaction+MotionAnimator.h"
2122
#import "private/CABasicAnimation+MotionAnimator.h"
2223
#import "private/MDMUIKitValueCoercion.h"
2324
#import "private/MDMDragCoefficient.h"
@@ -62,7 +63,7 @@ - (void)animateWithTiming:(MDMMotionTiming)timing
6263
return;
6364
}
6465

65-
CGFloat timeScaleFactor = MDMSimulatorAnimationDragCoefficient() * _timeScaleFactor;
66+
CGFloat timeScaleFactor = [self computedTimeScaleFactor];
6667
CABasicAnimation *animation = MDMAnimationFromTiming(timing, timeScaleFactor);
6768

6869
if (animation) {
@@ -126,5 +127,21 @@ - (void)addCoreAnimationTracer:(void (^)(CALayer *, CAAnimation *))tracer {
126127
[_tracers addObject:[tracer copy]];
127128
}
128129

130+
- (CGFloat)computedTimeScaleFactor {
131+
CGFloat timeScaleFactor;
132+
id transactionTimeScaleFactor = [CATransaction mdm_timeScaleFactor];
133+
if (transactionTimeScaleFactor != nil) {
134+
#if CGFLOAT_IS_DOUBLE
135+
timeScaleFactor = [transactionTimeScaleFactor doubleValue];
136+
#else
137+
timeScaleFactor = [transactionTimeScaleFactor floatValue];
138+
#endif
139+
} else {
140+
timeScaleFactor = _timeScaleFactor;
141+
}
142+
143+
return MDMSimulatorAnimationDragCoefficient() * timeScaleFactor;
144+
}
145+
129146
@end
130147

src/MotionAnimator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
limitations under the License.
1515
*/
1616

17+
#import "CATransaction+MotionAnimator.h"
1718
#import "MDMAnimatableKeyPaths.h"
1819
#import "MDMMotionAnimator.h"
1920

tests/unit/MotionAnimatorTests.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,3 @@ class MotionAnimatorTests: XCTestCase {
7070
}
7171
}
7272
}
73-

tests/unit/TimeScaleFactorTests.swift

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
import MotionAnimator
19+
20+
class TimeScaleFactorTests: XCTestCase {
21+
22+
let timing = MotionTiming(delay: 0,
23+
duration: 1,
24+
curve: .init(type: .bezier, data: (0, 0, 0, 0)),
25+
repetition: .init(type: .none, amount: 0, autoreverses: false))
26+
var layer: CALayer!
27+
var addedAnimations: [CAAnimation]!
28+
var animator: MotionAnimator!
29+
30+
override func setUp() {
31+
super.setUp()
32+
33+
addedAnimations = []
34+
animator = MotionAnimator()
35+
animator.addCoreAnimationTracer { (_, animation) in
36+
self.addedAnimations.append(animation)
37+
}
38+
39+
layer = CALayer()
40+
}
41+
42+
override func tearDown() {
43+
layer = nil
44+
addedAnimations = nil
45+
animator = nil
46+
47+
super.tearDown()
48+
}
49+
50+
func testDefaultTimeScaleFactorDoesNotModifyDuration() {
51+
animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation)
52+
53+
XCTAssertEqual(addedAnimations.count, 1)
54+
let animation = addedAnimations.last!
55+
XCTAssertEqual(animation.duration, 1)
56+
}
57+
58+
func testExplicitTimeScaleFactorChangesDuration() {
59+
animator.timeScaleFactor = 0.5
60+
61+
animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation)
62+
63+
XCTAssertEqual(addedAnimations.count, 1)
64+
let animation = addedAnimations.last!
65+
XCTAssertEqual(animation.duration, timing.duration * 0.5)
66+
}
67+
68+
func testTransactionTimeScaleFactorChangesDuration() {
69+
CATransaction.begin()
70+
CATransaction.mdm_setTimeScaleFactor(0.5)
71+
72+
animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation)
73+
CATransaction.commit()
74+
75+
XCTAssertEqual(addedAnimations.count, 1)
76+
let animation = addedAnimations.last!
77+
XCTAssertEqual(animation.duration, timing.duration * 0.5)
78+
}
79+
80+
func testTransactionTimeScaleFactorOverridesAnimatorTimeScaleFactor() {
81+
animator.timeScaleFactor = 2
82+
83+
CATransaction.begin()
84+
CATransaction.mdm_setTimeScaleFactor(0.5)
85+
86+
animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation)
87+
88+
CATransaction.commit()
89+
90+
XCTAssertEqual(addedAnimations.count, 1)
91+
let animation = addedAnimations.last!
92+
XCTAssertEqual(animation.duration, timing.duration * 0.5)
93+
}
94+
95+
func testNilTransactionTimeScaleFactorUsesAnimatorTimeScaleFactor() {
96+
animator.timeScaleFactor = 2
97+
98+
CATransaction.begin()
99+
CATransaction.mdm_setTimeScaleFactor(0.5)
100+
CATransaction.mdm_setTimeScaleFactor(nil)
101+
102+
animator.animate(with: timing, to: layer, withValues: [0, 1], keyPath: .rotation)
103+
104+
CATransaction.commit()
105+
106+
XCTAssertEqual(addedAnimations.count, 1)
107+
let animation = addedAnimations.last!
108+
XCTAssertEqual(animation.duration, timing.duration * 2)
109+
}
110+
}

0 commit comments

Comments
 (0)