// // TopLabelsAndBottomButtonsTableViewController.m // myverizon // // Created by Chris Yang on 2/3/16. // Copyright © 2016 Verizon Wireless. All rights reserved. // #import "TopLabelsAndBottomButtonsTableViewController.h" #import #import #import #import #import #import #import #import #import #import #import @import MVMAnimationFramework; @interface TopLabelsAndBottomButtonsTableViewController () @property (strong, nonatomic) UIView *topView; @property (strong, nonatomic) UIView *headerView; @property (strong, nonatomic) NSLayoutConstraint *topViewBottomConstraint; @property (strong, nonatomic) UIView *footerView; @property (strong, nonatomic) UIView *footerViewOutsideOfScroll; @property (strong, nonatomic) NSLayoutConstraint *bottomViewTopConstraint; @property (strong, nonatomic) UIView *headerAccessoryView; @end @implementation TopLabelsAndBottomButtonsTableViewController - (void)initialLoad { [super initialLoad]; self.rebuildUIOnSizeChange = YES; } - (void)updateViews { [super updateViews]; // If the screen size changed.... just rebuild everything.... this will cover all sizes and help for ipad slide over. 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 *)self.topView) updateView:width]; [self showHeader]; } [self.topLabelsView updateView:width]; if ([self.bottomView respondsToSelector:@selector(updateView:)]) { [((UIView *)self.bottomView) updateView:width]; [self showFooter]; } [self.tableView reloadData]; } } } - (void)newDataBuildScreen { [super newDataBuildScreen]; // Checks if subclass is using a different view than the top labels. self.topView = [self useCustomViewInsteadOfLabels]; if (!self.topView) { // The labels are toward the top of the screen. TopLabelsView *topLabelsView = [[TopLabelsView alloc] initWithTableView:self]; [topLabelsView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; UIEdgeInsets paddingForTopLabels = [self paddingForTopLabels]; topLabelsView.topLabelConstraint.constant = paddingForTopLabels.top; topLabelsView.bottomLabelConstraint.constant = paddingForTopLabels.bottom; [topLabelsView setLeftConstant:paddingForTopLabels.left]; [topLabelsView setRightConstant:paddingForTopLabels.right]; topLabelsView.separatorView.hidden = NO; self.topLabelsView = topLabelsView; [self setHeadlineAndMessage]; self.topView = topLabelsView; } // Setup accessory view. [self setUpHeaderAccessoryView]; // add top view to table header if (self.topView) { self.topView.translatesAutoresizingMaskIntoConstraints = NO; [self createViewForHeader]; [self showHeader]; } else { [self hideHeader]; } // Setup accessory view. [self setUpFooterAccessoryView]; // Checks if subclass is using a different view than the bottom buttons. [self.bottomView removeFromSuperview]; self.tableView.tableFooterView = nil; self.bottomView = [self useCustomViewInsteadOfButtons]; if (!self.bottomView) { // Sets up the buttons/button. NSDictionary *primaryButtonDictionary = [self primaryButtonMap]; NSDictionary *secondaryButtonDictionary = [self secondaryButtonMap]; TwoButtonView *buttonView = [[TwoButtonView alloc] initWithPrimaryButtonMap:primaryButtonDictionary secondaryButtonMap:secondaryButtonDictionary delegateObject:[self delegateObject] additionalData:nil]; self.secondaryButton = buttonView.secondaryButton; self.primaryButton = buttonView.primaryButton; self.bottomView = buttonView; buttonView.topPin.constant = [self paddingForBottomButtons].top; buttonView.bottomPin.constant = [self paddingForBottomButtons].bottom; buttonView.leftPin.constant = [self paddingForBottomButtons].left; buttonView.rightPin.constant = [self paddingForBottomButtons].right; if (self.bottomViewOutsideOfScroll) { buttonView.backgroundColor = [UIColor mfBackgroundGray]; } } // add bottom view to table footer if (self.bottomView) { self.bottomView.translatesAutoresizingMaskIntoConstraints = NO; [self createViewForFooter]; [self showFooter]; } else { [self hideFooter]; } [self.tableView reloadData]; } - (void)setHeadlineAndMessage { [self.topLabelsView setHeadlineString:[[self mapForTopLabels] stringForKey:KeyTitle] messageString:[[self mapForTopLabels] stringForKey:KeyMessage]]; } - (void)updateViewConstraints { [super updateViewConstraints]; BOOL spaceToFillTop = NO; CGFloat currentSpace = 0; if ([self spaceBelowTopViewSetToFill] && self.tableView.tableHeaderView) { // Space top view. spaceToFillTop = YES; currentSpace = self.topViewBottomConstraint.constant; } BOOL spaceToFillBottom = NO; if (!self.bottomViewOutsideOfScroll && [self spaceAboveBottomViewSetToFill] && self.tableView.tableFooterView) { // Space to fill bottom view. spaceToFillBottom = YES; currentSpace += self.bottomViewTopConstraint.constant; } if (spaceToFillTop || spaceToFillBottom) { // This logic only works because the minimum height is zero, otherwise we'd have to add both minimum heights like we did the current space. CGFloat newSpace = [MVMCoreUIUtility getVariableConstraintHeight:currentSpace inScrollView:self.tableView minimumHeight:0]; // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. CGFloat currentSpaceForCompare = currentSpace; if (spaceToFillTop && self.bottomViewOutsideOfScroll) { currentSpaceForCompare = currentSpace * 2; } if (!fequalwiththreshold(newSpace,currentSpaceForCompare,0.1)) { if (spaceToFillTop && spaceToFillBottom) { // Both are being spaced. self.topViewBottomConstraint.constant = newSpace / 2; self.bottomViewTopConstraint.constant = newSpace / 2; [self showHeader]; [self showFooter]; } else if (spaceToFillTop) { // Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one. if (self.bottomViewOutsideOfScroll) { self.topViewBottomConstraint.constant = newSpace / 2; } else { self.topViewBottomConstraint.constant = newSpace; } [self showHeader]; } else { // Only bottom is spaced. self.bottomViewTopConstraint.constant = newSpace; [self showFooter]; } } } } - (void)setUpHeaderAccessoryView { UIView *headerAccessoryView = [[UIView alloc] initWithFrame:CGRectZero]; headerAccessoryView.translatesAutoresizingMaskIntoConstraints = NO; self.headerAccessoryView = headerAccessoryView; NSArray *accessoryViews = [self populateHeaderAccessoryView]; if (accessoryViews.count > 0) { __block typeof(self) weakSelf = self; [StackableViewController populateView:self.headerAccessoryView withUIArray:accessoryViews withSpacingBlock:^UIEdgeInsets(id _Nullable object) { return [weakSelf spaceAroundUIObject:object]; }]; } else { [NSLayoutConstraint constraintPinView:headerAccessoryView heightConstraint:YES heightConstant:0 widthConstraint:NO widthConstant:0]; } } - (nonnull NSArray *)populateHeaderAccessoryView { return @[]; } - (void)setUpFooterAccessoryView { UIView *footerAccessoryView = [[UIView alloc] initWithFrame:CGRectZero]; footerAccessoryView.translatesAutoresizingMaskIntoConstraints = NO; self.tableFooterAccessoryView = footerAccessoryView; NSArray *accessoryViews = [self populateFooterAccessoryView]; if (accessoryViews.count > 0) { __block typeof(self) weakSelf = self; [StackableViewController populateView:self.tableFooterAccessoryView withUIArray:accessoryViews withSpacingBlock:^UIEdgeInsets(id _Nullable object) { return [weakSelf spaceAroundUIObject:object]; }]; } else { [NSLayoutConstraint constraintPinView:footerAccessoryView heightConstraint:YES heightConstant:0 widthConstraint:NO widthConstant:0]; } } - (nonnull NSArray *)populateFooterAccessoryView { return @[]; } - (void)createViewForHeader { // Creates the header view. UIView *header = [[UIView alloc] initWithFrame:CGRectZero]; header.translatesAutoresizingMaskIntoConstraints = NO; UIView *topView = self.topView; UIView *headerAccessoryView = self.headerAccessoryView; [header addSubview:topView]; [header addSubview:headerAccessoryView]; // Sets up the constraints if (headerAccessoryView) { [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topView]-0-[headerAccessoryView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topView,headerAccessoryView)]]; [NSLayoutConstraint constraintPinSubview:topView pinTop:YES topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0]; self.topViewBottomConstraint = [[NSLayoutConstraint constraintPinSubview:headerAccessoryView pinTop:NO topConstant:0 pinBottom:YES bottomConstant:[[self spaceAboveBetweenView] floatValue] pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0] objectForKey:ConstraintBot ofType:[NSLayoutConstraint class]]; } else { self.topViewBottomConstraint = [[NSLayoutConstraint constraintPinSubview:topView pinTop:YES topConstant:0 pinBottom:YES bottomConstant:[[self spaceAboveBetweenView] floatValue] pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0] objectForKey:ConstraintBot ofType:[NSLayoutConstraint class]]; } self.headerView = header; } - (void)createViewForFooter { // Creates the footer view. UIView *footer = [[UIView alloc] initWithFrame:CGRectZero]; footer.translatesAutoresizingMaskIntoConstraints = NO; [footer addSubview:self.tableFooterAccessoryView]; [footer addSubview:self.bottomView]; self.bottomViewTopConstraint = [[NSLayoutConstraint constraintPinSubview:self.tableFooterAccessoryView pinTop:YES topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0] objectForKey:ConstraintTop ofType:[NSLayoutConstraint class]]; [NSLayoutConstraint constraintPinSubview:self.bottomView pinTop:NO topConstant:0 pinBottom:YES bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0]; [NSLayoutConstraint constraintWithItem:self.bottomView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.tableFooterAccessoryView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES; self.footerView = footer; } - (void)showHeader { if (self.headerView) { [self.headerView removeFromSuperview]; self.tableView.tableHeaderView = nil; UIView *headerView = self.headerView; UIView *tableHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 0)]; [MVMCoreUIUtility sizeViewToFit:headerView]; CGRect frame = tableHeader.frame; frame.size.height = CGRectGetHeight(headerView.frame); tableHeader.frame = frame; [tableHeader addSubview:headerView]; [NSLayoutConstraint constraintPinSubview:headerView pinTop:YES pinBottom:YES pinLeft:YES pinRight:YES]; self.tableView.tableHeaderView = tableHeader; } } - (void)showFooter { if (self.footerView) { [self.footerView removeFromSuperview]; [self.safeAreaView removeFromSuperview]; if (self.bottomViewOutsideOfScroll) { if (self.tableView.tableFooterView) { self.tableView.tableFooterView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 0); } self.bottomConstraint.active = NO; // Bottom view will be outside of the scrolling view. [self.view addSubview:self.footerView]; UITableView *tableView = self.tableView; UIView *footerView = self.footerView; NSLayoutConstraint *bottomViewTop = [NSLayoutConstraint constraintWithItem:footerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:tableView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; bottomViewTop.active = YES; NSLayoutConstraint *bottomViewBot = [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:footerView.bottomAnchor]; bottomViewBot.priority = 900; bottomViewBot.active = YES; UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; safeAreaView.backgroundColor = self.bottomView.backgroundColor; self.safeAreaView = safeAreaView; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[footerView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(footerView)]]; } else { self.bottomConstraint.active = YES; CGFloat y = 0; //if footer already exists, use the same y location to avoid the strange animation if (self.tableView.tableFooterView) { y = self.tableView.tableFooterView.frame.origin.y; } UIView *tableFooter = [[UIView alloc] initWithFrame:CGRectMake(0, y, [UIScreen mainScreen].bounds.size.width, 0)]; [MVMCoreUIUtility sizeViewToFit:self.footerView]; CGRect frame = tableFooter.frame; frame.size.height = CGRectGetHeight(self.footerView.frame); tableFooter.frame = frame; [tableFooter addSubview:self.footerView]; [NSLayoutConstraint constraintPinSubview:self.footerView pinTop:YES pinBottom:YES pinLeft:YES pinRight:YES]; self.tableView.tableFooterView = tableFooter; } } } - (void)hideHeader { CGRect frame = CGRectZero; frame.size.height = CGFLOAT_MIN; self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:frame]; [self.view layoutIfNeeded]; } - (void)hideFooter { [self.footerView removeFromSuperview]; [self.safeAreaView removeFromSuperview]; self.bottomConstraint.active = YES; self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)]; [self.view layoutIfNeeded]; } #pragma mark - Can Subclass These - (nonnull NSNumber *)spaceAboveBetweenView { return @0; } - (BOOL)spaceBelowTopViewSetToFill { return NO; } - (BOOL)spaceAboveBottomViewSetToFill { return YES; } - (UIEdgeInsets)paddingForTopLabels { return UIEdgeInsetsMake(PaddingFive, [MFStyler defaultHorizontalPaddingForApplicationWidth], PaddingDefaultVerticalSpacing, [MFStyler defaultHorizontalPaddingForApplicationWidth]); } - (UIEdgeInsets)paddingForBottomButtons { return UIEdgeInsetsMake(PaddingDefaultVerticalSpacing, PaddingDefaultHorizontalSpacing, PaddingDefaultVerticalSpacing, PaddingDefaultHorizontalSpacing); } - (NSDictionary *)secondaryButtonMap { NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap]; return [buttonMap dict:KeySecondaryButton]; } - (NSDictionary *)primaryButtonMap { NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap]; return [buttonMap dict:KeyPrimaryButton]; } - (nullable NSDictionary *)mapForTopLabels { return self.loadObject.pageJSON; } - (nullable UIView *)useCustomViewInsteadOfLabels { return nil; } - (nullable UIView *)useCustomViewInsteadOfButtons { return nil; } - (BOOL)bottomViewOutsideOfScroll { return NO; } - (void)setPrimaryLeftButtonHidden:(BOOL)left rightButtonHidden:(BOOL)right { if ([self.bottomView isKindOfClass:[TwoButtonView class]]) { TwoButtonView *buttonView = (TwoButtonView *)self.bottomView; if (right && !left) { [buttonView hideRightButton]; } else if (!right && left) { [buttonView hideLeftButton]; } else if (right && left) { [buttonView hideBothButtons]; } else if (!right && !left) { [buttonView showBothButtons]; } } } #pragma mark - helper - (UIEdgeInsets)spaceAroundUIObject:(nullable id)object { CGFloat horizontal = [MFStyler defaultHorizontalPaddingForApplicationWidth]; return UIEdgeInsetsMake(PaddingDefault, horizontal, PaddingDefault, horizontal); } #pragma mark - ScrollView -(void)scrollViewDidScroll:(UIScrollView *)scrollView { // To stop handscroll animation if animating after scroll [self stopHandScrollAnimation:YES]; } - (void)dealloc { [self.tableView setDelegate:nil]; } #pragma mark - Animation -(void)setupIntroAnimations { if (self.topView.subviews.count) { [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.topView]]; } if (self.headerAccessoryView.subviews.count) { [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.headerAccessoryView]]; } [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations animateTableViewFadeInCellsWithTableView:self.tableView]]; if (self.bottomView.subviews.count) { [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.bottomView]]; } } @end