featherless 3b34e97f5b
[FlexibleHeader] Add new canAlwaysExpandToMaximumHeight behavior. (#4978)
This rolls forward #4794 which was rolled back in v62.0.0.

All runtime logic introduced by #4794 has bee gated behind the new behavioral flag in an effort to maintain existing functionality.

Closes https://github.com/material-components/material-components-ios/issues/4393
2018-08-31 14:11:14 -04:00

224 lines
7.9 KiB
Objective-C

// Copyright 2015-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 <UIKit/UIKit.h>
#import "MaterialFlexibleHeader.h"
#import "supplemental/FlexibleHeaderConfiguratorSupplemental.h"
#import "MaterialFlexibleHeader+CanAlwaysExpandToMaximumHeight.h"
@interface FlexibleHeaderConfiguratorExample ()
@property(nonatomic) BOOL overrideStatusBarHidden;
@end
@implementation FlexibleHeaderConfiguratorExample
// Invoked when the user has changed a control's value.
- (void)field:(FlexibleHeaderConfiguratorField)field didChangeValue:(NSNumber *)value {
MDCFlexibleHeaderView *headerView = self.fhvc.headerView;
switch (field) {
// Basic behavior
case FlexibleHeaderConfiguratorFieldCanOverExtend:
headerView.canOverExtend = [value boolValue];
break;
case FlexibleHeaderConfiguratorFieldInFrontOfInfiniteContent:
headerView.inFrontOfInfiniteContent = [value boolValue];
break;
case FlexibleHeaderConfiguratorFieldHideStatusBar: {
self.overrideStatusBarHidden = [value boolValue];
BOOL statusBarCanBeVisible = !self.overrideStatusBarHidden;
headerView.statusBarHintCanOverlapHeader = statusBarCanBeVisible;
[UIView animateWithDuration:0.4
animations:^{
[self setNeedsStatusBarAppearanceUpdate];
}];
break;
}
// Shift behavior
case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled: {
BOOL isOn = [value boolValue];
if (!isOn) {
headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorDisabled;
[self
didChangeValueForField:FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar
animated:YES];
} else {
headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorEnabled;
}
break;
}
case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar: {
BOOL isOn = [value boolValue];
if (!isOn) {
headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorEnabled;
} else {
headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar;
[self didChangeValueForField:FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled
animated:YES];
}
break;
}
case FlexibleHeaderConfiguratorFieldContentImportance:
headerView.headerContentImportance =
([value boolValue] ? MDCFlexibleHeaderContentImportanceHigh
: MDCFlexibleHeaderContentImportanceDefault);
break;
case FlexibleHeaderConfiguratorFieldShiftOffscreen: {
[headerView shiftHeaderOffScreenAnimated:YES];
break;
}
case FlexibleHeaderConfiguratorFieldShiftOnscreen: {
[headerView shiftHeaderOnScreenAnimated:YES];
break;
}
// Header height
case FlexibleHeaderConfiguratorFieldMinimumHeight:
headerView.minimumHeight = [self heightDenormalized:[value floatValue]];
break;
case FlexibleHeaderConfiguratorFieldMaximumHeight:
headerView.maximumHeight = [self heightDenormalized:[value floatValue]];
break;
case FlexibleHeaderConfiguratorFieldMinMaxHeightIncludeSafeArea:
headerView.minMaxHeightIncludesSafeArea = [value boolValue];
break;
case FlexibleHeaderConfiguratorFieldCanAlwaysExpandToMaximumHeight:
headerView.canAlwaysExpandToMaximumHeight = [value boolValue];
break;
}
}
#pragma mark - Typical Flexible Header implementations
// Required for shiftBehavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar.
- (BOOL)prefersStatusBarHidden {
return _overrideStatusBarHidden || self.fhvc.prefersStatusBarHidden;
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return UIStatusBarAnimationSlide;
}
#pragma mark - UIScrollViewDelegate
// Note that, unlike the Typical Use example, we are explicitly forwarding the UIScrollViewDelegate
// methods to the header view. This is because this example controller also needs to handle other
// UITableViewDelegate events.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.fhvc.headerView.trackingScrollView) {
[self.fhvc.headerView trackingScrollViewDidScroll];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == self.fhvc.headerView.trackingScrollView) {
[self.fhvc.headerView trackingScrollViewDidEndDecelerating];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
MDCFlexibleHeaderView *headerView = self.fhvc.headerView;
if (scrollView == headerView.trackingScrollView) {
[headerView trackingScrollViewDidEndDraggingWillDecelerate:decelerate];
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset {
MDCFlexibleHeaderView *headerView = self.fhvc.headerView;
if (scrollView == headerView.trackingScrollView) {
[headerView trackingScrollViewWillEndDraggingWithVelocity:velocity
targetContentOffset:targetContentOffset];
}
}
#pragma mark - Field data manipulation
static const CGFloat kHeightScalar = 300;
- (CGFloat)normalizedHeight:(CGFloat)height {
return (height - self.minimumHeaderHeight) / (kHeightScalar - self.minimumHeaderHeight);
}
- (CGFloat)heightDenormalized:(CGFloat)normalized {
return normalized * (kHeightScalar - self.minimumHeaderHeight) + self.minimumHeaderHeight;
}
- (NSNumber *)valueForField:(FlexibleHeaderConfiguratorField)field {
switch (field) {
case FlexibleHeaderConfiguratorFieldCanOverExtend:
return @(self.fhvc.headerView.canOverExtend);
case FlexibleHeaderConfiguratorFieldContentImportance:
return @(
(self.fhvc.headerView.headerContentImportance == MDCFlexibleHeaderContentImportanceHigh));
case FlexibleHeaderConfiguratorFieldHideStatusBar:
return @(self.overrideStatusBarHidden);
case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled: {
MDCFlexibleHeaderShiftBehavior behavior = self.fhvc.headerView.shiftBehavior;
BOOL enabled = (behavior == MDCFlexibleHeaderShiftBehaviorEnabled ||
behavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar);
return @(enabled);
}
case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar: {
MDCFlexibleHeaderShiftBehavior behavior = self.fhvc.headerView.shiftBehavior;
BOOL enabled = (behavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar);
return @(enabled);
}
// Buttons have no value
case FlexibleHeaderConfiguratorFieldShiftOffscreen:
case FlexibleHeaderConfiguratorFieldShiftOnscreen:
return nil;
case FlexibleHeaderConfiguratorFieldInFrontOfInfiniteContent:
return @(self.fhvc.headerView.inFrontOfInfiniteContent);
case FlexibleHeaderConfiguratorFieldMinimumHeight:
return @([self normalizedHeight:self.fhvc.headerView.minimumHeight]);
case FlexibleHeaderConfiguratorFieldMaximumHeight:
return @([self normalizedHeight:self.fhvc.headerView.maximumHeight]);
case FlexibleHeaderConfiguratorFieldMinMaxHeightIncludeSafeArea:
return @(self.fhvc.headerView.minMaxHeightIncludesSafeArea);
case FlexibleHeaderConfiguratorFieldCanAlwaysExpandToMaximumHeight:
return @(self.fhvc.headerView.canAlwaysExpandToMaximumHeight);
}
}
@end