352 lines
12 KiB
Objective-C
352 lines
12 KiB
Objective-C
//
|
|
// MFScrollingViewController.m
|
|
// MobileFirstFramework
|
|
//
|
|
// Created by Pfeil, Scott Robert on 11/21/17.
|
|
// Copyright © 2017 Verizon Wireless. All rights reserved.
|
|
//
|
|
|
|
#import "MFScrollingViewController.h"
|
|
#import <MVMCore/MVMCoreDispatchUtility.h>
|
|
#import "MFTransparentGIFView.h"
|
|
#import <MVMCore/MVMCoreGetterUtility.h>
|
|
#import <MVMCore/MVMCoreLoadObject.h>
|
|
#import <MVMCore/NSDictionary+MFConvenience.h>
|
|
#import "NSLayoutConstraint+MVMCoreUIConvenience.h"
|
|
#import <MVMCore/MVMCoreJSONConstants.h>
|
|
#import "MVMCoreUIUtility.h"
|
|
#import "MVMCoreUIConstants.h"
|
|
#import "MVMCoreUISession.h"
|
|
|
|
@interface MFScrollingViewController ()
|
|
|
|
// A flag for if the Keyboard Notifications are added.
|
|
@property (assign, nonatomic) BOOL keyboardNotificationsAdded;
|
|
|
|
// Stores the pre-keyboard content inset.
|
|
@property (assign, nonatomic) UIEdgeInsets preKeyboardContentInset;
|
|
|
|
// Boolean for if the keyboard is showing or now.
|
|
@property (assign, nonatomic, readwrite) BOOL keyboardIsShowing;
|
|
|
|
// Boolean for if the scroll indicator has flashed
|
|
@property (assign, nonatomic, readwrite) BOOL isScrollIndicatorFlashed;
|
|
|
|
// Property for reference of hand scroll animation gif
|
|
@property (strong, nonatomic) MFTransparentGIFView *gifView;
|
|
|
|
@property (assign, nonatomic, readwrite) BOOL isPageTypeEnabledForHandScrollAnimation;
|
|
|
|
@property (assign) __block BOOL isHandScrollAnimationRendered;
|
|
|
|
@property (nullable, strong, nonatomic) id scrollFlashNotificationObserver;
|
|
|
|
// To add gif view for scroll animation
|
|
- (void)addScrollDownHandAnimation;
|
|
|
|
@end
|
|
|
|
@implementation MFScrollingViewController
|
|
|
|
static CGFloat const HandScrollMargin = 10.f;
|
|
static NSTimeInterval const HandScrollAnimationTiming = 7.f;
|
|
|
|
- (BOOL)isFlashScrollIndicatorAccesible {
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)newDataBuildScreen {
|
|
[super newDataBuildScreen];
|
|
[self updatePageHandScrollAnimationStatus];
|
|
|
|
if ([self shouldEnableHandScrollAnimation]) {
|
|
[self addScrollDownHandAnimation];
|
|
}
|
|
}
|
|
|
|
- (void)initialLoad {
|
|
[super initialLoad];
|
|
|
|
// Adds the tap gesture to dismiss the keyboard.
|
|
self.dismissKeyboardTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissFieldInput:)];
|
|
self.dismissKeyboardTapGesture.enabled = NO;
|
|
|
|
//This flag is required to offset for footer for tableView
|
|
self.isHandScrollAnimationRendered = NO;
|
|
self.isPageTypeEnabledForHandScrollAnimation = NO;
|
|
}
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
// Do any additional setup after loading the view.
|
|
|
|
[self.view addGestureRecognizer:self.dismissKeyboardTapGesture];
|
|
self.scrollView.alwaysBounceVertical = NO;
|
|
self.scrollView.delegate = self;
|
|
|
|
self.isScrollIndicatorFlashed = NO;
|
|
}
|
|
|
|
- (void)viewDidLayoutSubviews {
|
|
[super viewDidLayoutSubviews];
|
|
|
|
BOOL automaticInset = NO;
|
|
if (@available(iOS 11.0, *)) {
|
|
automaticInset = self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic;
|
|
}
|
|
|
|
// Takes into account the navigation bar.
|
|
if (!automaticInset && (self.edgesForExtendedLayout & UIRectEdgeTop)) {
|
|
|
|
CGFloat top = 0;
|
|
if (self.navigationController && ![self.navigationController isNavigationBarHidden] && ![self navigationBarTransparent] && (self.navigationController.extendedLayoutIncludesOpaqueBars || !self.navigationController.navigationBar.opaque)) {
|
|
|
|
// Navigation bar is showing, set the top content inset to its height.
|
|
top = CGRectGetMaxY(self.navigationController.navigationBar.frame);
|
|
} else {
|
|
|
|
// No navigation bar, account for the status bar if need be
|
|
top = [MVMCoreUIUtility getTopSpaceWithStatusBarForView:self.scrollView];
|
|
}
|
|
|
|
UIEdgeInsets inset;
|
|
if (self.keyboardIsShowing) {
|
|
inset = self.preKeyboardContentInset;
|
|
} else {
|
|
inset = self.scrollView.contentInset;
|
|
}
|
|
|
|
if (!fequal(top, inset.top)) {
|
|
inset = UIEdgeInsetsMake(top, inset.left, inset.bottom, inset.right);
|
|
|
|
if (self.keyboardIsShowing) {
|
|
self.preKeyboardContentInset = inset;
|
|
} else {
|
|
self.scrollView.contentInset = inset;
|
|
}
|
|
|
|
// Fixes an ios bug where setting the scroll content doesn't redraw the scroll content at offset 0.
|
|
if (self.scrollView.contentOffset.y < 1) {
|
|
[self.scrollView scrollRectToVisible:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 1) animated:NO];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Update any constraints.
|
|
[self.view setNeedsUpdateConstraints];
|
|
|
|
[self.view layoutSubviews];
|
|
}
|
|
|
|
- (void)updateViewConstraints {
|
|
[super updateViewConstraints];
|
|
|
|
// Sets the width of the content to the width of the screen.
|
|
self.contentWidthConstraint.constant = CGRectGetWidth(self.view.bounds) - self.scrollView.contentInset.left - self.scrollView.contentInset.right;
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated {
|
|
[super viewWillAppear:animated];
|
|
[self registerForKeyboardNotifications];
|
|
[self registerForScrollFlashWhenLaunch];
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
[super viewWillDisappear:animated];
|
|
[self unregisterForKeyboardNotifications];
|
|
[self unregisterForScrollFlashWhenLaunch];
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated {
|
|
[super viewDidAppear:animated];
|
|
|
|
if (!self.shouldTriggerStartAnimations || DisableAnimations) {
|
|
[self pageAnimationEnded];
|
|
}
|
|
}
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
[super viewDidDisappear:animated];
|
|
|
|
if (!CGAffineTransformIsIdentity(self.view.transform)) {
|
|
self.view.transform = CGAffineTransformIdentity;
|
|
self.scrollView.contentInset = UIEdgeInsetsMake(0,0,0,0);
|
|
}
|
|
}
|
|
|
|
#pragma mark - keyboard handling
|
|
|
|
- (void)keyboardWillShow:(nonnull NSNotification *)notification {
|
|
|
|
// Stores the current scroll insets if the keyboard was hidden.
|
|
if (!self.keyboardIsShowing) {
|
|
self.preKeyboardContentInset = self.scrollView.contentInset;
|
|
}
|
|
self.keyboardIsShowing = YES;
|
|
|
|
// Enables the tap gesture.
|
|
self.dismissKeyboardTapGesture.enabled = YES;
|
|
|
|
[MVMCoreUIUtility setScrollViewInsetForKeyboardShow:notification scrollView:self.scrollView viewController:self rectToScrollTo:^CGRect{
|
|
return [self rectToScrollToWhenKeyboardPopsUp];
|
|
}];
|
|
}
|
|
|
|
- (void)keyboardWillBeHidden:(nonnull NSNotification *)notification {
|
|
self.keyboardIsShowing = NO;
|
|
|
|
// Disables the tap gesture.
|
|
self.dismissKeyboardTapGesture.enabled = NO;
|
|
|
|
[MVMCoreUIUtility setScrollViewInsetForKeyboardHide:notification scrollView:self.scrollView viewController:self contentInset:self.preKeyboardContentInset];
|
|
}
|
|
|
|
- (CGRect)rectToScrollToWhenKeyboardPopsUp {
|
|
return [self.scrollView convertRect:[self.selectedField frame] fromView:[self.selectedField superview]];
|
|
}
|
|
|
|
- (void)registerForKeyboardNotifications {
|
|
|
|
if (!self.keyboardNotificationsAdded) {
|
|
self.keyboardNotificationsAdded = YES;
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];
|
|
}
|
|
}
|
|
|
|
- (void)unregisterForKeyboardNotifications {
|
|
if (self.keyboardNotificationsAdded) {
|
|
self.keyboardNotificationsAdded = NO;
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
|
|
}
|
|
}
|
|
|
|
#pragma mark - UIScrollViewDelegate Functions
|
|
|
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
|
[self stopHandScrollAnimation:YES];
|
|
}
|
|
|
|
|
|
#pragma mark - Scroll Flash Handling
|
|
- (void)registerForScrollFlashWhenLaunch {
|
|
|
|
if ([self isFlashScrollIndicatorAccesible]) {
|
|
|
|
__weak MFScrollingViewController *weakSelf = self;
|
|
self.scrollFlashNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"PageAnimationEnded" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
|
|
[weakSelf pageAnimationEnded];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)unregisterForScrollFlashWhenLaunch {
|
|
|
|
if (self.scrollFlashNotificationObserver) {
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"PageAnimationEnded" object:nil];
|
|
self.scrollFlashNotificationObserver = nil;
|
|
}
|
|
}
|
|
|
|
- (void)pageAnimationEnded {
|
|
|
|
//Implemented for Single View Appearance for now
|
|
//Can be enabled for every time view appear
|
|
if (self.isScrollIndicatorFlashed == NO) {
|
|
|
|
__weak MFScrollingViewController *weakSelf = self;
|
|
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
|
[weakSelf.scrollView flashScrollIndicators];
|
|
weakSelf.isScrollIndicatorFlashed = YES;
|
|
}];
|
|
}
|
|
|
|
// To Show or Hide Hand Scroll Animation
|
|
if (![self shouldEnableHandScrollAnimation]) {
|
|
return;
|
|
}
|
|
if ([self isHandScrollAnimationRequired]) {
|
|
|
|
__weak MFScrollingViewController *weakSelf = self;
|
|
[MVMCoreDispatchUtility performBlockOnMainThread: ^{
|
|
[weakSelf startHandScrollAnimation];
|
|
}];
|
|
} else {
|
|
[self stopHandScrollAnimation:NO];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Hand Scroll Animation
|
|
|
|
// this can be ovveriden by subclass to enable or disable the same. but should check for server flag in subclass implementation
|
|
- (BOOL)shouldEnableHandScrollAnimation {
|
|
// depends on server master flag for app
|
|
// to check if the hand scroll animation is enabled for the page
|
|
return self.isPageTypeEnabledForHandScrollAnimation;
|
|
}
|
|
|
|
- (void)updatePageHandScrollAnimationStatus {
|
|
if ([MVMCoreUISession sharedGlobal].enableHandScrollAnimation) {
|
|
self.isPageTypeEnabledForHandScrollAnimation = [self.loadObject.pageJSON boolForKey:KeyHandScrollAnimation];
|
|
}
|
|
}
|
|
|
|
- (void)addScrollDownHandAnimation {
|
|
|
|
if (!self.gifView) {
|
|
|
|
MFTransparentGIFView *gifView = [[MFTransparentGIFView alloc] initWithFrame:CGRectZero ImageName:KeyHandScroll StartImmediately:YES Duration:-1 LoopCompletionBlock:nil];
|
|
gifView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.view addSubview:gifView];
|
|
gifView.contentMode = UIViewContentModeScaleAspectFit;
|
|
|
|
// width is changed to match aspect ratio of asset
|
|
[NSLayoutConstraint constraintPinView:gifView heightConstraint:YES heightConstant:50 widthConstraint:YES widthConstant:25];
|
|
[NSLayoutConstraint constraintPinSubview:gifView pinCenterX:YES pinCenterY:NO];
|
|
[NSLayoutConstraint constraintWithItem:gifView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeBottom multiplier:1.f constant:-30].active = YES;
|
|
|
|
self.gifView = gifView;
|
|
}
|
|
}
|
|
|
|
- (BOOL)isHandScrollAnimationRequired {
|
|
return (self.scrollView.contentSize.height - self.scrollView.frame.size.height) > HandScrollMargin;
|
|
}
|
|
|
|
- (void)startHandScrollAnimation {
|
|
|
|
if (self.isHandScrollAnimationRendered) {
|
|
return;
|
|
}
|
|
|
|
self.isHandScrollAnimationRendered = YES;
|
|
|
|
self.gifView.alpha = 1.f;
|
|
__weak MFScrollingViewController *weakSelf = self;
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(HandScrollAnimationTiming * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
[weakSelf stopHandScrollAnimation:YES];
|
|
});
|
|
|
|
}
|
|
|
|
- (void)stopHandScrollAnimation:(BOOL)animated {
|
|
|
|
__weak MFScrollingViewController *weakSelf = self;
|
|
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
|
if (animated) {
|
|
[UIView animateWithDuration:0.5 animations:^{
|
|
weakSelf.gifView.alpha = 0.f;
|
|
} completion:nil];
|
|
} else {
|
|
weakSelf.gifView.alpha = 0.f;
|
|
}
|
|
}];
|
|
}
|
|
|
|
@end
|