blob: 9c688682a8ede1d1fba7483b465f1270c1c59c96 [file] [log] [blame]
// Copyright 2015 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/web/web_state/ui/crw_web_controller_container_view.h"
#include "base/logging.h"
#import "ios/web/public/web_state/ui/crw_content_view.h"
#import "ios/web/public/web_state/ui/crw_native_content.h"
#import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
#import "ios/web/web_state/ui/crw_web_view_proxy_impl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#pragma mark - CRWToolbarContainerView
// Class that manages the display of toolbars.
@interface CRWToolbarContainerView : UIView {
// Backing object for |self.toolbars|.
NSMutableArray* _toolbars;
}
// The toolbars currently managed by this view.
@property(nonatomic, strong, readonly) NSMutableArray* toolbars;
// Adds |toolbar| as a subview and bottom aligns to any previously added
// toolbars.
- (void)addToolbar:(UIView*)toolbar;
// Removes |toolbar| from the container view.
- (void)removeToolbar:(UIView*)toolbar;
@end
@implementation CRWToolbarContainerView
#pragma mark Accessors
- (NSMutableArray*)toolbars {
if (!_toolbars)
_toolbars = [[NSMutableArray alloc] init];
return _toolbars;
}
#pragma mark Layout
- (void)layoutSubviews {
[super layoutSubviews];
// Bottom-align the toolbars.
CGPoint toolbarOrigin =
CGPointMake(self.bounds.origin.x, CGRectGetMaxY(self.bounds));
for (UIView* toolbar in self.toolbars) {
CGSize toolbarSize = [toolbar sizeThatFits:self.bounds.size];
toolbarSize.width = self.bounds.size.width;
toolbarOrigin.y -= toolbarSize.height;
toolbar.frame = CGRectMake(toolbarOrigin.x, toolbarOrigin.y,
toolbarSize.width, toolbarSize.height);
}
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize boundingRectSize = CGSizeMake(size.width, CGFLOAT_MAX);
CGFloat necessaryHeight = 0.0f;
for (UIView* toolbar in self.toolbars)
necessaryHeight += [toolbar sizeThatFits:boundingRectSize].height;
return CGSizeMake(size.width, necessaryHeight);
}
#pragma mark Toolbars
- (void)addToolbar:(UIView*)toolbar {
DCHECK(toolbar);
DCHECK(![self.toolbars containsObject:toolbar]);
toolbar.translatesAutoresizingMaskIntoConstraints = NO;
[self.toolbars addObject:toolbar];
[self addSubview:toolbar];
}
- (void)removeToolbar:(UIView*)toolbar {
DCHECK(toolbar);
DCHECK([self.toolbars containsObject:toolbar]);
[self.toolbars removeObject:toolbar];
[toolbar removeFromSuperview];
}
@end
#pragma mark - CRWWebControllerContainerView
@interface CRWWebControllerContainerView () {
}
// Redefine properties as readwrite.
@property(nonatomic, strong, readwrite)
CRWWebViewContentView* webViewContentView;
@property(nonatomic, strong, readwrite) id<CRWNativeContent> nativeController;
@property(nonatomic, strong, readwrite) CRWContentView* transientContentView;
// Container view that displays any added toolbars. It is always the top-most
// subview, and is bottom aligned with the CRWWebControllerContainerView.
@property(nonatomic, strong, readonly)
CRWToolbarContainerView* toolbarContainerView;
// Convenience getter for the proxy object.
@property(nonatomic, weak, readonly) CRWWebViewProxyImpl* contentViewProxy;
// Returns |self.bounds| after being inset at the top by the header height
// returned by the delegate. This is only used to lay out native controllers,
// as the header height is already accounted for in the scroll view content
// insets for other CRWContentViews.
@property(nonatomic, readonly) CGRect visibleFrame;
@end
@implementation CRWWebControllerContainerView
@synthesize webViewContentView = _webViewContentView;
@synthesize nativeController = _nativeController;
@synthesize transientContentView = _transientContentView;
@synthesize toolbarContainerView = _toolbarContainerView;
@synthesize delegate = _delegate;
- (instancetype)initWithDelegate:
(id<CRWWebControllerContainerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
DCHECK(delegate);
_delegate = delegate;
self.backgroundColor = [UIColor whiteColor];
self.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder*)decoder {
NOTREACHED();
return nil;
}
- (instancetype)initWithFrame:(CGRect)frame {
NOTREACHED();
return nil;
}
- (void)dealloc {
self.contentViewProxy.contentView = nil;
}
#pragma mark Accessors
- (void)setWebViewContentView:(CRWWebViewContentView*)webViewContentView {
if (![_webViewContentView isEqual:webViewContentView]) {
[_webViewContentView removeFromSuperview];
_webViewContentView = webViewContentView;
[_webViewContentView setFrame:self.bounds];
[self addSubview:_webViewContentView];
}
}
- (void)setNativeController:(id<CRWNativeContent>)nativeController {
if (![_nativeController isEqual:nativeController]) {
__weak id oldController = _nativeController;
if ([oldController respondsToSelector:@selector(willBeDismissed)]) {
[oldController willBeDismissed];
}
[[oldController view] removeFromSuperview];
_nativeController = nativeController;
// TODO(crbug.com/503297): Re-enable this DCHECK once native controller
// leaks are fixed.
// DCHECK(!oldController);
}
}
- (void)setTransientContentView:(CRWContentView*)transientContentView {
if (![_transientContentView isEqual:transientContentView]) {
[_transientContentView removeFromSuperview];
_transientContentView = transientContentView;
}
}
- (void)setToolbarContainerView:(CRWToolbarContainerView*)toolbarContainerView {
if (![_toolbarContainerView isEqual:toolbarContainerView]) {
[_toolbarContainerView removeFromSuperview];
_toolbarContainerView = toolbarContainerView;
}
}
- (CRWWebViewProxyImpl*)contentViewProxy {
return [_delegate contentViewProxyForContainerView:self];
}
- (CGRect)visibleFrame {
CGFloat headerHeight = [_delegate headerHeightForContainerView:self];
return UIEdgeInsetsInsetRect(self.bounds,
UIEdgeInsetsMake(headerHeight, 0, 0, 0));
}
#pragma mark Layout
- (void)layoutSubviews {
[super layoutSubviews];
self.webViewContentView.frame = self.bounds;
// TODO(crbug.com/570114): Move adding of the following subviews to another
// place.
// nativeController layout.
if (self.nativeController) {
UIView* nativeView = [self.nativeController view];
if (!nativeView.superview) {
[self addSubview:nativeView];
[nativeView setNeedsUpdateConstraints];
}
nativeView.frame = self.visibleFrame;
}
// transientContentView layout.
if (self.transientContentView) {
if (!self.transientContentView.superview)
[self addSubview:self.transientContentView];
self.transientContentView.frame = self.bounds;
}
// Bottom align the toolbars with the bottom of the container.
if (self.toolbarContainerView) {
if (!self.toolbarContainerView.superview)
[self addSubview:self.toolbarContainerView];
else
[self bringSubviewToFront:self.toolbarContainerView];
CGSize toolbarContainerSize =
[self.toolbarContainerView sizeThatFits:self.bounds.size];
self.toolbarContainerView.frame =
CGRectMake(CGRectGetMinX(self.bounds),
CGRectGetMaxY(self.bounds) - toolbarContainerSize.height,
toolbarContainerSize.width, toolbarContainerSize.height);
}
}
- (BOOL)isViewAlive {
return self.webViewContentView || self.transientContentView ||
[self.nativeController isViewAlive];
}
#pragma mark Content Setters
- (void)resetContent {
self.webViewContentView = nil;
self.nativeController = nil;
self.transientContentView = nil;
[self removeAllToolbars];
self.contentViewProxy.contentView = nil;
}
- (void)displayWebViewContentView:(CRWWebViewContentView*)webViewContentView {
DCHECK(webViewContentView);
self.webViewContentView = webViewContentView;
self.nativeController = nil;
self.transientContentView = nil;
self.contentViewProxy.contentView = self.webViewContentView;
[self setNeedsLayout];
}
- (void)displayNativeContent:(id<CRWNativeContent>)nativeController {
DCHECK(nativeController);
self.webViewContentView = nil;
self.nativeController = nativeController;
self.transientContentView = nil;
self.contentViewProxy.contentView = nil;
[self setNeedsLayout];
}
- (void)displayTransientContent:(CRWContentView*)transientContentView {
DCHECK(transientContentView);
self.transientContentView = transientContentView;
self.contentViewProxy.contentView = self.transientContentView;
[self setNeedsLayout];
}
- (void)clearTransientContentView {
self.transientContentView = nil;
self.contentViewProxy.contentView = self.webViewContentView;
}
#pragma mark Toolbars
- (void)addToolbar:(UIView*)toolbar {
// Create toolbar container if necessary.
if (!self.toolbarContainerView) {
self.toolbarContainerView =
[[CRWToolbarContainerView alloc] initWithFrame:CGRectZero];
}
// Add the toolbar to the container.
[self.toolbarContainerView addToolbar:toolbar];
[self setNeedsLayout];
}
- (void)addToolbars:(NSArray*)toolbars {
DCHECK(toolbars);
for (UIView* toolbar in toolbars)
[self addToolbar:toolbar];
}
- (void)removeToolbar:(UIView*)toolbar {
// Remove the toolbar from the container view.
[self.toolbarContainerView removeToolbar:toolbar];
// Reset the container if there are no more toolbars.
if ([self.toolbarContainerView.toolbars count])
[self setNeedsLayout];
else
self.toolbarContainerView = nil;
}
- (void)removeAllToolbars {
// Resetting the property will remove the toolbars from the hierarchy.
self.toolbarContainerView = nil;
}
@end