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

[iOS] Creates ToolbarContainerCoordinator and its view controller.

This coordinator manages a stack of optionally collapsible toolbars.
It manages laying out these toolbars in a top-to-bottom or bottom-to-
top order and interpolating the toolbars' heights based on the
fullscreen progress.

Ultimately, toolbar layout management will be moved from BVC to this
coordinator.

Bug: 880672
Cq-Include-Trybots: luci.chromium.try:ios-simulator-cronet;luci.chromium.try:ios-simulator-full-configs
Change-Id: I2b143895d30ce1ec68f58141d7364f1d32ef3f39
Reviewed-on: https://chromium-review.googlesource.com/c/1220722
Commit-Queue: Kurt Horimoto <kkhorimoto@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599790}
parent 964a0180
...@@ -405,6 +405,7 @@ source_set("ui_internal") { ...@@ -405,6 +405,7 @@ source_set("ui_internal") {
"//ios/chrome/browser/ui/toolbar/fullscreen:fullscreen_broadcasting_util", "//ios/chrome/browser/ui/toolbar/fullscreen:fullscreen_broadcasting_util",
"//ios/chrome/browser/ui/toolbar/public", "//ios/chrome/browser/ui/toolbar/public",
"//ios/chrome/browser/ui/toolbar/public:feature_flags", "//ios/chrome/browser/ui/toolbar/public:feature_flags",
"//ios/chrome/browser/ui/toolbar_container",
"//ios/chrome/browser/ui/toolbar_container:feature_flags", "//ios/chrome/browser/ui/toolbar_container:feature_flags",
"//ios/chrome/browser/ui/translate", "//ios/chrome/browser/ui/translate",
"//ios/chrome/browser/ui/util", "//ios/chrome/browser/ui/util",
......
...@@ -202,6 +202,8 @@ ...@@ -202,6 +202,8 @@
#import "ios/chrome/browser/ui/toolbar/public/primary_toolbar_coordinator.h" #import "ios/chrome/browser/ui/toolbar/public/primary_toolbar_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/secondary_toolbar_coordinator.h" #import "ios/chrome/browser/ui/toolbar/secondary_toolbar_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/toolbar_coordinator_adaptor.h" #import "ios/chrome/browser/ui/toolbar/toolbar_coordinator_adaptor.h"
#import "ios/chrome/browser/ui/toolbar_container/toolbar_container_coordinator.h"
#import "ios/chrome/browser/ui/toolbar_container/toolbar_container_features.h"
#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h" #import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
#include "ios/chrome/browser/ui/ui_feature_flags.h" #include "ios/chrome/browser/ui/ui_feature_flags.h"
#include "ios/chrome/browser/ui/ui_util.h" #include "ios/chrome/browser/ui/ui_util.h"
...@@ -351,10 +353,10 @@ NSString* const kBrowserViewControllerSnackbarCategory = ...@@ -351,10 +353,10 @@ NSString* const kBrowserViewControllerSnackbarCategory =
// TODO(crbug.com/880672): This is a temporary solution. This logic should be // TODO(crbug.com/880672): This is a temporary solution. This logic should be
// handled by ToolbarContainerViewController. // handled by ToolbarContainerViewController.
@interface ToolbarContainerView : UIView @interface LegacyToolbarContainerView : UIView
@end @end
@implementation ToolbarContainerView @implementation LegacyToolbarContainerView
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
// Don't receive events that don't occur within a subview. This is necessary // Don't receive events that don't occur within a subview. This is necessary
...@@ -674,6 +676,9 @@ NSString* const kBrowserViewControllerSnackbarCategory = ...@@ -674,6 +676,9 @@ NSString* const kBrowserViewControllerSnackbarCategory =
// The container view for the secondary toolbar. // The container view for the secondary toolbar.
// TODO(crbug.com/880656): Convert to a container coordinator. // TODO(crbug.com/880656): Convert to a container coordinator.
@property(nonatomic, strong) UIView* secondaryToolbarContainerView; @property(nonatomic, strong) UIView* secondaryToolbarContainerView;
// Coordinator used to manage the secondary toolbar view.
@property(nonatomic, strong)
ToolbarContainerCoordinator* secondaryToolbarContainerCoordinator;
// Interface object with the toolbars. // Interface object with the toolbars.
@property(nonatomic, strong) id<ToolbarCoordinating> toolbarInterface; @property(nonatomic, strong) id<ToolbarCoordinating> toolbarInterface;
...@@ -883,6 +888,8 @@ NSString* const kBrowserViewControllerSnackbarCategory = ...@@ -883,6 +888,8 @@ NSString* const kBrowserViewControllerSnackbarCategory =
@synthesize primaryToolbarCoordinator = _primaryToolbarCoordinator; @synthesize primaryToolbarCoordinator = _primaryToolbarCoordinator;
@synthesize secondaryToolbarCoordinator = _secondaryToolbarCoordinator; @synthesize secondaryToolbarCoordinator = _secondaryToolbarCoordinator;
@synthesize secondaryToolbarContainerView = _secondaryToolbarContainerView; @synthesize secondaryToolbarContainerView = _secondaryToolbarContainerView;
@synthesize secondaryToolbarContainerCoordinator =
_secondaryToolbarContainerCoordinator;
@synthesize primaryToolbarOffsetConstraint = _primaryToolbarOffsetConstraint; @synthesize primaryToolbarOffsetConstraint = _primaryToolbarOffsetConstraint;
@synthesize primaryToolbarHeightConstraint = _primaryToolbarHeightConstraint; @synthesize primaryToolbarHeightConstraint = _primaryToolbarHeightConstraint;
@synthesize secondaryToolbarHeightConstraint = @synthesize secondaryToolbarHeightConstraint =
...@@ -2418,7 +2425,7 @@ applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint { ...@@ -2418,7 +2425,7 @@ applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
if (self.secondaryToolbarCoordinator) { if (self.secondaryToolbarCoordinator) {
// Create the container view for the secondary toolbar and add it to the // Create the container view for the secondary toolbar and add it to the
// hierarchy // hierarchy
UIView* container = [[ToolbarContainerView alloc] init]; UIView* container = [[LegacyToolbarContainerView alloc] init];
container.translatesAutoresizingMaskIntoConstraints = NO; container.translatesAutoresizingMaskIntoConstraints = NO;
[container [container
addSubview:self.secondaryToolbarCoordinator.viewController.view]; addSubview:self.secondaryToolbarCoordinator.viewController.view];
......
...@@ -2,6 +2,22 @@ ...@@ -2,6 +2,22 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
source_set("toolbar_container") {
sources = [
"toolbar_container_coordinator.h",
"toolbar_container_coordinator.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
":ui",
"//base",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/browser/ui/fullscreen",
]
}
source_set("feature_flags") { source_set("feature_flags") {
sources = [ sources = [
"toolbar_container_features.h", "toolbar_container_features.h",
...@@ -14,3 +30,42 @@ source_set("feature_flags") { ...@@ -14,3 +30,42 @@ source_set("feature_flags") {
"//base", "//base",
] ]
} }
source_set("ui") {
sources = [
"collapsing_toolbar_height_constraint.h",
"collapsing_toolbar_height_constraint.mm",
"toolbar_collapsing.h",
"toolbar_container_view.h",
"toolbar_container_view.mm",
"toolbar_container_view_controller.h",
"toolbar_container_view_controller.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
"//base",
"//ios/chrome/browser/ui:ui_util",
"//ios/chrome/browser/ui/fullscreen:ui",
"//ios/chrome/common/ui_util",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"collapsing_toolbar_height_constraint_unittest.mm",
"toolbar_container_view_controller_unittest.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
":ui",
"//base/test:test_support",
"//ios/chrome/browser/ui:ui_util",
"//ios/chrome/common/ui_util",
"//testing/gtest",
]
}
// 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_TOOLBAR_CONTAINER_COLLAPSING_TOOLBAR_HEIGHT_CONSTRAINT_H_
#define IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_COLLAPSING_TOOLBAR_HEIGHT_CONSTRAINT_H_
#import <UIKit/UIKit.h>
// A constraint that scales between a collapsed and expanded height value.
@interface CollapsingToolbarHeightConstraint : NSLayoutConstraint
// Returns a constraint that manages the height of |view|. If |view|
// conforms to the ToolbarCollapsing protocol, the collapsed and expanded
// heights are set using those return values. Otherwise, the intrinsic height
// is used as both the collapsed and expanded height.
+ (nullable instancetype)constraintWithView:(nonnull UIView*)view;
// The collapsed and expanded toolbar heights.
@property(nonatomic, readonly) CGFloat collapsedHeight;
@property(nonatomic, readonly) CGFloat expandedHeight;
// Used to add additional height to the toolbar.
@property(nonatomic, assign) CGFloat additionalHeight;
// Whether the additional height should be collapsed. When set to YES, the
// view's height ranges from |collapsedHeight| to |expandedHeight| +
// |additionalHeight|. When set to NO, the view's height ranges from
// |additionalHeight| + |collapsedHeight| to |additionalHeight| +
// |expandedHeight|.
@property(nonatomic, assign) BOOL collapsesAdditionalHeight;
// The interpolation progress within the height range to use for the
// constraint's constant.
@property(nonatomic, assign) CGFloat progress;
// Returns the height of the toolbar at |progress|
- (CGFloat)toolbarHeightForProgress:(CGFloat)progress;
@end
#endif // IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_COLLAPSING_TOOLBAR_HEIGHT_CONSTRAINT_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/toolbar_container/collapsing_toolbar_height_constraint.h"
#include <algorithm>
#include "base/logging.h"
#include "base/numerics/ranges.h"
#import "ios/chrome/browser/ui/toolbar_container/toolbar_collapsing.h"
#include "ios/chrome/browser/ui/ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// The progress range.
const CGFloat kMinProgress = 0.0;
const CGFloat kMaxProgress = 1.0;
} // namespace
@interface CollapsingToolbarHeightConstraint ()
// Redefine as readwrite.
@property(nonatomic, readwrite) CGFloat collapsedHeight;
@property(nonatomic, readwrite) CGFloat expandedHeight;
// The collapsing toolbar whose height range is being observed.
@property(nonatomic, weak) UIView<ToolbarCollapsing>* collapsingToolbar;
@end
@implementation CollapsingToolbarHeightConstraint
@synthesize collapsedHeight = _collapsedHeight;
@synthesize expandedHeight = _expandedHeight;
@synthesize additionalHeight = _additionalHeight;
@synthesize collapsesAdditionalHeight = _collapsesAdditionalHeight;
@synthesize progress = _progress;
@synthesize collapsingToolbar = _collapsingToolbar;
+ (instancetype)constraintWithView:(UIView*)view {
DCHECK(view);
CollapsingToolbarHeightConstraint* constraint =
[[self class] constraintWithItem:view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:0.0
constant:0.0];
if ([view conformsToProtocol:@protocol(ToolbarCollapsing)]) {
constraint.collapsingToolbar =
static_cast<UIView<ToolbarCollapsing>*>(view);
} else {
CGFloat intrinsicHeight = view.intrinsicContentSize.height;
constraint.collapsedHeight = intrinsicHeight;
constraint.expandedHeight = intrinsicHeight;
}
constraint.progress = 1.0;
[constraint updateHeight];
return constraint;
}
#pragma mark - Accessors
- (void)setActive:(BOOL)active {
[super setActive:active];
if (self.active)
[self startObservingCollapsingToolbar];
else
[self stopObservingCollapsingToolbar];
}
- (void)setCollapsedHeight:(CGFloat)collapsedHeight {
if (AreCGFloatsEqual(_collapsedHeight, collapsedHeight))
return;
_collapsedHeight = collapsedHeight;
[self updateHeight];
}
- (void)setExpandedHeight:(CGFloat)expandedHeight {
if (AreCGFloatsEqual(_expandedHeight, expandedHeight))
return;
_expandedHeight = expandedHeight;
[self updateHeight];
}
- (void)setAdditionalHeight:(CGFloat)additionalHeight {
if (AreCGFloatsEqual(_additionalHeight, additionalHeight))
return;
_additionalHeight = additionalHeight;
[self updateHeight];
}
- (void)setCollapsesAdditionalHeight:(BOOL)collapsesAdditionalHeight {
if (_collapsesAdditionalHeight == collapsesAdditionalHeight)
return;
_collapsesAdditionalHeight = collapsesAdditionalHeight;
[self updateHeight];
}
- (void)setProgress:(CGFloat)progress {
progress = base::ClampToRange(progress, kMinProgress, kMaxProgress);
if (AreCGFloatsEqual(_progress, progress))
return;
_progress = progress;
[self updateHeight];
}
- (void)setCollapsingToolbar:(UIView<ToolbarCollapsing>*)collapsingToolbar {
if (_collapsingToolbar == collapsingToolbar)
return;
[self stopObservingCollapsingToolbar];
_collapsingToolbar = collapsingToolbar;
[self updateToolbarHeightRange];
if (self.active)
[self startObservingCollapsingToolbar];
}
#pragma mark - Public
- (CGFloat)toolbarHeightForProgress:(CGFloat)progress {
progress = base::ClampToRange(progress, kMinProgress, kMaxProgress);
CGFloat base = self.collapsedHeight;
CGFloat range = self.expandedHeight - self.collapsedHeight;
if (self.collapsesAdditionalHeight) {
range += self.additionalHeight;
} else {
base += self.additionalHeight;
}
return base + progress * range;
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString*)key
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
[self updateToolbarHeightRange];
}
#pragma mark - KVO Helpers
- (NSArray<NSString*>* const)collapsingToolbarKeyPaths {
static NSArray<NSString*>* const kKeyPaths =
@[ @"expandedToolbarHeight", @"collapsedToolbarHeight" ];
return kKeyPaths;
}
- (void)startObservingCollapsingToolbar {
for (NSString* keyPath in [self collapsingToolbarKeyPaths]) {
[self.collapsingToolbar addObserver:self
forKeyPath:keyPath
options:NSKeyValueObservingOptionNew
context:nullptr];
}
}
- (void)stopObservingCollapsingToolbar {
for (NSString* keyPath in [self collapsingToolbarKeyPaths]) {
[self.collapsingToolbar removeObserver:self forKeyPath:keyPath];
}
}
#pragma mark - Private
// Updates the constraint using the collapsing toolbar's height range.
- (void)updateToolbarHeightRange {
self.collapsedHeight = self.collapsingToolbar.collapsedToolbarHeight;
self.expandedHeight = self.collapsingToolbar.expandedToolbarHeight;
}
// Updates the constraint's constant
- (void)updateHeight {
self.constant = [self toolbarHeightForProgress:self.progress];
}
@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/toolbar_container/collapsing_toolbar_height_constraint.h"
#import "ios/chrome/browser/ui/toolbar_container/toolbar_collapsing.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// A view with a settable intrinsic height.
@interface IntrinsicHeightView : UIView
@property(nonatomic, assign) CGFloat intrinsicHeight;
@end
@implementation IntrinsicHeightView
@synthesize intrinsicHeight = _intrinsicHeight;
- (CGSize)intrinsicContentSize {
return CGSizeMake(UIViewNoIntrinsicMetric, _intrinsicHeight);
}
@end
// A view with a settable expanded and collapsed height.
@interface CollapsingView : UIView<ToolbarCollapsing>
@property(nonatomic, assign, readwrite) CGFloat expandedToolbarHeight;
@property(nonatomic, assign, readwrite) CGFloat collapsedToolbarHeight;
@end
@implementation CollapsingView
@synthesize expandedToolbarHeight = _expandedToolbarHeight;
@synthesize collapsedToolbarHeight = _collapsedToolbarHeight;
@end
// Test fixture for CollapsingToolbarHeightConstraint.
class CollapsingToolbarHeightConstraintTest : public PlatformTest {
public:
CollapsingToolbarHeightConstraintTest()
: PlatformTest(),
container_(
[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 1000.0)]),
constraints_([[NSMutableArray alloc] init]) {}
~CollapsingToolbarHeightConstraintTest() override {
[NSLayoutConstraint deactivateConstraints:constraints_];
}
// Sets the progress on |constraint| and forces a layout so the changes take
// effect.
void SetProgress(CollapsingToolbarHeightConstraint* constraint,
CGFloat progress) {
constraint.progress = progress;
[container_ setNeedsLayout];
[container_ layoutIfNeeded];
}
// Adds |view| to |container_| using constraints to hug the top, leading, and
// trailing sides. The return value is an activated constraint that can be
// used to update the height.
CollapsingToolbarHeightConstraint* AddViewToContainer(UIView* view) {
view.translatesAutoresizingMaskIntoConstraints = NO;
[container_ addSubview:view];
AddSameConstraintsToSides(
container_, view,
LayoutSides::kLeading | LayoutSides::kTop | LayoutSides::kTrailing);
CollapsingToolbarHeightConstraint* constraint =
[CollapsingToolbarHeightConstraint constraintWithView:view];
constraint.active = YES;
[constraints_ addObject:constraint];
return constraint;
}
private:
UIView* container_ = nil;
NSMutableArray<NSLayoutConstraint*>* constraints_ = nil;
};
// Tests that |-toolbarHeightForProgress:| returns the expected values.
TEST_F(CollapsingToolbarHeightConstraintTest, ToolbarHeightForProgress) {
CollapsingView* view = [[CollapsingView alloc] initWithFrame:CGRectZero];
view.expandedToolbarHeight = 100.0;
view.collapsedToolbarHeight = 50.0;
CollapsingToolbarHeightConstraint* constraint = AddViewToContainer(view);
// Test collapsing toolbar.
EXPECT_EQ([constraint toolbarHeightForProgress:1.0], 100.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.5], 75.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.0], 50.0);
// Tests with collapsing additional height.
constraint.additionalHeight = 100.0;
constraint.collapsesAdditionalHeight = YES;
EXPECT_EQ([constraint toolbarHeightForProgress:1.0], 200.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.5], 125.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.0], 50.0);
// Tests with non-collapsing additional height.
constraint.collapsesAdditionalHeight = NO;
EXPECT_EQ([constraint toolbarHeightForProgress:1.0], 200.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.5], 175.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.0], 150.0);
// Test non-collapsing toolbar.
constraint.additionalHeight = 0.0;
view.collapsedToolbarHeight = 100.0;
EXPECT_EQ([constraint toolbarHeightForProgress:1.0], 100.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.5], 100.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.0], 100.0);
// Tests with collapsing additional height.
constraint.additionalHeight = 100.0;
constraint.collapsesAdditionalHeight = YES;
EXPECT_EQ([constraint toolbarHeightForProgress:1.0], 200.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.5], 150.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.0], 100.0);
// Tests with non-collapsing additional height.
constraint.collapsesAdditionalHeight = NO;
EXPECT_EQ([constraint toolbarHeightForProgress:1.0], 200.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.5], 200.0);
EXPECT_EQ([constraint toolbarHeightForProgress:0.0], 200.0);
}
// Tests interpolating the height value of a collapsing view.
TEST_F(CollapsingToolbarHeightConstraintTest, CollapsingConstraint) {
CollapsingView* view = [[CollapsingView alloc] initWithFrame:CGRectZero];
view.expandedToolbarHeight = 100.0;
view.collapsedToolbarHeight = 50.0;
CollapsingToolbarHeightConstraint* constraint = AddViewToContainer(view);
SetProgress(constraint, 1.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 100.0);
SetProgress(constraint, 0.5);
EXPECT_EQ(CGRectGetHeight(view.bounds), 75.0);
SetProgress(constraint, 0.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 50.0);
}
// Tests interpolating the height value of a non-collapsing view.
TEST_F(CollapsingToolbarHeightConstraintTest, NonCollapsingConstraint) {
IntrinsicHeightView* view =
[[IntrinsicHeightView alloc] initWithFrame:CGRectZero];
view.intrinsicHeight = 80.0;
CollapsingToolbarHeightConstraint* constraint = AddViewToContainer(view);
SetProgress(constraint, 1.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 80.0);
SetProgress(constraint, 0.5);
EXPECT_EQ(CGRectGetHeight(view.bounds), 80.0);
SetProgress(constraint, 0.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 80.0);
}
// Tests a collapsing additional height.
TEST_F(CollapsingToolbarHeightConstraintTest, AdditionalHeight) {
CollapsingView* view = [[CollapsingView alloc] initWithFrame:CGRectZero];
view.expandedToolbarHeight = 100.0;
view.collapsedToolbarHeight = 50.0;
CollapsingToolbarHeightConstraint* constraint = AddViewToContainer(view);
constraint.additionalHeight = 100.0;
constraint.collapsesAdditionalHeight = YES;
SetProgress(constraint, 1.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 200.0);
SetProgress(constraint, 0.5);
EXPECT_EQ(CGRectGetHeight(view.bounds), 125.0);
SetProgress(constraint, 0.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 50.0);
constraint.collapsesAdditionalHeight = NO;
SetProgress(constraint, 1.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 200.0);
SetProgress(constraint, 0.5);
EXPECT_EQ(CGRectGetHeight(view.bounds), 175.0);
SetProgress(constraint, 0.0);
EXPECT_EQ(CGRectGetHeight(view.bounds), 150.0);
}
// 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_TOOLBAR_CONTAINER_TOOLBAR_COLLAPSING_H_
#define IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_COLLAPSING_H_
// Protocol for UI that displays collapsible toolbars.
@protocol ToolbarCollapsing<NSObject>
// The height of the toolber when fully expanded.
@property(nonatomic, readonly) CGFloat expandedToolbarHeight;
// The height of the toolbar when fully collapsed.
@property(nonatomic, readonly) CGFloat collapsedToolbarHeight;
@end
#endif // IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_COLLAPSING_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.
#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_COORDINATOR_H_
#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
@class ToolbarContainerViewController;
// Enum type describing which toolbars will be held by this container.
enum class ToolbarContainerType { kPrimary, kSecondary };
// Coordinator that manages a stack of toolbars.
@interface ToolbarContainerCoordinator : ChromeCoordinator
// Initializes a container with |type| and |browserState|.
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
type:(ToolbarContainerType)type
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
NS_UNAVAILABLE;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browserState:
(ios::ChromeBrowserState*)browserState
NS_UNAVAILABLE;
// The container view controller being managed by this coordinator.
@property(nonatomic, strong, readonly) UIViewController* viewController;
// The toolbar coordinators being managed by this container.
@property(nonatomic, strong) NSArray<ChromeCoordinator*>* toolbarCoordinators;
@end
#endif // IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_COORDINATOR_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/toolbar_container/toolbar_container_coordinator.h"
#include <memory>
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
#import "ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface ToolbarContainerCoordinator () {
// The updater for the container view controller.
std::unique_ptr<FullscreenUIUpdater> _fullscreenUpdater;
}
// The container view controller.
@property(nonatomic, strong)
ToolbarContainerViewController* containerViewController;
// Whether the coordinator's UI has been started.
@property(nonatomic, assign, getter=isStarted) BOOL started;
// The container's type.
@property(nonatomic, assign) ToolbarContainerType type;
@end
@implementation ToolbarContainerCoordinator
@synthesize containerViewController = _containerViewController;
@synthesize toolbarCoordinators = _toolbarCoordinators;
@synthesize type = _type;
@synthesize started = _started;
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
type:(ToolbarContainerType)type {
if (self = [super initWithBaseViewController:nil browserState:browserState]) {
_type = type;
}
return self;
}
#pragma mark - Accessors
- (UIViewController*)viewController {
return self.containerViewController;
}
- (void)setToolbarCoordinators:
(NSArray<ChromeCoordinator*>*)toolbarCoordinators {
if ([_toolbarCoordinators isEqualToArray:toolbarCoordinators])
return;
if (self.started)
[self stopToolbarCoordinators];
_toolbarCoordinators = toolbarCoordinators;
if (self.started)
[self startToolbarCoordinators];
}
#pragma mark - ChromeCoordinator
- (void)start {
if (self.started)
return;
[super start];
// Create the container view controller.
self.containerViewController = [[ToolbarContainerViewController alloc] init];
BOOL isPrimary = self.type == ToolbarContainerType::kPrimary;
self.containerViewController.orientation =
isPrimary ? ToolbarContainerOrientation::kTopToBottom
: ToolbarContainerOrientation::kBottomToTop;
self.containerViewController.collapsesSafeArea = !isPrimary;
[self startToolbarCoordinators];
// Start observing fullscreen events.
_fullscreenUpdater =
std::make_unique<FullscreenUIUpdater>(self.containerViewController);
FullscreenControllerFactory::GetInstance()
->GetForBrowserState(self.browserState)
->AddObserver(_fullscreenUpdater.get());
self.started = YES;
}
- (void)stop {
if (!self.started)
return;
[super stop];
[self.containerViewController willMoveToParentViewController:nil];
[self.containerViewController.view removeFromSuperview];
[self.containerViewController removeFromParentViewController];
self.containerViewController = nil;
[self stopToolbarCoordinators];
FullscreenControllerFactory::GetInstance()
->GetForBrowserState(self.browserState)
->RemoveObserver(_fullscreenUpdater.get());
_fullscreenUpdater = nullptr;
self.started = NO;
}
#pragma mark - Private
// Returns the view controllers associated with the toobar coordinators.
- (NSArray<UIViewController*>*)toolbarViewControllers {
NSMutableArray<UIViewController*>* toolbarViewControllers =
[[NSMutableArray alloc] init];
for (ChromeCoordinator* coordinator in _toolbarCoordinators) {
if ([coordinator respondsToSelector:@selector(viewController)]) {
id toolbarCoordinator = coordinator;
[toolbarViewControllers addObject:[toolbarCoordinator viewController]];
}
}
return toolbarViewControllers;
}
// Starts the toolbar coordinators and adds their view to the container.
- (void)startToolbarCoordinators {
for (ChromeCoordinator* coordinator in _toolbarCoordinators) {
[coordinator start];
}
self.containerViewController.toolbars = [self toolbarViewControllers];
}
// Stops the toolbar coordinators and removes their views from the container.
- (void)stopToolbarCoordinators {
self.containerViewController.toolbars = nil;
for (ChromeCoordinator* coordinator in _toolbarCoordinators) {
[coordinator stop];
}
}
@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.
#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_VIEW_H_
#define IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_VIEW_H_
#import <UIKit/UIKit.h>
// View that holds a stack of toolbars.
@interface ToolbarContainerView : UIView
@end
#endif // IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_VIEW_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/toolbar_container/toolbar_container_view.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation ToolbarContainerView
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
// Don't receive events that don't occur within a subview. This is necessary
// because the container view overlaps with web content and the default
// behavior will intercept touches meant for the web page when the toolbars
// are collapsed.
for (UIView* subview in self.subviews) {
if (CGRectContainsPoint(subview.frame, point))
return [super hitTest:point withEvent:event];
}
return nil;
}
@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.
#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_VIEW_CONTROLLER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h"
// The layout orientation for a toolbar container.
enum class ToolbarContainerOrientation { kTopToBottom, kBottomToTop };
// The view controller that manages a stack of toolbars.
@interface ToolbarContainerViewController
: UIViewController<FullscreenUIElement>
// The orientation of the container.
@property(nonatomic, assign) ToolbarContainerOrientation orientation;
// Whether the container should collapse the toolbars past the edge of the safe
// area.
@property(nonatomic, assign) BOOL collapsesSafeArea;
// The toolbar view controllers being managed by this container.
@property(nonatomic, strong) NSArray<UIViewController*>* toolbars;
// Returns the height of the toolbar views managed by this container at
// |progress|.
- (CGFloat)toolbarStackHeightForFullscreenProgress:(CGFloat)progress;
@end
#endif // IOS_CHROME_BROWSER_UI_TOOLBAR_CONTAINER_TOOLBAR_CONTAINER_VIEW_CONTROLLER_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/toolbar_container/toolbar_container_view_controller.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_animator.h"
#import "ios/chrome/browser/ui/toolbar_container/collapsing_toolbar_height_constraint.h"
#import "ios/chrome/browser/ui/toolbar_container/toolbar_container_view.h"
#include "ios/chrome/browser/ui/ui_util.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface ToolbarContainerViewController ()
// The constraint managing the height of the container.
@property(nonatomic, strong, readonly) NSLayoutConstraint* heightConstraint;
// The height constraints for the toolbar views.
@property(nonatomic, strong, readonly)
NSMutableArray<CollapsingToolbarHeightConstraint*>*
toolbarHeightConstraints;
// Returns the height constraint for the first toolbar in self.toolbars.
@property(nonatomic, readonly)
CollapsingToolbarHeightConstraint* firstToolbarHeightConstraint;
// Additional height to be added to the first toolbar in the stack.
@property(nonatomic, assign) CGFloat additionalStackHeight;
@end
@implementation ToolbarContainerViewController
@synthesize orientation = _orientation;
@synthesize collapsesSafeArea = _collapsesSafeArea;
@synthesize toolbars = _toolbars;
@synthesize heightConstraint = _heightConstraint;
@synthesize toolbarHeightConstraints = _toolbarHeightConstraints;
@synthesize additionalStackHeight = _additionalStackHeight;
#pragma mark - Accessors
- (CollapsingToolbarHeightConstraint*)firstToolbarHeightConstraint {
if (!self.viewLoaded || !self.toolbars.count)
return nil;
DCHECK_EQ(self.toolbarHeightConstraints.count, self.toolbars.count);
DCHECK_EQ(self.toolbarHeightConstraints[0].firstItem, self.toolbars[0].view);
return self.toolbarHeightConstraints[0];
}
- (void)setAdditionalStackHeight:(CGFloat)additionalStackHeight {
if (AreCGFloatsEqual(_additionalStackHeight, additionalStackHeight))
return;
_additionalStackHeight = additionalStackHeight;
self.firstToolbarHeightConstraint.additionalHeight = _additionalStackHeight;
[self updateHeightConstraint];
}
#pragma mark - Public
- (CGFloat)toolbarStackHeightForFullscreenProgress:(CGFloat)progress {
CGFloat height = 0.0;
for (CollapsingToolbarHeightConstraint* constraint in self
.toolbarHeightConstraints) {
height += [constraint toolbarHeightForProgress:progress];
}
return height;
}
#pragma mark - FullscreenUIElement
- (void)updateForFullscreenProgress:(CGFloat)progress {
for (CollapsingToolbarHeightConstraint* heightConstraint in self
.toolbarHeightConstraints) {
heightConstraint.progress = progress;
}
}
- (void)updateForFullscreenEnabled:(BOOL)enabled {
[self updateForFullscreenProgress:1.0];
}
- (void)animateFullscreenWithAnimator:(FullscreenAnimator*)animator {
__weak ToolbarContainerViewController* weakSelf = self;
CGFloat finalProgress = animator.finalProgress;
[animator addAnimations:^{
[weakSelf updateForFullscreenProgress:finalProgress];
[[weakSelf view] setNeedsLayout];
[[weakSelf view] layoutIfNeeded];
}];
}
#pragma mark - ToolbarContainerConsumer
- (void)setOrientation:(ToolbarContainerOrientation)orientation {
if (_orientation == orientation)
return;
_orientation = orientation;
[self setUpToolbarStack];
}
- (void)setCollapsesSafeArea:(BOOL)collapsesSafeArea {
if (_collapsesSafeArea == collapsesSafeArea)
return;
_collapsesSafeArea = collapsesSafeArea;
self.firstToolbarHeightConstraint.collapsesAdditionalHeight = YES;
}
- (void)setToolbars:(NSArray<UIViewController*>*)toolbars {
if ([_toolbars isEqualToArray:toolbars])
return;
[self removeToolbars];
_toolbars = toolbars;
[self setUpToolbarStack];
}
#pragma mark - UIViewController
- (void)loadView {
self.view = [[ToolbarContainerView alloc] init];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.translatesAutoresizingMaskIntoConstraints = NO;
_heightConstraint = [self.view.heightAnchor constraintEqualToConstant:0.0];
_heightConstraint.active = YES;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self setUpToolbarStack];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self removeToolbars];
}
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
[self updateForSafeArea];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self updateForSafeArea];
}
#pragma mark - Layout Helpers
// Sets up the stack of toolbars.
- (void)setUpToolbarStack {
if (!self.viewLoaded)
return;
[self removeToolbars];
for (NSUInteger i = 0; i < self.toolbars.count; ++i) {
[self addToolbarAtIndex:i];
}
[self createToolbarHeightConstraints];
[self updateForSafeArea];
[self updateHeightConstraint];
}
// Removes all the toolbars from the view.
- (void)removeToolbars {
for (UIViewController* toolbar in self.toolbars) {
[toolbar willMoveToParentViewController:nil];
[toolbar.view removeFromSuperview];
[toolbar removeFromParentViewController];
}
[self resetToolbarHeightConstraints];
}
// Adds the toolbar at |index| to the view.
- (void)addToolbarAtIndex:(NSUInteger)index {
DCHECK_LT(index, self.toolbars.count);
UIViewController* toolbar = self.toolbars[index];
if (toolbar.parentViewController == self)
return;
// Add the toolbar and its view controller.
UIView* toolbarView = toolbar.view;
[self addChildViewController:toolbar];
[self.view addSubview:toolbar.view];
toolbarView.translatesAutoresizingMaskIntoConstraints = NO;
[toolbar didMoveToParentViewController:self];
// The toolbars will always be the full width of the container.
AddSameConstraintsToSides(self.view, toolbarView,
LayoutSides::kLeading | LayoutSides::kTrailing);
// Calculate the positioning constraint.
BOOL topToBottom =
self.orientation == ToolbarContainerOrientation::kTopToBottom;
NSLayoutAnchor* toolbarPositioningAnchor =
topToBottom ? toolbarView.topAnchor : toolbarView.bottomAnchor;
NSLayoutAnchor* positioningAnchor = nil;
if (index > 0) {
NSUInteger previousIndex = index - 1;
UIViewController* previousToolbar = self.toolbars[previousIndex];
DCHECK_EQ(previousToolbar.parentViewController, self);
UIView* previousToolbarView = previousToolbar.view;
positioningAnchor = topToBottom ? previousToolbarView.bottomAnchor
: previousToolbarView.topAnchor;
} else {
positioningAnchor =
topToBottom ? self.view.topAnchor : self.view.bottomAnchor;
}
[toolbarPositioningAnchor constraintEqualToAnchor:positioningAnchor].active =
YES;
}
// Deactivates the toolbar height constraints and resets the property.
- (void)resetToolbarHeightConstraints {
if (_toolbarHeightConstraints.count)
[NSLayoutConstraint deactivateConstraints:_toolbarHeightConstraints];
_toolbarHeightConstraints = nil;
}
// Creates and activates height constriants for the toolbars and adds them to
// self.toolbarHeightConstraints at the same index of their corresponding
// toolbar view controller.
- (void)createToolbarHeightConstraints {
[self resetToolbarHeightConstraints];
_toolbarHeightConstraints = [NSMutableArray array];
for (NSUInteger i = 0; i < self.toolbars.count; ++i) {
UIView* toolbarView = self.toolbars[i].view;
CollapsingToolbarHeightConstraint* heightConstraint =
[CollapsingToolbarHeightConstraint constraintWithView:toolbarView];
heightConstraint.active = YES;
// Set up the additional height for the first toolbar.
if (!self.toolbarHeightConstraints.count) {
heightConstraint.additionalHeight = self.additionalStackHeight;
heightConstraint.collapsesAdditionalHeight = self.collapsesSafeArea;
}
[_toolbarHeightConstraints addObject:heightConstraint];
}
}
// Updates the height of the first toolbar to account for the safe area.
- (void)updateForSafeArea {
if (@available(iOS 11, *)) {
if (self.orientation == ToolbarContainerOrientation::kTopToBottom) {
self.additionalStackHeight = self.view.safeAreaInsets.top;
} else {
self.additionalStackHeight = self.view.safeAreaInsets.bottom;
}
} else {
if (self.orientation == ToolbarContainerOrientation::kTopToBottom) {
self.additionalStackHeight = self.topLayoutGuide.length;
} else {
self.additionalStackHeight = self.bottomLayoutGuide.length;
}
}
}
// Updates the height constraint's constant to the cumulative expanded height of
// all the toolbars.
- (void)updateHeightConstraint {
if (!self.viewLoaded)
return;
// Calculate the cumulative expanded toolbar height.
CGFloat cumulativeExpandedHeight = 0.0;
for (CollapsingToolbarHeightConstraint* constraint in self
.toolbarHeightConstraints) {
cumulativeExpandedHeight +=
constraint.expandedHeight + constraint.additionalHeight;
}
self.heightConstraint.constant = cumulativeExpandedHeight;
}
@end
...@@ -237,6 +237,7 @@ test("ios_chrome_unittests") { ...@@ -237,6 +237,7 @@ test("ios_chrome_unittests") {
"//ios/chrome/browser/ui/tabs:unit_tests", "//ios/chrome/browser/ui/tabs:unit_tests",
"//ios/chrome/browser/ui/toolbar:unit_tests", "//ios/chrome/browser/ui/toolbar:unit_tests",
"//ios/chrome/browser/ui/toolbar/fullscreen:unit_tests", "//ios/chrome/browser/ui/toolbar/fullscreen:unit_tests",
"//ios/chrome/browser/ui/toolbar_container:unit_tests",
"//ios/chrome/browser/ui/util:unit_tests", "//ios/chrome/browser/ui/util:unit_tests",
"//ios/chrome/browser/ui/voice:unit_tests", "//ios/chrome/browser/ui/voice:unit_tests",
"//ios/chrome/browser/update_client:unit_tests", "//ios/chrome/browser/update_client:unit_tests",
......
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