mirror of
https://github.com/material-components/material-components-ios.git
synced 2026-02-20 08:27:32 +08:00
Definitions: - Headless layer: a CALayer without a delegate. - Mounted layer: a CALayer that has been flushed to the render server either via a runloop pump or via `[CATransaction flush]`. Context: MDCShadowLayer includes some logic for animating shadowPath changes alongside corresponding bounds animations. The intent is that, if `shadowPath` is changed then it attempts to inherit any animation traits from existing bounds-related animations, if they exist. This behavior was added in https://github.com/material-components/material-components-ios/pull/2523. This behavior enables changes to shadowPath (which are not animatable by UIView-based animations) to inherit existing UIView-based animations. Prior to this change, only bounds.size would be inspected for this piggy-backing behavior. While this works for UIView-based animations, which decompose `.frame` and `.bounds` animations into `bounds.size` and `bounds.origin`, this does not work for headless layers which directly animate the `bounds` property. E.g. the following snippet would piggy back as expected: ```objc [UIView animateWithDuration:0.1 animations:^{ someView.frame = CGRectMake(0, 0, 100, 50); someView.layer.shadowPath = [UIBezierPath bezierPathWithRect:someView.bounds].CGPath; }]; ``` But the following would not: ```objc [CATransaction begin]; [CATransaction setAnimationDuration:0.5]; shadowLayer.bounds = CGRectMake(0, 0, 100, 50); shadowLayer.shadowPath = [UIBezierPath bezierPathWithRect:shadowLayer.bounds].CGPath; [CATransaction commit]; ``` After this change, MDCShadowLayer will check for animations to `bounds` as well, increasing the likelihood that the shadowPath is able to piggy-back existing animations. This change is part of addressing https://github.com/material-components/material-components-ios/issues/8644
97 lines
3.3 KiB
Objective-C
97 lines
3.3 KiB
Objective-C
// Copyright 2016-present the Material Components for iOS 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/XCTest.h>
|
|
#import "MaterialShadowLayer.h"
|
|
|
|
@interface ShadowLayerTestsView : UIView
|
|
@end
|
|
|
|
@implementation ShadowLayerTestsView
|
|
|
|
+ (Class)layerClass {
|
|
return [MDCShadowLayer class];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface ShadowLayerTests : XCTestCase
|
|
@end
|
|
|
|
@implementation ShadowLayerTests
|
|
|
|
- (void)testDefaultValues {
|
|
// Given
|
|
MDCShadowLayer *shadowLayer = [[MDCShadowLayer alloc] init];
|
|
|
|
// Then
|
|
XCTAssertEqualWithAccuracy(shadowLayer.elevation, 0, 0.0001);
|
|
XCTAssertTrue(shadowLayer.isShadowMaskEnabled);
|
|
}
|
|
|
|
- (void)testShadowLayerBackedViewShadowPathAnimationPiggyBacksUIKitFrameAnimation {
|
|
// Given
|
|
UIView *someView = [[ShadowLayerTestsView alloc] init];
|
|
|
|
// When
|
|
[UIView animateWithDuration:0.1
|
|
animations:^{
|
|
[CATransaction begin];
|
|
[CATransaction setAnimationDuration:0.5];
|
|
someView.frame = CGRectMake(0, 0, 100, 50);
|
|
someView.layer.shadowPath =
|
|
[UIBezierPath bezierPathWithRect:someView.bounds].CGPath;
|
|
[CATransaction commit];
|
|
}];
|
|
|
|
// Then
|
|
XCTAssertNotNil([someView.layer animationForKey:@"position"]);
|
|
XCTAssertNotNil([someView.layer animationForKey:@"bounds.origin"]);
|
|
XCTAssertNotNil([someView.layer animationForKey:@"bounds.size"]);
|
|
CFTimeInterval boundsDuration = [someView.layer animationForKey:@"bounds.origin"].duration;
|
|
for (CALayer *sublayer in someView.layer.sublayers) {
|
|
CAAnimation *animation = [sublayer animationForKey:@"shadowPath"];
|
|
XCTAssertNotNil(animation);
|
|
XCTAssertEqualWithAccuracy(animation.duration, boundsDuration, 0.001);
|
|
}
|
|
}
|
|
|
|
- (void)testShadowLayerBackedViewShadowPathAnimationPiggyBacksUIKitBoundsAnimation {
|
|
// Given
|
|
UIView *someView = [[ShadowLayerTestsView alloc] init];
|
|
|
|
// When
|
|
[UIView animateWithDuration:0.1
|
|
animations:^{
|
|
[CATransaction begin];
|
|
[CATransaction setAnimationDuration:0.5];
|
|
someView.bounds = CGRectMake(0, 0, 100, 50);
|
|
someView.layer.shadowPath =
|
|
[UIBezierPath bezierPathWithRect:someView.bounds].CGPath;
|
|
[CATransaction commit];
|
|
}];
|
|
|
|
// Then
|
|
XCTAssertNotNil([someView.layer animationForKey:@"bounds.origin"]);
|
|
XCTAssertNotNil([someView.layer animationForKey:@"bounds.size"]);
|
|
CFTimeInterval boundsDuration = [someView.layer animationForKey:@"bounds.origin"].duration;
|
|
for (CALayer *sublayer in someView.layer.sublayers) {
|
|
CAAnimation *animation = [sublayer animationForKey:@"shadowPath"];
|
|
XCTAssertNotNil(animation);
|
|
XCTAssertEqualWithAccuracy(animation.duration, boundsDuration, 0.001);
|
|
}
|
|
}
|
|
|
|
@end
|