mvm_core_ui/MVMCoreUI/BaseControllers/TopLabelsAndBottomButtonsViewController.m
Pfeil, Scott Robert 3f482625a5 replace type
2019-01-28 17:44:58 -05:00

444 lines
22 KiB
Objective-C

//
// TopLabelsAndBottomButtonsViewController.m
// myverizon
//
// Created by Scott Pfeil on 1/26/16.
// Copyright © 2016 Verizon Wireless. All rights reserved.
//
#import "TopLabelsAndBottomButtonsViewController.h"
#import <MVMCoreUI/PrimaryButtonView.h>
#import <MVMCoreUI/MFSizeObject.h>
#import <MVMCoreUI/MVMCoreUICommonViewsUtility.h>
#import <MVMCoreUI/UIColor+MFConvenience.h>
#import <MVMCoreUI/NSLayoutConstraint+MFConvenience.h>
#import <MVMCoreUI/MVMCoreUIConstants.h>
#import <MVMCoreUI/MFStyler.h>
#import <MVMCore/NSDictionary+MFConvenience.h>
#import <MVMCore/MVMCoreLoadObject.h>
#import <MVMCore/MVMCoreJSONConstants.h>
#import <MVMCore/MVMCoreConstants.h>
@import MVMAnimationFramework;
@interface TopLabelsAndBottomButtonsViewController ()
@property (nullable, strong, nonatomic) NSLayoutConstraint *heightConstraint;
@property (nullable, weak, nonatomic) UIView *topView;
@property (nullable, weak, nonatomic) UIView *bottomView;
@property (nonatomic) BOOL customBottemView;
@property (nullable, weak, nonatomic) NSArray *middleViews;
@property (nullable, weak, nonatomic) UIView *betweenView;
@property (nullable, weak, nonatomic) ViewConstrainingView *bottomAccessoryView;
// Adds the button view to the screen. Out of the scroll or in.
- (void)addViewOutsideOfScrollView:(UIView *)bottomView;
- (void)addViewToContentView:(UIView *)bottomView;
@end
@implementation TopLabelsAndBottomButtonsViewController
- (void)initialLoad {
[super initialLoad];
self.rebuildUIOnSizeChange = YES;
}
- (void)updateViews {
[super updateViews];
if ([self screenSizeChanged]) {
if (self.rebuildUIOnSizeChange) {
// If the screen size changed.... just rebuild everything.... this will cover all sizes and help for ipad slide over.
[self newDataBuildScreen];
} else {
// Update top labels and bottom buttons.
CGFloat width = CGRectGetWidth(self.view.bounds);
if ([self.topView respondsToSelector:@selector(updateView:)]) {
[((UIView <MVMCoreViewProtocol>*)self.topView) updateView:width];
}
if ([self.bottomView respondsToSelector:@selector(updateView:)]) {
[((UIView <MVMCoreViewProtocol>*)self.bottomView) updateView:width];
}
[self updateTopLabelsBottomButtonsPadding];
[self.bottomAccessoryView updateView:width];
[self resetSpaceForFormArrayWithConstrainingViews];
}
}
}
- (void)updateTopLabelsBottomButtonsPadding {
if (self.topLabelsView) {
UIEdgeInsets paddingForTopLabels = [self paddingForTopLabels];
self.topLabelsView.topLabelConstraint.constant = paddingForTopLabels.top;
self.topLabelsView.bottomLabelConstraint.constant = paddingForTopLabels.bottom;
[self.topLabelsView setLeftConstant:paddingForTopLabels.left];
[self.topLabelsView setRightConstant:paddingForTopLabels.right];
}
if (!self.customBottemView) {
PrimaryButtonView *buttonView = (PrimaryButtonView *)self.bottomView;
if (self.secondaryButton || self.primaryButton) {
UIEdgeInsets paddingForBottomButtons = [self paddingForBottomButtons];
buttonView.leftPin.constant = paddingForBottomButtons.left;
buttonView.rightPin.constant = paddingForBottomButtons.right;
buttonView.topPin.constant = paddingForBottomButtons.top;
buttonView.bottomPin.constant = paddingForBottomButtons.bottom;
} else {
buttonView.topPin.constant = 0;
buttonView.bottomPin.constant = 0;
buttonView.leftPin.constant = 0;
buttonView.rightPin.constant = 0;
}
}
}
- (void)newDataBuildScreen {
[super newDataBuildScreen];
// Removes the bottom view out of scroll if it is there.
[self.viewOutOfScroll removeFromSuperview];
[StackableViewController removeUIViews:[self.contentView subviews]];
// Checks if we are using a different object than top labels.
UIView *topView = [self useCustomViewInsteadOfLabels];
self.topView = topView;
if (!topView) {
// Sets up the labels toward the top of the screen.
TopLabelsView *topLabelsView = [[TopLabelsView alloc] initWithFrame:CGRectZero];
topLabelsView.separatorView.hidden = NO;
self.topLabelsView = topLabelsView;
topView = topLabelsView;
self.topView = topView;
[topLabelsView setWithJSON:[self mapForTopLabels]];
}
[self.contentView addSubview:topView];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topView)]];
self.topConstraintForTopView = [NSLayoutConstraint constraintWithItem:topView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
self.topConstraintForTopView.active = YES;
// Checks if we are using a different object than the bottom buttons.
UIView *bottomView = [self useCustomViewInsteadOfButtons];
self.customBottemView = (bottomView != nil);
if (!self.customBottemView) {
// Sets up the buttons/button.
NSDictionary *primaryButtonDictionary = [self primaryButtonMap];
NSDictionary *secondaryButtonDictionary = [self secondaryButtonMap];
PrimaryButtonView *buttonView = [[PrimaryButtonView alloc] initWithPrimaryButtonMap:primaryButtonDictionary secondaryButtonMap:secondaryButtonDictionary actionDelegate:self additionalData:nil buttonDelegate:self];
self.secondaryButton = buttonView.secondaryButton;
self.primaryButton = buttonView.primaryButton;
bottomView = buttonView;
self.bottomView = bottomView;
} else {
self.bottomView = bottomView;
}
[self updateTopLabelsBottomButtonsPadding];
// Adds the bottom view outside the scroll if directed.
[self.safeAreaView removeFromSuperview];
if ([self bottomViewOutsideOfScroll]) {
if (!self.customBottemView) {
bottomView.backgroundColor = [UIColor mfBackgroundGray];
}
[self addViewOutsideOfScrollView:bottomView];
self.viewOutOfScroll = bottomView;
// Sets this so that the button view added to the content view will be empty.
bottomView = [ViewConstrainingView emptyView];
}
// Adds either the bottom view or a blank view inside the scroll.
self.viewInScroll = bottomView;
[self addViewToContentView:bottomView];
// Allows addition of any custom ui before custom center view.
[self buildInAdditionalViewsBeforeCenteredContent];
// Positions the views in between the labels and buttons.
UIView *viewBetween = nil;
NSArray *views = [self buildViewsBetweenLabelsAndButtons];
self.middleViews = views;
if (views.count > 0) {
viewBetween = [MVMCoreUICommonViewsUtility commonView];
[self generateFormView:viewBetween withUIArrayForConstrainingViews:views];
[self.contentView addSubview:viewBetween];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[viewBetween]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewBetween)]];
[viewBetween setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
}
self.betweenView = viewBetween;
// This whole section is for handling vertical spacing.
//-------------------------------------------------------------
UIView *topBetweenEdgeView = topView;
if (self.topBetweenEdgeView) {
topBetweenEdgeView = self.topBetweenEdgeView;
}
UIView *bottomBetweenEdgeView = bottomView;
if (self.bottomBetweenEdgeView) {
bottomBetweenEdgeView = self.bottomBetweenEdgeView;
}
// Sets the height so when there is not enough content, the bottom buttons will stay pinned. But if there is too much content, the constraint has low priority so the screen will scroll. Only made active in certain conditions.
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0];
heightConstraint.priority = UILayoutPriorityDefaultLow;
self.heightConstraint = heightConstraint;
if (viewBetween) {
// These spacer views are what causes the buttons to be pinned to the bottom.
NSNumber *spaceAbove = [self spaceAboveBetweenView];
NSNumber *spaceBelow = [self spaceBelowBetweenView];
if (spaceAbove && spaceBelow) {
// Both top and bottom space set, buttons not pinned to bottom.
NSDictionary *verticalMetrics = @{@"top":spaceAbove,@"bottom":spaceBelow};
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-top-[viewBetween]-bottom-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,viewBetween,bottomBetweenEdgeView)]];
} else {
// Needs the height constraint
heightConstraint.active = YES;
if (!spaceAbove && !spaceBelow) {
// No set space above or below, make the spacers the same height with a default minimum.
UIView *topSpacer = [MVMCoreUICommonViewsUtility commonView];
UIView *bottomSpacer = [MVMCoreUICommonViewsUtility commonView];
[self.contentView addSubview:topSpacer];
[self.contentView addSubview:bottomSpacer];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topSpacer)]];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomSpacer)]];
NSLayoutConstraint *sameHeightSpacer = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0];
sameHeightSpacer.active = YES;
NSLayoutConstraint *minimumHeightSpacer = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing];
minimumHeightSpacer.active = YES;
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[topSpacer]-0-[viewBetween]-0-[bottomSpacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topBetweenEdgeView,topSpacer,viewBetween,bottomSpacer,bottomBetweenEdgeView)]];
} else if (spaceAbove) {
// Space above is set, space below is free.
UIView *bottomSpacer = [MVMCoreUICommonViewsUtility commonView];
[self.contentView addSubview:bottomSpacer];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomSpacer)]];
NSLayoutConstraint *bottomSpacerHeight = [NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing];
bottomSpacerHeight.active = YES;
NSDictionary *verticalMetrics = @{@"top":spaceAbove};
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-top-[viewBetween]-0-[bottomSpacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,viewBetween,bottomSpacer,bottomBetweenEdgeView)]];
} else if (spaceBelow) {
// Space below is set, space above is free.
UIView *topSpacer = [MVMCoreUICommonViewsUtility commonView];
[self.contentView addSubview:topSpacer];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topSpacer)]];
NSLayoutConstraint *topSpacerHeight = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing];
topSpacerHeight.active = YES;
NSDictionary *verticalMetrics = @{@"bottom":spaceBelow};
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[topSpacer]-0-[viewBetween]-bottom-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,topSpacer,viewBetween,bottomBetweenEdgeView)]];
}
}
} else {
// No views in between. Just messages and buttons
UIView *spacer = [MVMCoreUICommonViewsUtility commonView];
[self.contentView addSubview:spacer];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[spacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(spacer)]];
// Needs the height constraint
heightConstraint.active = YES;
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[spacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topBetweenEdgeView,spacer,bottomBetweenEdgeView)]];
}
}
- (void)updateViewConstraints {
[super updateViewConstraints];
// Updates for ios 11
if (@available(iOS 11.0, *)) {
if (self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) {
self.heightConstraint.constant = -self.scrollView.adjustedContentInset.top - self.scrollView.adjustedContentInset.bottom;
} else {
self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom;
}
} else {
self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (UIEdgeInsets)spaceAroundUIObject:(nullable id)object {
CGFloat horizontal = [MFStyler defaultHorizontalPaddingForApplicationWidth];
if (self.bottomAccessoryView == object) {
return UIEdgeInsetsMake(0, horizontal, PaddingDefaultVerticalSpacing, horizontal);
}
return UIEdgeInsetsMake(0, horizontal, 0, horizontal);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)addViewToContentView:(UIView *)bottomView {
self.bottomConstraint.active = YES;
// Buttons will be at the bottom of the content view.
[self.contentView addSubview:bottomView];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[bottomView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]];
}
- (void)addViewOutsideOfScrollView:(UIView *)bottomView {
self.bottomConstraint.active = NO;
// Buttons will be outside of the scrolling view.
[self.view addSubview:bottomView];
UIScrollView *scrollview = self.scrollView;
if (@available(iOS 11.0, *)) {
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]];
[self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:bottomView.bottomAnchor].active = YES;
UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view];
safeAreaView.backgroundColor = bottomView.backgroundColor;
self.safeAreaView = safeAreaView;
} else {
// Fallback on earlier versions
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]];
}
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]];
}
#pragma mark - Can Subclass These
- (nullable UIView *)buttonsAccessoryView {
return nil;
}
- (void)buildInAdditionalViewsBeforeCenteredContent {
UIView *accessoryView = [self buttonsAccessoryView];
if (accessoryView) {
accessoryView.translatesAutoresizingMaskIntoConstraints = NO;
ViewConstrainingView *constrainingView = [ViewConstrainingView viewConstrainingView:accessoryView];
constrainingView.updateViewHorizontalDefaults = YES;
[self.contentView addSubview:constrainingView];
self.bottomBetweenEdgeView = constrainingView;
self.bottomAccessoryView = constrainingView;
[constrainingView setPinConstantsWithInsets:[self spaceAroundUIObject:constrainingView]];
[NSLayoutConstraint constraintPinLeftSubview:constrainingView leftConstant:0];
[NSLayoutConstraint constraintPinRightSubview:constrainingView rightConstant:0];
[NSLayoutConstraint constraintPinFirstView:constrainingView toSecondView:self.viewInScroll
withConstant:0 directionVertical:YES];
} else {
self.bottomAccessoryView = nil;
}
}
- (nullable NSArray <UIView *>*)buildViewsBetweenLabelsAndButtons {
return nil;
}
- (nullable NSNumber *)spaceAboveBetweenView {
return nil;
}
- (nullable NSNumber *)spaceBelowBetweenView {
return nil;
}
- (UIEdgeInsets)paddingForTopLabels {
return UIEdgeInsetsMake(PaddingFive, [MFStyler defaultHorizontalPaddingForApplicationWidth], PaddingDefaultVerticalSpacing, [MFStyler defaultHorizontalPaddingForApplicationWidth]);
}
- (UIEdgeInsets)paddingForBottomButtons {
// Smaller space for smaller devices. Also, top is 0 by default when in scroll.
CGFloat verticalSpacing = [[MFSizeObject sizeObjectWithStandardSize:PaddingDefaultVerticalSpacing smalliPhoneSize:PaddingDefault] getValueBasedOnScreenSize];
return UIEdgeInsetsMake(([self bottomViewOutsideOfScroll] ? verticalSpacing : 0), PaddingDefaultHorizontalSpacing, verticalSpacing, PaddingDefaultHorizontalSpacing);
}
- (NSDictionary*)primaryButtonMap {
NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap];
return [buttonMap dict:KeyPrimaryButton];
}
- (NSDictionary*)secondaryButtonMap {
NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap];
return [buttonMap dict:KeySecondaryButton];
}
- (nullable NSDictionary *)mapForTopLabels {
return self.loadObject.pageJSON;
}
- (nullable UIView *)useCustomViewInsteadOfLabels {
return nil;
}
- (nullable UIView *)useCustomViewInsteadOfButtons {
return nil;
}
- (BOOL)bottomViewOutsideOfScroll {
return NO;
}
#pragma mark - Animations
-(void)setupIntroAnimations {
if (self.topView.subviews) {
[self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.topView]];
}
if (self.topBetweenEdgeView.subviews.count) {
[self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.topBetweenEdgeView]];
}
// NSMutableArray *centerAnimationObjects = [NSMutableArray new];
// for (int i = 0; i < self.middleViews.count; i++) {
// MVMAnimationObject *aobj =[MVMAnimations fadeUpAnimationWithView:self.middleViews[i]];
// [centerAnimationObjects addObject:aobj];
// }
// [self.introAnimationManager addAnimationGroupWithAnimations:centerAnimationObjects];
[self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.betweenView]];
if (self.bottomBetweenEdgeView.subviews.count) {
[self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.bottomBetweenEdgeView]];
}
if (self.bottomView.subviews) {
[self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.bottomView]];
}
}
@end