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

Commit 51ac34a

Browse files
authored
Add support for removing added animations (#42)
* Add an animation registrar. * Docs. * Misc consolidation. * Remove newline. * Comment. * Remove unused method. * Rename to stop. * Docs. * Generics. * Comments. * Runloop.
1 parent 213bfb3 commit 51ac34a

File tree

8 files changed

+330
-13
lines changed

8 files changed

+330
-13
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
667A3F4C1DEE269400CB3A99 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 667A3F4A1DEE269400CB3A99 /* LaunchScreen.storyboard */; };
1919
667A3F541DEE273000CB3A99 /* TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667A3F531DEE273000CB3A99 /* TableOfContents.swift */; };
2020
6687264A1EF04B4C00113675 /* MotionAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668726491EF04B4C00113675 /* MotionAnimatorTests.swift */; };
21+
66A6A6681FBA158000DE54CB /* AnimationRemovalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */; };
2122
66BF5A8F1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */; };
2223
66DD4BF51EEF0ECB00207119 /* CalendarCardExpansionExample.m in Sources */ = {isa = PBXBuildFile; fileRef = 66DD4BF41EEF0ECB00207119 /* CalendarCardExpansionExample.m */; };
2324
66DD4BF81EEF1C4B00207119 /* CalendarChipMotionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 66DD4BF71EEF1C4B00207119 /* CalendarChipMotionSpec.m */; };
@@ -62,6 +63,7 @@
6263
667A3F4D1DEE269400CB3A99 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6364
667A3F531DEE273000CB3A99 /* TableOfContents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableOfContents.swift; sourceTree = "<group>"; };
6465
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionAnimatorTests.swift; sourceTree = "<group>"; };
66+
66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationRemovalTests.swift; sourceTree = "<group>"; };
6567
66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImplicitAnimationTests.swift; sourceTree = "<group>"; };
6668
66DD4BF31EEF0ECB00207119 /* CalendarCardExpansionExample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CalendarCardExpansionExample.h; sourceTree = "<group>"; };
6769
66DD4BF41EEF0ECB00207119 /* CalendarCardExpansionExample.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CalendarCardExpansionExample.m; sourceTree = "<group>"; };
@@ -208,6 +210,7 @@
208210
66FD99F81EE9FBA000C53A82 /* unit */ = {
209211
isa = PBXGroup;
210212
children = (
213+
66A6A6671FBA158000DE54CB /* AnimationRemovalTests.swift */,
211214
66FD99F91EE9FBBE00C53A82 /* MotionAnimatorTests.m */,
212215
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */,
213216
66BF5A8E1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift */,
@@ -491,6 +494,7 @@
491494
6625876C1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift in Sources */,
492495
660636021FACC24300C3DFB8 /* TimeScaleFactorTests.swift in Sources */,
493496
66BF5A8F1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift in Sources */,
497+
66A6A6681FBA158000DE54CB /* AnimationRemovalTests.swift in Sources */,
494498
6687264A1EF04B4C00113675 /* MotionAnimatorTests.swift in Sources */,
495499
66FD99FA1EE9FBBE00C53A82 /* MotionAnimatorTests.m in Sources */,
496500
);

src/MDMMotionAnimator.h

+17
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,21 @@ NS_SWIFT_NAME(MotionAnimator)
132132
animations:(nonnull void (^)(void))animations
133133
completion:(nullable void(^)(void))completion;
134134

135+
/**
136+
Removes every animation added by this animator.
137+
138+
Removing animations in this manner will give the appearance of each animated layer property
139+
instantaneously jumping to its animated destination.
140+
*/
141+
- (void)removeAllAnimations;
142+
143+
/**
144+
Commits the presentation layer value to the model layer value for every active animation's key path
145+
and then removes every animation.
146+
147+
This method is most commonly called in reaction to the initiation of a gesture so that any
148+
in-flight animations are stopped at their current on-screen position.
149+
*/
150+
- (void)stopAllAnimations;
151+
135152
@end

src/MDMMotionAnimator.m

+13-13
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@
2020

2121
#import "CATransaction+MotionAnimator.h"
2222
#import "private/CABasicAnimation+MotionAnimator.h"
23+
#import "private/MDMAnimationRegistrar.h"
2324
#import "private/MDMUIKitValueCoercion.h"
2425
#import "private/MDMBlockAnimations.h"
2526
#import "private/MDMDragCoefficient.h"
2627

2728
@implementation MDMMotionAnimator {
2829
NSMutableArray *_tracers;
30+
MDMAnimationRegistrar *_registrar;
2931
}
3032

3133
- (instancetype)init {
3234
self = [super init];
3335
if (self) {
36+
_registrar = [[MDMAnimationRegistrar alloc] init];
3437
_timeScaleFactor = 1;
3538
_additive = true;
3639
}
@@ -93,23 +96,12 @@ - (void)animateWithTiming:(MDMMotionTiming)timing
9396
animation.fillMode = kCAFillModeBackwards;
9497
}
9598

96-
if (completion) {
97-
[CATransaction begin];
98-
[CATransaction setCompletionBlock:completion];
99-
}
100-
101-
// When we use a nil key, Core Animation will ensure that the animation is added with a
102-
// unique key - this enables our additive animations to stack upon one another.
10399
NSString *key = _additive ? nil : keyPath;
104-
[layer addAnimation:animation forKey:key];
100+
[_registrar addAnimation:animation toLayer:layer forKey:key completion:completion];
105101

106102
for (void (^tracer)(CALayer *, CAAnimation *) in _tracers) {
107103
tracer(layer, animation);
108104
}
109-
110-
if (completion) {
111-
[CATransaction commit];
112-
}
113105
}
114106
}
115107

@@ -165,5 +157,13 @@ - (CGFloat)computedTimeScaleFactor {
165157
return MDMSimulatorAnimationDragCoefficient() * timeScaleFactor;
166158
}
167159

168-
@end
160+
- (void)removeAllAnimations {
161+
[_registrar removeAllAnimations];
162+
}
163+
164+
- (void)stopAllAnimations {
165+
[_registrar commitCurrentAnimationValuesToAllLayers];
166+
[_registrar removeAllAnimations];
167+
}
169168

169+
@end

src/private/MDMAnimationRegistrar.h

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
// Tracks and manipulates animations that have been added to a layer.
21+
@interface MDMAnimationRegistrar : NSObject
22+
23+
// Invokes the layer's addAnimation:forKey: method with the provided animation and key and tracks
24+
// its association. Upon completion of the animation, the provided optional completion block will be
25+
// executed.
26+
- (void)addAnimation:(nonnull CABasicAnimation *)animation
27+
toLayer:(nonnull CALayer *)layer
28+
forKey:(nonnull NSString *)key
29+
completion:(void(^ __nullable)(void))completion;
30+
31+
// For every active animation, reads the associated layer's presentation layer key path and writes
32+
// it to the layer.
33+
- (void)commitCurrentAnimationValuesToAllLayers;
34+
35+
// Removes all active animations from their associated layer.
36+
- (void)removeAllAnimations;
37+
38+
@end

src/private/MDMAnimationRegistrar.m

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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 "MDMAnimationRegistrar.h"
18+
19+
#import "MDMRegisteredAnimation.h"
20+
21+
@implementation MDMAnimationRegistrar {
22+
NSMapTable<CALayer *, NSMutableSet<MDMRegisteredAnimation *> *> *_layersToRegisteredAnimation;
23+
}
24+
25+
- (instancetype)init {
26+
self = [super init];
27+
if (self) {
28+
_layersToRegisteredAnimation = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory
29+
valueOptions:NSPointerFunctionsStrongMemory];
30+
}
31+
return self;
32+
}
33+
34+
#pragma mark - Private
35+
36+
- (void)forEachAnimation:(void (^)(CALayer *, CABasicAnimation *, NSString *))work {
37+
// Copy the registered animations before iteration in case further modifications happen to the
38+
// registered animations. Consider if we remove an animation, its associated completion block
39+
// might invoke logic that adds a new animation, potentially modifying our collections.
40+
for (CALayer *layer in [_layersToRegisteredAnimation copy]) {
41+
NSSet *keyPathAnimations = [_layersToRegisteredAnimation objectForKey:layer];
42+
for (MDMRegisteredAnimation *keyPathAnimation in [keyPathAnimations copy]) {
43+
if (![keyPathAnimation.animation isKindOfClass:[CABasicAnimation class]]) {
44+
continue;
45+
}
46+
47+
work(layer, [keyPathAnimation.animation copy], keyPathAnimation.key);
48+
}
49+
}
50+
}
51+
52+
#pragma mark - Public
53+
54+
- (void)addAnimation:(CABasicAnimation *)animation
55+
toLayer:(CALayer *)layer
56+
forKey:(NSString *)key
57+
completion:(void(^)(void))completion {
58+
if (key == nil) {
59+
key = [NSUUID UUID].UUIDString;
60+
}
61+
62+
NSMutableSet *animatedKeyPaths = [_layersToRegisteredAnimation objectForKey:layer];
63+
if (!animatedKeyPaths) {
64+
animatedKeyPaths = [[NSMutableSet alloc] init];
65+
[_layersToRegisteredAnimation setObject:animatedKeyPaths forKey:layer];
66+
}
67+
MDMRegisteredAnimation *keyPathAnimation =
68+
[[MDMRegisteredAnimation alloc] initWithKey:key animation:animation];
69+
[animatedKeyPaths addObject:keyPathAnimation];
70+
71+
[CATransaction begin];
72+
[CATransaction setCompletionBlock:^{
73+
[animatedKeyPaths removeObject:keyPathAnimation];
74+
75+
if (completion) {
76+
completion();
77+
}
78+
}];
79+
80+
[layer addAnimation:animation forKey:key];
81+
82+
[CATransaction commit];
83+
}
84+
85+
- (void)commitCurrentAnimationValuesToAllLayers {
86+
[self forEachAnimation:^(CALayer *layer, CABasicAnimation *animation, NSString *key) {
87+
id presentationLayer = [layer presentationLayer];
88+
if (presentationLayer != nil) {
89+
id presentationValue = [presentationLayer valueForKeyPath:animation.keyPath];
90+
[layer setValue:presentationValue forKeyPath:animation.keyPath];
91+
}
92+
}];
93+
}
94+
95+
- (void)removeAllAnimations {
96+
[self forEachAnimation:^(CALayer *layer, CABasicAnimation *animation, NSString *key) {
97+
[layer removeAnimationForKey:key];
98+
}];
99+
[_layersToRegisteredAnimation removeAllObjects];
100+
}
101+
102+
@end

src/private/MDMRegisteredAnimation.h

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 MDMRegisteredAnimation : NSObject
21+
22+
- (instancetype)initWithKey:(NSString *)key animation:(CABasicAnimation *)animation;
23+
24+
@property(nonatomic, copy, readonly) NSString *key;
25+
26+
@property(nonatomic, strong, readonly) CABasicAnimation *animation;
27+
28+
@end

src/private/MDMRegisteredAnimation.m

+39
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 "MDMRegisteredAnimation.h"
18+
19+
@implementation MDMRegisteredAnimation
20+
21+
- (instancetype)initWithKey:(NSString *)key animation:(CABasicAnimation *)animation {
22+
self = [super init];
23+
if (self) {
24+
_key = [key copy];
25+
_animation = animation;
26+
}
27+
return self;
28+
}
29+
30+
- (NSUInteger)hash {
31+
return _key.hash;
32+
}
33+
34+
- (BOOL)isEqual:(id)object {
35+
return [_key isEqual:object];
36+
}
37+
38+
@end
39+

0 commit comments

Comments
 (0)