1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ios/chrome/browser/ui/toolbar_container/collapsing_toolbar_height_constraint.h" 6 7#include <algorithm> 8 9#include "base/check.h" 10#include "base/numerics/ranges.h" 11#import "ios/chrome/browser/ui/toolbar_container/collapsing_toolbar_height_constraint_delegate.h" 12#import "ios/chrome/browser/ui/toolbar_container/toolbar_collapsing.h" 13#include "ios/chrome/browser/ui/util/ui_util.h" 14 15#if !defined(__has_feature) || !__has_feature(objc_arc) 16#error "This file requires ARC support." 17#endif 18 19namespace { 20// The progress range. 21const CGFloat kMinProgress = 0.0; 22const CGFloat kMaxProgress = 1.0; 23} // namespace 24 25using toolbar_container::HeightRange; 26 27@interface CollapsingToolbarHeightConstraint () { 28 // Backing variable for property of same name. 29 HeightRange _heightRange; 30} 31// The height values extracted from the constrained view. If the view conforms 32// to ToolbarCollapsing, these will be the values from that protocol, and will 33// be updated using KVO if those values change. Otherwise, they will both be 34// equal to the intrinsic height of the view. 35@property(nonatomic, readwrite) CGFloat collapsedToolbarHeight; 36@property(nonatomic, readwrite) CGFloat expandedToolbarHeight; 37// The collapsing toolbar whose height range is being observed. 38@property(nonatomic, weak) UIView<ToolbarCollapsing>* collapsingToolbar; 39@end 40 41@implementation CollapsingToolbarHeightConstraint 42@synthesize collapsedToolbarHeight = _collapsedToolbarHeight; 43@synthesize expandedToolbarHeight = _expandedToolbarHeight; 44@synthesize additionalHeight = _additionalHeight; 45@synthesize collapsesAdditionalHeight = _collapsesAdditionalHeight; 46@synthesize progress = _progress; 47@synthesize delegate = _delegate; 48@synthesize collapsingToolbar = _collapsingToolbar; 49 50+ (instancetype)constraintWithView:(UIView*)view { 51 DCHECK(view); 52 CollapsingToolbarHeightConstraint* constraint = 53 [[self class] constraintWithItem:view 54 attribute:NSLayoutAttributeHeight 55 relatedBy:NSLayoutRelationEqual 56 toItem:nil 57 attribute:NSLayoutAttributeNotAnAttribute 58 multiplier:0.0 59 constant:0.0]; 60 if ([view conformsToProtocol:@protocol(ToolbarCollapsing)]) { 61 constraint.collapsingToolbar = 62 static_cast<UIView<ToolbarCollapsing>*>(view); 63 } else { 64 CGFloat intrinsicHeight = view.intrinsicContentSize.height; 65 constraint.collapsedToolbarHeight = intrinsicHeight; 66 constraint.expandedToolbarHeight = intrinsicHeight; 67 [constraint updateToolbarHeightRange]; 68 } 69 constraint.progress = 1.0; 70 71 return constraint; 72} 73 74#pragma mark - Accessors 75 76- (void)setActive:(BOOL)active { 77 [super setActive:active]; 78 if (self.active) 79 [self startObservingCollapsingToolbar]; 80 else 81 [self stopObservingCollapsingToolbar]; 82} 83 84- (void)setAdditionalHeight:(CGFloat)additionalHeight { 85 if (AreCGFloatsEqual(_additionalHeight, additionalHeight)) 86 return; 87 _additionalHeight = additionalHeight; 88 [self updateToolbarHeightRange]; 89} 90 91- (void)setCollapsesAdditionalHeight:(BOOL)collapsesAdditionalHeight { 92 if (_collapsesAdditionalHeight == collapsesAdditionalHeight) 93 return; 94 _collapsesAdditionalHeight = collapsesAdditionalHeight; 95 [self updateToolbarHeightRange]; 96} 97 98- (const HeightRange&)heightRange { 99 // Custom getter is needed to support the C++ reference type. 100 return _heightRange; 101} 102 103- (void)setProgress:(CGFloat)progress { 104 progress = base::ClampToRange(progress, kMinProgress, kMaxProgress); 105 if (AreCGFloatsEqual(_progress, progress)) 106 return; 107 _progress = progress; 108 [self updateHeightConstant]; 109} 110 111- (void)setCollapsingToolbar:(UIView<ToolbarCollapsing>*)collapsingToolbar { 112 if (_collapsingToolbar == collapsingToolbar) 113 return; 114 [self stopObservingCollapsingToolbar]; 115 _collapsingToolbar = collapsingToolbar; 116 [self updateCollapsingToolbarHeights]; 117 if (self.active) 118 [self startObservingCollapsingToolbar]; 119} 120 121#pragma mark - Public 122 123- (CGFloat)toolbarHeightForProgress:(CGFloat)progress { 124 progress = base::ClampToRange(progress, kMinProgress, kMaxProgress); 125 CGFloat base = self.collapsedToolbarHeight; 126 CGFloat range = self.expandedToolbarHeight - self.collapsedToolbarHeight; 127 if (self.collapsesAdditionalHeight) { 128 range += self.additionalHeight; 129 } else { 130 base += self.additionalHeight; 131 } 132 return base + progress * range; 133} 134 135#pragma mark - KVO 136 137- (void)observeValueForKeyPath:(NSString*)key 138 ofObject:(id)object 139 change:(NSDictionary*)change 140 context:(void*)context { 141 [self updateCollapsingToolbarHeights]; 142} 143 144#pragma mark - KVO Helpers 145 146- (NSArray<NSString*>* const)collapsingToolbarKeyPaths { 147 static NSArray<NSString*>* const kKeyPaths = 148 @[ @"expandedToolbarHeight", @"collapsedToolbarHeight" ]; 149 return kKeyPaths; 150} 151 152- (void)startObservingCollapsingToolbar { 153 for (NSString* keyPath in [self collapsingToolbarKeyPaths]) { 154 [self.collapsingToolbar addObserver:self 155 forKeyPath:keyPath 156 options:NSKeyValueObservingOptionNew 157 context:nullptr]; 158 } 159} 160 161- (void)stopObservingCollapsingToolbar { 162 for (NSString* keyPath in [self collapsingToolbarKeyPaths]) { 163 [self.collapsingToolbar removeObserver:self forKeyPath:keyPath]; 164 } 165} 166 167#pragma mark - Private 168 169// Upates the collapsed and expanded heights from self.collapsingToolbar. 170- (void)updateCollapsingToolbarHeights { 171 self.collapsedToolbarHeight = self.collapsingToolbar.collapsedToolbarHeight; 172 self.expandedToolbarHeight = self.collapsingToolbar.expandedToolbarHeight; 173 [self updateToolbarHeightRange]; 174} 175 176// Updates the height range using the current collapsing toolbar height values 177// and additional height behavior. 178- (void)updateToolbarHeightRange { 179 HeightRange oldHeightRange = self.heightRange; 180 CGFloat minHeight = 181 self.collapsedToolbarHeight + 182 (self.collapsesAdditionalHeight ? 0.0 : self.additionalHeight); 183 CGFloat maxHeight = self.expandedToolbarHeight + self.additionalHeight; 184 _heightRange = HeightRange(minHeight, maxHeight); 185 if (_heightRange == oldHeightRange) 186 return; 187 [self updateHeightConstant]; 188 [self.delegate collapsingHeightConstraint:self 189 didUpdateFromHeightRange:oldHeightRange]; 190} 191 192// Updates the constraint's constant 193- (void)updateHeightConstant { 194 self.constant = self.heightRange.GetInterpolatedHeight(self.progress); 195} 196 197@end 198