mattermost-mobile/libraries/@mattermost/keyboard-tracker/ios/Views/KeyboardTrackingView.m
Elias Nahum b8c088cc70
Upgrade RN as well as update or replace other dependencies (#8011)
* start upgrade to RN 74

* migrate react-native-fs to expo-file-system

* exclude expo-asset module

* fix database manager remove directory

* fix: android network helper

* include expo on android

* temporarily disable android dep lock

* replace react-native-create-thumbnail with expo-video-thumnails

* update patches file version

* fix android build on 74

* create local library to replace MattermostManaged, Notifications and SplitView modules with new arch support

* migrate app to use new mattermost-rnutils library

* remove unused flipper class for android unsigned

* fix mattermost-rnutils android foldedObserver lifecycle

* use mattermost-rnutils on Android

* use mattermost-rnutils on iOS

* path react-native-navigation to not crash when activity is not NavigationActivity

* create local library for android share extension with new arch support

* Replace ShareModule with @mattermost/rnshare library

* remove ShareModule from android native code

* update react-intl

* update nodejs to 20.x.x npm to 10.x.x and dev deps

* update @gorhom/bottom-sheet

* use MattermostShare conditionally based on the platform

* update @react-native-camera-roll/camera-roll

* remove unused react-native-calendars

* fix metro config

* fix terminate session race condition

* remove unused analytics

* replace react-native-device-info with expo-application and expo-device

* update @react-native-clipboard/clipboard

* update @react-native-community/datetimepicker

* update @react-native-community/netinfo

* update @sentry/react-native

* update react-native-document-picker

* update react-native-gesture-handler

* update react-native-share

* update react-native-svg and react-native-svg-transformer

* update react-native-vector-icons

* update babel

* update react-native-shadow-2

* update semver

* remove react-native-svg-transformer and convert svg files to svg components

* fix @mattermost/rnshare new-arch build on android

* remove react-native-create-thumbnail resolution in build.gradle

* create @mattermost/hardware-keyboard library to replace hw-keyboard-events

* fix hardware-keyboard library

* fix rnutils library

* create @mattermost/keyboard-tracker library

* replace react-native-keyboard-tracking-view with @mattermost/keyboard-tracker

* fix: rnutils to not crash on lateinit context

* fix: rnutils delete database

* revert changes to session_manager

* Removed react-native-webview and added expo-web-browser instead

With expo-web-browser we no longer need the webview for SSO login
the SSO login is now done by using "custom Chrome tabs" on Android
and ASWebAuthenticationSession on iOS

* remove patch for react-native-keyboard-tracking-view

* replace react-native-linear-gradient with expo-linear-gradient

* replace react-native-in-app-review with expo-store-review

* fix: shared group database directory on ios

* replace react-native-fast-image with expo-image

* remove unusued type def for react-native-keyboard-tracking-view

* replace react-native-elements and react-native-button with @rneui

* node version

* update sizzling methods

* fix tests using jest-expo

* replace jail-monkey with expo-device

* update babel deps

* update typescript eslint

* update rn and expo

* react-native-document-picker @react-native-camera-roll/camera-roll @react-native-community/datetimepicker react-native-reanimated react-native-safe-area-context

* update patches

* update @sentry/react-native

* upgrade react-native-navigation

* update expo & expo-image

* upgrade to working version of @sentry/react-native

* update node, cocoapods, bundler, fastlane versions

* @testing-library/react-native and eslint-plugin-jest

* fix: FloatingTextInput causing a crash with reanimated

* update sentry, localize, @types/lodash and uuid

* fix floating text input label

* update react-native-video

* fix: cannot calculate shadow efficiently on some components

* fix: reduce motion warning for bottomSheet

* fix: shadow on YouTube component

* update react-native-webrtc expo and @typescript-eslint

* audit fix

* fix swizzling bad merge

* temp use of github dependency for @mattermost libraries

* feedback review

* feedback review

* npm audit fix

* update bundle deps

* update @mattermost/react-native-turbo-log

* update deps
2024-06-19 09:33:45 +08:00

622 lines
21 KiB
Objective-C

#import "KeyboardTrackingView.h"
#import <React/RCTUIManager.h>
#import <React/RCTScrollView.h>
#import "UIResponder+FirstResponder.h"
#import "ObservingInputAccessoryView.h"
#import <objc/runtime.h>
NSUInteger const kInputViewKey = 101010;
NSUInteger const kMaxDeferedInitializeAccessoryViews = 15;
NSInteger const kTrackingViewNotFoundErrorCode = 1;
NSInteger const kBottomViewHeight = 34;
@interface KeyboardTrackingView () <ObservingInputAccessoryViewDelegate, UIScrollViewDelegate>
@end
@implementation KeyboardTrackingView
-(instancetype)init
{
self = [super init];
if (self)
{
[self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
_inputViewsMap = [NSMapTable weakToWeakObjectsMapTable];
_deferedInitializeAccessoryViewsCount = 0;
_rctScrollViewsArray = [[NSMutableDictionary alloc] init];
_observingInputAccessoryView = [ObservingInputAccessoryView new];
_observingInputAccessoryView.delegate = self;
_initialOffsetY = 0;
_initialOffsetIsSet = NO;
_viewInitialOffsetY = 0;
_manageScrollView = YES;
_allowHitsOutsideBounds = NO;
_requiresSameParentToManageScrollView = YES;
_bottomViewHeight = kBottomViewHeight;
self.addBottomView = NO;
self.scrollToFocusedInput = NO;
self.paused = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rctContentDidAppearNotification:) name:RCTContentDidAppearNotification object:nil];
}
return self;
}
-(RCTRootView*)getRootView
{
UIView *view = self;
while (view.superview != nil)
{
view = view.superview;
if ([view isKindOfClass:[RCTRootView class]])
break;
}
if ([view isKindOfClass:[RCTRootView class]])
{
return (RCTRootView*)view;
}
return nil;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!_allowHitsOutsideBounds) {
return [super hitTest:point withEvent:event];
}
if (self.isHidden || self.alpha == 0 || self.clipsToBounds) {
return nil;
}
UIView *subview = [super hitTest:point withEvent:event];
if (subview == nil) {
NSArray<UIView*>* allSubviews = [self getBreadthFirstSubviewsForView:self];
for (UIView *tmpSubview in allSubviews) {
CGPoint pointInSubview = [self convertPoint:point toView:tmpSubview];
if ([tmpSubview pointInside:pointInSubview withEvent:event]) {
subview = tmpSubview;
break;
}
}
}
return subview;
}
-(void)layoutSubviews
{
[super layoutSubviews];
[self updateBottomViewFrame];
}
- (void)initializeAccessoryViewsAndHandleInsets
{
NSArray<UIView*>* allSubviews = [self getBreadthFirstSubviewsForView:[self getRootView]];
for (UIView* subview in allSubviews)
{
if(subview.nativeID) {
NSLog(@"self.accessoriesContainerID %@ %@", self.accessoriesContainerID, subview.nativeID);
}
if (subview.nativeID && [subview.nativeID isEqualToString:self.accessoriesContainerID]) {
NSLog(@"SuperView ID: %@", subview.nativeID);
_accessoriesContainer = subview;
}
if(_manageScrollView)
{
if(_scrollViewToManage == nil)
{
if([subview isKindOfClass:[RCTScrollView class]])
{
RCTScrollView *scrollView = (RCTScrollView*)subview;
if (subview.nativeID && [subview.nativeID isEqualToString:self.scrollViewNativeID]) {
[_rctScrollViewsArray setObject:scrollView forKey:subview.nativeID];
_scrollViewToManage = scrollView.scrollView;
}
}
}
}
if ([subview isKindOfClass:NSClassFromString(@"RCTTextField")])
{
UITextField *textField = nil;
Ivar backedTextInputIvar = class_getInstanceVariable([subview class], "_backedTextInput");
if (backedTextInputIvar != NULL)
{
textField = [subview valueForKey:@"_backedTextInput"];
}
else if([subview isKindOfClass:[UITextField class]])
{
textField = (UITextField*)subview;
}
[self setupTextField:textField];
}
else if ([subview isKindOfClass:NSClassFromString(@"RCTUITextField")] && [subview isKindOfClass:[UITextField class]])
{
[self setupTextField:(UITextField*)subview];
}
else if ([subview isKindOfClass:NSClassFromString(@"RCTMultilineTextInputView")])
{
[self setupTextView:[subview valueForKey:@"_backedTextInputView"]];
}
else if ([subview isKindOfClass:NSClassFromString(@"RCTTextView")])
{
UITextView *textView = nil;
Ivar backedTextInputIvar = class_getInstanceVariable([subview class], "_backedTextInput");
if (backedTextInputIvar != NULL)
{
textView = [subview valueForKey:@"_backedTextInput"];
}
else if([subview isKindOfClass:[UITextView class]])
{
textView = (UITextView*)subview;
}
[self setupTextView:textView];
}
else if ([subview isKindOfClass:NSClassFromString(@"RCTUITextView")] && [subview isKindOfClass:[UITextView class]])
{
[self setupTextView:(UITextView*)subview];
}
}
for (RCTScrollView *scrollView in [_rctScrollViewsArray allValues])
{
if(scrollView.scrollView == _scrollViewToManage)
{
[scrollView removeScrollListener:self];
[scrollView addScrollListener:self];
break;
}
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_3
if (@available(iOS 11.0, *)) {
if (_scrollViewToManage != nil) {
_scrollViewToManage.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
#endif
[self _updateScrollViewInsets];
_originalHeight = _observingInputAccessoryView.height;
[self addBottomViewIfNecessary];
}
- (void)resetScrollView:(NSString*) scrollViewNativeID {
for (RCTScrollView *scrollView in [_rctScrollViewsArray allValues])
{
[scrollView removeScrollListener:self];
}
[_rctScrollViewsArray removeAllObjects];
_scrollViewToManage = nil;
_scrollViewNativeID = scrollViewNativeID;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self deferedInitializeAccessoryViewsAndHandleInsets];
[self scrollToStart];
});
}
-(void)pauseTracking:(NSString*) scrollViewNativeID {
if ([self.scrollViewNativeID isEqualToString:scrollViewNativeID]) {
_observingInputAccessoryView.delegate = nil;
self.scrollViewToManage = nil;
self.accessoriesContainer = nil;
self.paused = YES;
}
}
-(void)resumeTracking:(NSString*) scrollViewNativeID {
if ([self.scrollViewNativeID isEqualToString:scrollViewNativeID]) {
self.paused = NO;
_observingInputAccessoryView.delegate = self;
[self deferedInitializeAccessoryViewsAndHandleInsets];
}
}
- (void)setupTextView:(UITextView*)textView
{
if (textView != nil)
{
[textView setInputAccessoryView:_observingInputAccessoryView];
[textView reloadInputViews];
[_inputViewsMap setObject:textView forKey:@(kInputViewKey)];
}
}
- (void)setupTextField:(UITextField*)textField
{
if (textField != nil)
{
[textField setInputAccessoryView:_observingInputAccessoryView];
[textField reloadInputViews];
[_inputViewsMap setObject:textField forKey:@(kInputViewKey)];
}
}
-(void) deferedInitializeAccessoryViewsAndHandleInsets
{
if(self.window == nil)
{
return;
}
if (_observingInputAccessoryView.height == 0 && self.deferedInitializeAccessoryViewsCount < kMaxDeferedInitializeAccessoryViews)
{
self.deferedInitializeAccessoryViewsCount++;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self deferedInitializeAccessoryViewsAndHandleInsets];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[self initializeAccessoryViewsAndHandleInsets];
});
}
}
- (void)willMoveToWindow:(nullable UIWindow *)newWindow
{
if (newWindow == nil && [ObservingInputAccessoryViewManager sharedInstance].activeObservingInputAccessoryView == _observingInputAccessoryView)
{
[ObservingInputAccessoryViewManager sharedInstance].activeObservingInputAccessoryView = nil;
}
else if (newWindow != nil)
{
[ObservingInputAccessoryViewManager sharedInstance].activeObservingInputAccessoryView = _observingInputAccessoryView;
}
}
-(void)didMoveToWindow
{
[super didMoveToWindow];
self.deferedInitializeAccessoryViewsCount = 0;
[self deferedInitializeAccessoryViewsAndHandleInsets];
}
-(void)dealloc
{
[self removeObserver:self forKeyPath:@"bounds"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if (self.paused) {
return;
}
_observingInputAccessoryView.height = self.bounds.size.height;
}
- (void)observingInputAccessoryViewKeyboardWillDisappear:(ObservingInputAccessoryView *)observingInputAccessoryView
{
if (self.paused) {
return;
}
_bottomViewHeight = [self getBottomSafeArea];
[self updateBottomViewFrame];
}
- (NSArray*)getBreadthFirstSubviewsForView:(UIView*)view
{
if(view == nil)
{
return nil;
}
NSMutableArray *allSubviews = [NSMutableArray new];
NSMutableArray *queue = [NSMutableArray new];
[allSubviews addObject:view];
[queue addObject:view];
while ([queue count] > 0) {
UIView *current = [queue lastObject];
[queue removeLastObject];
for (UIView *n in current.subviews)
{
[allSubviews addObject:n];
[queue insertObject:n atIndex:0];
}
}
return allSubviews;
}
- (NSArray*)getAllReactSubviewsForView:(UIView*)view
{
NSMutableArray *allSubviews = [NSMutableArray new];
for (UIView *subview in view.reactSubviews)
{
[allSubviews addObject:subview];
[allSubviews addObjectsFromArray:[self getAllReactSubviewsForView:subview]];
}
return allSubviews;
}
- (void)_updateScrollViewInsets
{
if(self.scrollViewToManage != nil && !self.paused)
{
if (!_initialOffsetIsSet) {
self.initialOffsetY = self.scrollViewToManage.contentOffset.y;
_initialOffsetIsSet = YES;
}
if (_observingInputAccessoryView.keyboardState != KeyboardStateWillHide && _observingInputAccessoryView.keyboardState != KeyboardStateHidden) {
[self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, self.initialOffsetY) animated:NO];
}
UIEdgeInsets insets = self.scrollViewToManage.contentInset;
CGFloat bottomSafeArea = [self getBottomSafeArea];
CGFloat bottomInset = MAX(self.bounds.size.height, _observingInputAccessoryView.keyboardHeight + _observingInputAccessoryView.height);
CGFloat originalBottomInset = self.scrollIsInverted ? insets.top : insets.bottom;
CGPoint originalOffset = self.scrollViewToManage.contentOffset;
CGFloat keyboardHeight = _observingInputAccessoryView.keyboardHeight;
bottomInset += (keyboardHeight == 0 ? bottomSafeArea : 0);
if(self.scrollIsInverted)
{
insets.top = bottomInset;
}
else
{
insets.bottom = keyboardHeight;
}
self.scrollViewToManage.contentInset = insets;
if(self.scrollBehavior == KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly && _scrollIsInverted)
{
BOOL firstTime = _observingInputAccessoryView.keyboardHeight == 0 && _observingInputAccessoryView.keyboardState == KeyboardStateHidden;
BOOL willOpen = _observingInputAccessoryView.keyboardHeight != 0 && _observingInputAccessoryView.keyboardState == KeyboardStateHidden;
BOOL isOpen = _observingInputAccessoryView.keyboardHeight != 0 && _observingInputAccessoryView.keyboardState == KeyboardStateShown;
if(firstTime || willOpen || (isOpen && !self.isDraggingScrollView))
{
[self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, self.scrollViewToManage.contentOffset.y) animated:!firstTime];
}
}
else if(self.scrollBehavior == KeyboardTrackingScrollBehaviorFixedOffset && !self.isDraggingScrollView)
{
CGFloat insetsDiff = (bottomInset - originalBottomInset) * (self.scrollIsInverted ? -1 : 1);
self.scrollViewToManage.contentOffset = CGPointMake(originalOffset.x, originalOffset.y + insetsDiff);
}
CGFloat kHeight = _observingInputAccessoryView.keyboardHeight;
if (kHeight != 0 && (_observingInputAccessoryView.keyboardState == KeyboardStateShown || _observingInputAccessoryView.keyboardState == KeyboardStateWillShow)) {
kHeight -= (bottomSafeArea + _viewInitialOffsetY);
}
CGFloat positionY = self.normalList ? 0 : kHeight;
CGRect frame = CGRectMake(self.scrollViewToManage.frame.origin.x, positionY,
self.scrollViewToManage.frame.size.width, self.scrollViewToManage.frame.size.height);
self.scrollViewToManage.frame = frame;
if (self.accessoriesContainer) {
CGFloat containerPositionY = self.normalList ? 0 : kHeight;
self.accessoriesContainer.bounds = CGRectMake(self.accessoriesContainer.bounds.origin.x, containerPositionY,
self.accessoriesContainer.bounds.size.width, self.accessoriesContainer.bounds.size.height);
}
}
}
#pragma mark - bottom view
-(void)setAddBottomView:(BOOL)addBottomView
{
_addBottomView = addBottomView;
[self addBottomViewIfNecessary];
}
-(void)addBottomViewIfNecessary
{
if (self.addBottomView && _bottomView == nil)
{
_bottomView = [UIView new];
[self addSubview:_bottomView];
[self updateBottomViewFrame];
}
else if (!self.addBottomView && _bottomView != nil)
{
[_bottomView removeFromSuperview];
_bottomView = nil;
}
}
-(void)updateBottomViewFrame
{
if (_bottomView != nil)
{
_bottomView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, _bottomViewHeight);
}
}
-(void)setBottomViewBackgroundColor:(UIColor*) color {
if (_bottomView != nil) {
_bottomView.backgroundColor = color;
}
}
#pragma mark - safe area
-(void)safeAreaInsetsDidChange
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_3
if (@available(iOS 11.0, *)) {
[super safeAreaInsetsDidChange];
}
#endif
[self updateTransformAndInsets];
}
-(CGFloat)getBottomSafeArea
{
CGFloat bottomSafeArea = 0;
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_3
if (@available(iOS 11.0, *)) {
bottomSafeArea = self.superview ? self.superview.safeAreaInsets.bottom : self.safeAreaInsets.bottom;
}
#endif
return bottomSafeArea;
}
#pragma RCTRootView notifications
- (void) rctContentDidAppearNotification:(NSNotification*)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
if(notification.object == [self getRootView] && self->_manageScrollView && self->_scrollViewToManage == nil)
{
[self initializeAccessoryViewsAndHandleInsets];
}
});
}
#pragma mark - ObservingInputAccessoryViewDelegate methods
-(void)updateTransformAndInsets
{
if (self.paused) {
return;
}
CGFloat bottomSafeArea = [self getBottomSafeArea];
CGFloat accessoryTranslation = MIN(-bottomSafeArea, -(_observingInputAccessoryView.keyboardHeight - _viewInitialOffsetY));
if (_observingInputAccessoryView.keyboardHeight <= bottomSafeArea) {
_bottomViewHeight = [self getBottomSafeArea];
} else if (_observingInputAccessoryView.keyboardState != KeyboardStateWillHide) {
_bottomViewHeight = 0;
}
[self updateBottomViewFrame];
self.transform = CGAffineTransformMakeTranslation(0, accessoryTranslation);
[self _updateScrollViewInsets];
}
- (void)performScrollToFocusedInput
{
if (_scrollViewToManage != nil && self.scrollToFocusedInput)
{
UIResponder *currentFirstResponder = [UIResponder currentFirstResponder];
if (currentFirstResponder != nil && [currentFirstResponder isKindOfClass:[UIView class]])
{
UIView *reponderView = (UIView*)currentFirstResponder;
if ([reponderView isDescendantOfView:_scrollViewToManage])
{
CGRect frame = [_scrollViewToManage convertRect:reponderView.frame fromView:reponderView];
frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height + 20);
[_scrollViewToManage scrollRectToVisible:frame animated:NO];
}
}
}
}
- (void)observingInputAccessoryViewDidChangeFrame:(ObservingInputAccessoryView*)observingInputAccessoryView
{
[self updateTransformAndInsets];
}
-(void) observingInputAccessoryViewKeyboardWillAppear:(ObservingInputAccessoryView *)observingInputAccessoryView keyboardDelta:(CGFloat)keyboardDelta animationDuration:(double)animationDuration keyboardFrameEndHeight:(CGFloat)keyboardFrameEndHeight {
if (observingInputAccessoryView.keyboardHeight > 0) //prevent hiding the bottom view if an external keyboard is in use
{
_bottomViewHeight = 0;
[self updateBottomViewFrame];
}
[self performScrollToFocusedInput];
_onKeyboardWillShow(@{
@"trackingViewHeight": [NSNumber numberWithDouble:self.bounds.size.height],
@"keyboardHeight": [NSNumber numberWithDouble:[self getKeyboardHeight]],
@"contentTopInset": [NSNumber numberWithDouble:[self getScrollViewTopContentInset]],
@"animationDuration": [NSNumber numberWithDouble:animationDuration * 1000],
@"keyboardFrameEndHeight": [NSNumber numberWithDouble:keyboardFrameEndHeight],
});
}
#pragma mark - UIScrollViewDelegate methods
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if(_observingInputAccessoryView.keyboardState != KeyboardStateHidden || !self.revealKeyboardInteractive)
{
return;
}
UIView *inputView = [_inputViewsMap objectForKey:@(kInputViewKey)];
if (inputView != nil && scrollView.contentOffset.y * (self.scrollIsInverted ? -1 : 1) > (self.scrollIsInverted ? scrollView.contentInset.top : scrollView.contentInset.bottom) + 50 && ![inputView isFirstResponder])
{
for (UIGestureRecognizer *gesture in scrollView.gestureRecognizers)
{
if([gesture isKindOfClass:[UIPanGestureRecognizer class]])
{
gesture.enabled = NO;
gesture.enabled = YES;
}
}
[inputView reactFocus];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.isDraggingScrollView = YES;
_initialOffsetIsSet = NO;
self.initialOffsetY = scrollView.contentOffset.y;
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
self.isDraggingScrollView = NO;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
self.isDraggingScrollView = NO;
self.initialOffsetY = scrollView.contentOffset.y;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
self.initialOffsetY = scrollView.contentOffset.y;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.initialOffsetY = scrollView.contentOffset.y;
}
- (CGFloat)getKeyboardHeight
{
return _observingInputAccessoryView ? _observingInputAccessoryView.keyboardHeight : 0;
}
-(CGFloat)getScrollViewTopContentInset
{
return (self.scrollViewToManage != nil) ? -self.scrollViewToManage.contentInset.top : 0;
}
-(void)scrollToStart
{
if (self.scrollViewToManage != nil)
{
[self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, -self.scrollViewToManage.contentInset.top) animated:YES];
}
}
@end