Commit da2084a7 authored by Kurt Horimoto's avatar Kurt Horimoto Committed by Commit Bot

[iOS] Create OptionalPropertyAnimator.

This UIViewPropertyAnimator subclass records whether animations have
been added and early returns before attempting to start an animator that
has no animation blocks.  This is necessary because the default
implementations of |-startAnimation| and |-startAnimationAfterDelay:|
throw exceptions if there are no animations.

This CL also updates FullscreenAnimator to be optional, as there is no
guarantee that FullscreenControllerObservers will add animation blocks
to FullscreenAnimators.

Bug: 819623
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I1e73a7163f5bd9b82cf4ff693b290464ecec63cd
Reviewed-on: https://chromium-review.googlesource.com/953793Reviewed-by: default avataredchin <edchin@chromium.org>
Reviewed-by: default avatarKurt Horimoto <kkhorimoto@chromium.org>
Commit-Queue: Kurt Horimoto <kkhorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#541909}
parent ac8c96e7
...@@ -99,6 +99,7 @@ source_set("ui") { ...@@ -99,6 +99,7 @@ source_set("ui") {
deps = [ deps = [
"//base", "//base",
"//ios/chrome/browser/ui/util",
"//ios/chrome/common:timing", "//ios/chrome/common:timing",
"//ui/gfx/geometry", "//ui/gfx/geometry",
] ]
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
#ifndef IOS_CHROME_BROWSER_UI_FULLSCREEN_FULLSCREEN_ANIMATOR_H_ #ifndef IOS_CHROME_BROWSER_UI_FULLSCREEN_FULLSCREEN_ANIMATOR_H_
#define IOS_CHROME_BROWSER_UI_FULLSCREEN_FULLSCREEN_ANIMATOR_H_ #define IOS_CHROME_BROWSER_UI_FULLSCREEN_FULLSCREEN_ANIMATOR_H_
#import <UIKit/UIKit.h> #import "ios/chrome/browser/ui/util/optional_property_animator.h"
// Helper object for animating changes to fullscreen progress. Subclasses of // Helper object for animating changes to fullscreen progress. Subclasses of
// this object are provided to FullscreenControllerObservers to coordinate // this object are provided to FullscreenControllerObservers to coordinate
// animations across several different ojects. // animations across several different ojects.
@interface FullscreenAnimator : UIViewPropertyAnimator @interface FullscreenAnimator : OptionalPropertyAnimator
// The progress value at the start of the animation. // The progress value at the start of the animation.
@property(nonatomic, readonly) CGFloat startProgress; @property(nonatomic, readonly) CGFloat startProgress;
......
...@@ -56,6 +56,11 @@ class FullscreenMediator : public FullscreenModelObserver { ...@@ -56,6 +56,11 @@ class FullscreenMediator : public FullscreenModelObserver {
// completion block. // completion block.
void SetUpAnimator(__strong FullscreenAnimator** animator); void SetUpAnimator(__strong FullscreenAnimator** animator);
// Starts |animator| if it has animations to run. |animator| is expected to
// be a pointer to a data member of this object, and will be reset when
// attempting to start an animator without any animation blocks.
void StartAnimator(__strong FullscreenAnimator** animator);
// Stops the current scroll end animation if one is in progress. If // Stops the current scroll end animation if one is in progress. If
// |update_model| is true, the FullscreenModel will be updated with the active // |update_model| is true, the FullscreenModel will be updated with the active
// animator's current progress value. // animator's current progress value.
...@@ -67,6 +72,10 @@ class FullscreenMediator : public FullscreenModelObserver { ...@@ -67,6 +72,10 @@ class FullscreenMediator : public FullscreenModelObserver {
// animator before deallocation. // animator before deallocation.
void StopAnimator(__strong FullscreenAnimator** animator, bool update_model); void StopAnimator(__strong FullscreenAnimator** animator, bool update_model);
// Checks whether |animator| is a valid pointer to one of the three animator
// data members of this class. No-op for non-debug builds.
void VerifyAnimatorPointer(__strong FullscreenAnimator** animator) const;
// The controller. // The controller.
FullscreenController* controller_ = nullptr; FullscreenController* controller_ = nullptr;
// The model. // The model.
......
...@@ -41,7 +41,7 @@ void FullscreenMediator::ScrollToTop() { ...@@ -41,7 +41,7 @@ void FullscreenMediator::ScrollToTop() {
for (auto& observer : observers_) { for (auto& observer : observers_) {
observer.FullscreenWillScrollToTop(controller_, scroll_to_top_animator_); observer.FullscreenWillScrollToTop(controller_, scroll_to_top_animator_);
} }
[scroll_to_top_animator_ startAnimation]; StartAnimator(&scroll_to_top_animator_);
} }
void FullscreenMediator::WillEnterForeground() { void FullscreenMediator::WillEnterForeground() {
...@@ -53,7 +53,7 @@ void FullscreenMediator::WillEnterForeground() { ...@@ -53,7 +53,7 @@ void FullscreenMediator::WillEnterForeground() {
for (auto& observer : observers_) { for (auto& observer : observers_) {
observer.FullscreenWillEnterForeground(controller_, foreground_animator_); observer.FullscreenWillEnterForeground(controller_, foreground_animator_);
} }
[foreground_animator_ startAnimation]; StartAnimator(&foreground_animator_);
} }
void FullscreenMediator::Disconnect() { void FullscreenMediator::Disconnect() {
...@@ -103,7 +103,7 @@ void FullscreenMediator::FullscreenModelScrollEventEnded( ...@@ -103,7 +103,7 @@ void FullscreenMediator::FullscreenModelScrollEventEnded(
for (auto& observer : observers_) { for (auto& observer : observers_) {
observer.FullscreenScrollEventEnded(controller_, scroll_end_animator_); observer.FullscreenScrollEventEnded(controller_, scroll_end_animator_);
} }
[scroll_end_animator_ startAnimation]; StartAnimator(&scroll_end_animator_);
} }
void FullscreenMediator::FullscreenModelWasReset(FullscreenModel* model) { void FullscreenMediator::FullscreenModelWasReset(FullscreenModel* model) {
...@@ -118,11 +118,7 @@ void FullscreenMediator::FullscreenModelWasReset(FullscreenModel* model) { ...@@ -118,11 +118,7 @@ void FullscreenMediator::FullscreenModelWasReset(FullscreenModel* model) {
} }
void FullscreenMediator::SetUpAnimator(__strong FullscreenAnimator** animator) { void FullscreenMediator::SetUpAnimator(__strong FullscreenAnimator** animator) {
DCHECK(animator); VerifyAnimatorPointer(animator);
DCHECK(*animator);
DCHECK(*animator == scroll_end_animator_ ||
*animator == scroll_to_top_animator_ ||
*animator == foreground_animator_);
[*animator addCompletion:^(UIViewAnimatingPosition finalPosition) { [*animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
DCHECK_EQ(finalPosition, UIViewAnimatingPositionEnd); DCHECK_EQ(finalPosition, UIViewAnimatingPositionEnd);
model_->AnimationEndedWithProgress( model_->AnimationEndedWithProgress(
...@@ -131,6 +127,17 @@ void FullscreenMediator::SetUpAnimator(__strong FullscreenAnimator** animator) { ...@@ -131,6 +127,17 @@ void FullscreenMediator::SetUpAnimator(__strong FullscreenAnimator** animator) {
}]; }];
} }
void FullscreenMediator::StartAnimator(__strong FullscreenAnimator** animator) {
// Start the animator if animations have been added to it, or reset the ivar
// otherwise.
VerifyAnimatorPointer(animator);
if ((*animator).hasAnimations) {
[*animator startAnimation];
} else {
*animator = nil;
}
}
void FullscreenMediator::StopAnimating(bool update_model) { void FullscreenMediator::StopAnimating(bool update_model) {
if (!scroll_end_animator_ && !scroll_to_top_animator_ && if (!scroll_end_animator_ && !scroll_to_top_animator_ &&
!foreground_animator_) { !foreground_animator_) {
...@@ -152,14 +159,19 @@ void FullscreenMediator::StopAnimating(bool update_model) { ...@@ -152,14 +159,19 @@ void FullscreenMediator::StopAnimating(bool update_model) {
void FullscreenMediator::StopAnimator(__strong FullscreenAnimator** animator, void FullscreenMediator::StopAnimator(__strong FullscreenAnimator** animator,
bool update_model) { bool update_model) {
DCHECK(animator); VerifyAnimatorPointer(animator);
DCHECK(*animator);
DCHECK(*animator == scroll_end_animator_ ||
*animator == scroll_to_top_animator_ ||
*animator == foreground_animator_);
DCHECK_EQ((*animator).state, UIViewAnimatingStateActive); DCHECK_EQ((*animator).state, UIViewAnimatingStateActive);
if (update_model) if (update_model)
model_->AnimationEndedWithProgress((*animator).currentProgress); model_->AnimationEndedWithProgress((*animator).currentProgress);
[*animator stopAnimation:YES]; [*animator stopAnimation:YES];
*animator = nil; *animator = nil;
} }
void FullscreenMediator::VerifyAnimatorPointer(
__strong FullscreenAnimator** animator) const {
DCHECK(animator);
DCHECK(*animator);
DCHECK(*animator == scroll_end_animator_ ||
*animator == scroll_to_top_animator_ ||
*animator == foreground_animator_);
}
...@@ -25,6 +25,8 @@ source_set("util") { ...@@ -25,6 +25,8 @@ source_set("util") {
"named_guide.mm", "named_guide.mm",
"named_guide_util.h", "named_guide_util.h",
"named_guide_util.mm", "named_guide_util.mm",
"optional_property_animator.h",
"optional_property_animator.mm",
"pasteboard_util.h", "pasteboard_util.h",
"pasteboard_util.mm", "pasteboard_util.mm",
"relaxed_bounds_constraints_hittest.h", "relaxed_bounds_constraints_hittest.h",
...@@ -63,6 +65,7 @@ source_set("unit_tests") { ...@@ -63,6 +65,7 @@ source_set("unit_tests") {
"label_observer_unittest.mm", "label_observer_unittest.mm",
"manual_text_framer_unittest.mm", "manual_text_framer_unittest.mm",
"named_guide_unittest.mm", "named_guide_unittest.mm",
"optional_property_animator_unittest.mm",
"text_region_mapper_unittest.mm", "text_region_mapper_unittest.mm",
] ]
deps = [ deps = [
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_UTIL_OPTIONAL_PROPERTY_ANIMATOR_H_
#define IOS_CHROME_BROWSER_UI_UTIL_OPTIONAL_PROPERTY_ANIMATOR_H_
#import <UIKit/UIKit.h>
// UIViewPropertyAnimators throw exceptions if they're started before any
// animation blocks were added. OptionalPropertyAnimators can be used in
// scenarios when it is not guaranteed that animation blocks will be added (i.e.
// animation blocks provided by observers).
@interface OptionalPropertyAnimator : UIViewPropertyAnimator
// Whether animations have been added to this animator. |-startAnimation| and
// |-startAnimationAfterDelay:| are no-ops if this property is NO.
@property(nonatomic, readonly) BOOL hasAnimations;
@end
#endif // IOS_CHROME_BROWSER_UI_UTIL_OPTIONAL_PROPERTY_ANIMATOR_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/util/optional_property_animator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface OptionalPropertyAnimator ()
// Redefine property as readwrite.
@property(nonatomic, readwrite) BOOL hasAnimations;
@end
@implementation OptionalPropertyAnimator
@synthesize hasAnimations = _hasAnimations;
#pragma mark - UIViewPropertyAnimator
- (instancetype)initWithDuration:(NSTimeInterval)duration
curve:(UIViewAnimationCurve)curve
animations:(void (^__nullable)(void))animations {
if (self =
[super initWithDuration:duration curve:curve animations:animations]) {
_hasAnimations = !!animations;
}
return self;
}
- (instancetype)initWithDuration:(NSTimeInterval)duration
controlPoint1:(CGPoint)point1
controlPoint2:(CGPoint)point2
animations:(void (^__nullable)(void))animations {
if (self = [super initWithDuration:duration
controlPoint1:point1
controlPoint2:point2
animations:animations]) {
_hasAnimations = !!animations;
}
return self;
}
- (instancetype)initWithDuration:(NSTimeInterval)duration
dampingRatio:(CGFloat)ratio
animations:(void (^__nullable)(void))animations {
if (self = [super initWithDuration:duration
dampingRatio:ratio
animations:animations]) {
_hasAnimations = !!animations;
}
return self;
}
#pragma mark - UIViewImplicitlyAnimating
- (void)addAnimations:(void (^)(void))animation
delayFactor:(CGFloat)delayFactor {
if (animation)
self.hasAnimations = YES;
[super addAnimations:animation delayFactor:delayFactor];
}
- (void)addAnimations:(void (^)(void))animation {
if (animation)
self.hasAnimations = YES;
[super addAnimations:animation];
}
#pragma mark - UIViewAnimating
- (void)startAnimation {
if (!self.hasAnimations)
return;
[super startAnimation];
}
- (void)startAnimationAfterDelay:(NSTimeInterval)delay {
if (!self.hasAnimations)
return;
[super startAnimationAfterDelay:delay];
}
@end
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/util/optional_property_animator.h"
#import "base/ios/block_types.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Duration to use for test animators.
const NSTimeInterval kDuration = 0.1;
}
// Test fixture for OptionalPropertyAnimator.
using OptionalPropertyAnimatorTest = PlatformTest;
// Tests that the animator's |hasAnimations| property is NO before adding an
// animation.
TEST_F(OptionalPropertyAnimatorTest, NoAnimations) {
id<UITimingCurveProvider> curve = [[UICubicTimingParameters alloc]
initWithAnimationCurve:UIViewAnimationCurveEaseOut];
OptionalPropertyAnimator* animator =
[[OptionalPropertyAnimator alloc] initWithDuration:kDuration
timingParameters:curve];
EXPECT_FALSE(animator.hasAnimations);
}
// Tests that animations added in initializers are correctly reflected in
// |hasAnimations|
TEST_F(OptionalPropertyAnimatorTest, InitializersWithAnimations) {
ProceduralBlock empty_animation = ^{
};
OptionalPropertyAnimator* animator = [[OptionalPropertyAnimator alloc]
initWithDuration:kDuration
curve:UIViewAnimationCurveEaseOut
animations:empty_animation];
EXPECT_TRUE(animator.hasAnimations);
animator =
[[OptionalPropertyAnimator alloc] initWithDuration:kDuration
controlPoint1:CGPointZero
controlPoint2:CGPointZero
animations:empty_animation];
EXPECT_TRUE(animator.hasAnimations);
animator =
[[OptionalPropertyAnimator alloc] initWithDuration:kDuration
dampingRatio:0.0
animations:empty_animation];
EXPECT_TRUE(animator.hasAnimations);
animator = [OptionalPropertyAnimator
runningPropertyAnimatorWithDuration:0.0
delay:0.0
options:0
animations:empty_animation
completion:nil];
EXPECT_TRUE(animator.hasAnimations);
}
// Tests that starting an animator with no animations is a no-op.
TEST_F(OptionalPropertyAnimatorTest, NoOpStart) {
OptionalPropertyAnimator* animator = [[OptionalPropertyAnimator alloc]
initWithDuration:kDuration
curve:UIViewAnimationCurveEaseOut
animations:nil];
ASSERT_FALSE(animator.hasAnimations);
ASSERT_EQ(animator.state, UIViewAnimatingStateInactive);
ASSERT_FALSE(animator.running);
// Attempt to start the animator and verify that the state hasn't changed.
[animator startAnimation];
EXPECT_EQ(animator.state, UIViewAnimatingStateInactive);
EXPECT_FALSE(animator.running);
// Also attempt to start with a delay and ensure that state has still not
// changed.
[animator startAnimationAfterDelay:0.0];
EXPECT_EQ(animator.state, UIViewAnimatingStateInactive);
EXPECT_FALSE(animator.running);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment