mirror of
https://github.com/HackPlan/IQKeyboardManager.git
synced 2026-03-29 08:47:51 +08:00
1736 lines
74 KiB
Objective-C
Executable File
1736 lines
74 KiB
Objective-C
Executable File
//
|
|
// IQKeyboardManager.m
|
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
|
// Copyright (c) 2013-15 Iftekhar Qurashi.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
#import "IQKeyboardManager.h"
|
|
#import "IQUIView+Hierarchy.h"
|
|
#import "IQUIView+IQKeyboardToolbar.h"
|
|
#import "IQUIWindow+Hierarchy.h"
|
|
#import "IQNSArray+Sort.h"
|
|
#import "IQToolbar.h"
|
|
#import "IQBarButtonItem.h"
|
|
#import "IQKeyboardManagerConstantsInternal.h"
|
|
|
|
#import <UIKit/UINavigationBar.h>
|
|
#import <UIKit/UITapGestureRecognizer.h>
|
|
#import <UIKit/UITextField.h>
|
|
#import <UIKit/UITextView.h>
|
|
#import <UIKit/UITableViewController.h>
|
|
#import <UIKit/UINavigationController.h>
|
|
#import <UIKit/UITableView.h>
|
|
#import <UIKit/UITouch.h>
|
|
|
|
#ifdef NSFoundationVersionNumber_iOS_5_1
|
|
#import <UIKit/UICollectionView.h>
|
|
#import <UIKit/NSLayoutConstraint.h>
|
|
#endif
|
|
|
|
NSInteger const kIQDoneButtonToolbarTag = -1002;
|
|
NSInteger const kIQPreviousNextButtonToolbarTag = -1005;
|
|
|
|
void _IQShowLog(NSString *logString);
|
|
|
|
@interface IQKeyboardManager()<UIGestureRecognizerDelegate>
|
|
|
|
// Private helper methods
|
|
- (void)adjustFrame;
|
|
|
|
// Private function to manipulate RootViewController's frame with animation.
|
|
- (void)setRootViewFrame:(CGRect)frame;
|
|
|
|
// Keyboard Notification methods
|
|
- (void)keyboardWillShow:(NSNotification*)aNotification;
|
|
- (void)keyboardWillHide:(NSNotification*)aNotification;
|
|
- (void)keyboardDidHide:(NSNotification*)aNotification;
|
|
|
|
// UITextField/UITextView Notification methods
|
|
- (void)textFieldViewDidBeginEditing:(NSNotification*)notification;
|
|
- (void)textFieldViewDidEndEditing:(NSNotification*)notification;
|
|
- (void)textFieldViewDidChange:(NSNotification*)notification;
|
|
|
|
// Rotation notification
|
|
- (void)willChangeStatusBarOrientation:(NSNotification*)aNotification;
|
|
|
|
// Tap Recognizer
|
|
- (void)tapRecognized:(UITapGestureRecognizer*)gesture;
|
|
|
|
// Next/Previous/Done methods
|
|
-(void)previousAction:(id)segmentedControl;
|
|
-(void)nextAction:(id)segmentedControl;
|
|
-(void)doneAction:(IQBarButtonItem*)barButton;
|
|
|
|
// Adding Removing IQToolbar methods
|
|
- (void)addToolbarIfRequired;
|
|
- (void)removeToolbarIfRequired;
|
|
|
|
@end
|
|
|
|
@implementation IQKeyboardManager
|
|
{
|
|
@package
|
|
/*******************************************/
|
|
|
|
/** To save UITextField/UITextView object voa textField/textView notifications. */
|
|
__weak UIView *_textFieldView;
|
|
|
|
/** used with canAdjustTextView boolean. */
|
|
__block CGRect _textFieldViewIntialFrame;
|
|
|
|
/** To save rootViewController.view.frame. */
|
|
CGRect _topViewBeginRect;
|
|
|
|
/** To save rootViewController */
|
|
__weak UIViewController *_rootViewController;
|
|
|
|
/*******************************************/
|
|
|
|
/** Variable to save lastScrollView that was scrolled. */
|
|
__weak UIScrollView *_lastScrollView;
|
|
|
|
/** LastScrollView's initial contentInsets. */
|
|
UIEdgeInsets _startingContentInsets;
|
|
|
|
/** LastScrollView's initial contentOffset. */
|
|
CGPoint _startingContentOffset;
|
|
|
|
/*******************************************/
|
|
|
|
/** To save keyboardWillShowNotification. Needed for enable keyboard functionality. */
|
|
NSNotification *_kbShowNotification;
|
|
|
|
/** To save keyboard size. */
|
|
CGSize _kbSize;
|
|
|
|
/** To save keyboard animation duration. */
|
|
CGFloat _animationDuration;
|
|
|
|
/** To mimic the keyboard animation */
|
|
NSInteger _animationCurve;
|
|
|
|
/*******************************************/
|
|
|
|
/** TapGesture to resign keyboard on view's touch. */
|
|
UITapGestureRecognizer *_tapGesture;
|
|
|
|
/*******************************************/
|
|
|
|
/** Default toolbar tintColor to be used within the project. Default is black. */
|
|
UIColor *_defaultToolbarTintColor;
|
|
|
|
/*******************************************/
|
|
|
|
/** Set of restricted classes for library */
|
|
NSMutableSet *_disabledClasses;
|
|
|
|
/** Set of restricted classes for adding toolbar */
|
|
NSMutableSet *_disabledToolbarClasses;
|
|
|
|
/** Set of permitted classes to add all inner textField as siblings */
|
|
NSMutableSet *_toolbarPreviousNextConsideredClass;
|
|
|
|
/*******************************************/
|
|
|
|
struct {
|
|
/** used with canAdjustTextView to detect a textFieldView frame is changes or not. (Bug ID: #92)*/
|
|
unsigned int isTextFieldViewFrameChanged:1;
|
|
|
|
/** Boolean to maintain keyboard is showing or it is hide. To solve rootViewController.view.frame calculations. */
|
|
unsigned int isKeyboardShowing:1;
|
|
|
|
} _keyboardManagerFlags;
|
|
}
|
|
|
|
//UIKeyboard handling
|
|
@synthesize enable = _enable;
|
|
@synthesize keyboardDistanceFromTextField = _keyboardDistanceFromTextField;
|
|
@synthesize preventShowingBottomBlankSpace = _preventShowingBottomBlankSpace;
|
|
|
|
//Keyboard Appearance handling
|
|
@synthesize overrideKeyboardAppearance = _overrideKeyboardAppearance;
|
|
@synthesize keyboardAppearance = _keyboardAppearance;
|
|
|
|
//IQToolbar handling
|
|
@synthesize enableAutoToolbar = _enableAutoToolbar;
|
|
@synthesize toolbarManageBehaviour = _toolbarManageBehaviour;
|
|
|
|
#ifdef NSFoundationVersionNumber_iOS_6_1
|
|
@synthesize shouldToolbarUsesTextFieldTintColor = _shouldToolbarUsesTextFieldTintColor;
|
|
#endif
|
|
|
|
@synthesize shouldShowTextFieldPlaceholder = _shouldShowTextFieldPlaceholder;
|
|
@synthesize placeholderFont = _placeholderFont;
|
|
|
|
//TextView handling
|
|
@synthesize canAdjustTextView = _canAdjustTextView;
|
|
@synthesize shouldFixTextViewClip = _shouldFixTextViewClip;
|
|
|
|
//Resign handling
|
|
@synthesize shouldResignOnTouchOutside = _shouldResignOnTouchOutside;
|
|
|
|
//Sound handling
|
|
@synthesize shouldPlayInputClicks = _shouldPlayInputClicks;
|
|
|
|
//Animation handling
|
|
@synthesize shouldAdoptDefaultKeyboardAnimation = _shouldAdoptDefaultKeyboardAnimation;
|
|
|
|
//ScrollView handling
|
|
@synthesize shouldRestoreScrollViewContentOffset= _shouldRestoreScrollViewContentOffset;
|
|
|
|
#pragma mark - Initializing functions
|
|
|
|
/** Override +load method to enable KeyboardManager when class loader load IQKeyboardManager. Enabling when app starts (No need to write any code) */
|
|
+(void)load
|
|
{
|
|
[super load];
|
|
|
|
//Enabling IQKeyboardManager.
|
|
[[IQKeyboardManager sharedManager] setEnable:YES];
|
|
}
|
|
|
|
/* Singleton Object Initialization. */
|
|
-(instancetype)init
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
// Registering for keyboard notification.
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
|
|
|
|
// Registering for textField notification.
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
|
|
|
|
// Registering for textView notification.
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidEndEditing:) name:UITextViewTextDidEndEditingNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidChange:) name:UITextViewTextDidChangeNotification object:nil];
|
|
|
|
// Registering for orientation changes notification
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willChangeStatusBarOrientation:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
|
|
|
|
//Creating gesture for @shouldResignOnTouchOutside. (Enhancement ID: #14)
|
|
_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized:)];
|
|
[_tapGesture setDelegate:self];
|
|
|
|
// Default settings
|
|
[self setKeyboardDistanceFromTextField:10.0];
|
|
_animationDuration = 0.25;
|
|
|
|
//Setting it's initial values
|
|
_enable = NO;
|
|
_defaultToolbarTintColor = [UIColor blackColor];
|
|
[self setCanAdjustTextView:NO];
|
|
[self setShouldPlayInputClicks:NO];
|
|
[self setShouldResignOnTouchOutside:NO];
|
|
[self setOverrideKeyboardAppearance:NO];
|
|
[self setKeyboardAppearance:UIKeyboardAppearanceDefault];
|
|
|
|
[self setEnableAutoToolbar:YES];
|
|
[self setShouldFixTextViewClip:YES];
|
|
[self setPreventShowingBottomBlankSpace:YES];
|
|
[self setShouldShowTextFieldPlaceholder:YES];
|
|
[self setShouldAdoptDefaultKeyboardAnimation:YES];
|
|
[self setShouldRestoreScrollViewContentOffset:NO];
|
|
[self setToolbarManageBehaviour:IQAutoToolbarBySubviews];
|
|
|
|
//Initializing disabled classes Set.
|
|
_disabledClasses = [[NSMutableSet alloc] initWithObjects:[UITableViewController class], nil];
|
|
_disabledToolbarClasses = [[NSMutableSet alloc] init];
|
|
|
|
#ifdef NSFoundationVersionNumber_iOS_6_1
|
|
[self setShouldToolbarUsesTextFieldTintColor:NO];
|
|
#endif
|
|
|
|
#ifdef NSFoundationVersionNumber_iOS_5_1
|
|
_toolbarPreviousNextConsideredClass = [[NSMutableSet alloc] initWithObjects:[UITableView class],[UICollectionView class], nil];
|
|
#else
|
|
_toolbarPreviousNextConsideredClass = [[NSMutableSet alloc] initWithObjects:[UITableView class], nil];
|
|
#endif
|
|
|
|
});
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/* Automatically called from the `+(void)load` method. */
|
|
+ (instancetype)sharedManager
|
|
{
|
|
//Singleton instance
|
|
static IQKeyboardManager *kbManager;
|
|
|
|
//Dispatching it once.
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
// Initializing keyboard manger.
|
|
kbManager = [[self alloc] init];
|
|
});
|
|
|
|
//Returning kbManager.
|
|
return kbManager;
|
|
}
|
|
|
|
#pragma mark - Dealloc
|
|
-(void)dealloc
|
|
{
|
|
// Disable the keyboard manager.
|
|
[self setEnable:NO];
|
|
|
|
//Removing notification observers on dealloc.
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
#pragma mark - Property functions
|
|
-(void)setEnable:(BOOL)enable
|
|
{
|
|
// If not enabled, enable it.
|
|
if (enable == YES && _enable == NO)
|
|
{
|
|
//Setting NO to _enable.
|
|
_enable = enable;
|
|
|
|
//If keyboard is currently showing. Sending a fake notification for keyboardWillShow to adjust view according to keyboard.
|
|
if (_kbShowNotification) [self keyboardWillShow:_kbShowNotification];
|
|
|
|
_IQShowLog(IQLocalizedString(@"enabled", nil));
|
|
}
|
|
//If not disable, desable it.
|
|
else if (enable == NO && _enable == YES)
|
|
{
|
|
//Sending a fake notification for keyboardWillHide to retain view's original frame.
|
|
[self keyboardWillHide:nil];
|
|
|
|
//Setting NO to _enable.
|
|
_enable = enable;
|
|
|
|
_IQShowLog(IQLocalizedString(@"disabled", nil));
|
|
}
|
|
//If already disabled.
|
|
else if (enable == NO && _enable == NO)
|
|
{
|
|
_IQShowLog(IQLocalizedString(@"already disabled", nil));
|
|
}
|
|
//If already enabled.
|
|
else if (enable == YES && _enable == YES)
|
|
{
|
|
_IQShowLog(IQLocalizedString(@"already enabled", nil));
|
|
}
|
|
}
|
|
|
|
// Setting keyboard distance from text field.
|
|
-(void)setKeyboardDistanceFromTextField:(CGFloat)keyboardDistanceFromTextField
|
|
{
|
|
//Can't be less than zero. Minimum is zero.
|
|
_keyboardDistanceFromTextField = MAX(keyboardDistanceFromTextField, 0);
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"keyboardDistanceFromTextField: %.2f",_keyboardDistanceFromTextField]);
|
|
}
|
|
|
|
/** Enabling/disable gesture on touching. */
|
|
-(void)setShouldResignOnTouchOutside:(BOOL)shouldResignOnTouchOutside
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"shouldResignOnTouchOutside: %@",shouldResignOnTouchOutside?@"Yes":@"No"]);
|
|
|
|
_shouldResignOnTouchOutside = shouldResignOnTouchOutside;
|
|
|
|
//Enable/Disable gesture recognizer (Enhancement ID: #14)
|
|
[_tapGesture setEnabled:_shouldResignOnTouchOutside];
|
|
}
|
|
|
|
/** Enable/disable autotoolbar. Adding and removing toolbar if required. */
|
|
-(void)setEnableAutoToolbar:(BOOL)enableAutoToolbar
|
|
{
|
|
_enableAutoToolbar = enableAutoToolbar;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"enableAutoToolbar: %@",enableAutoToolbar?@"Yes":@"No"]);
|
|
|
|
//If enabled then adding toolbar.
|
|
if (_enableAutoToolbar == YES)
|
|
{
|
|
[self addToolbarIfRequired];
|
|
}
|
|
//Else removing toolbar.
|
|
else
|
|
{
|
|
[self removeToolbarIfRequired];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Private Methods
|
|
|
|
/** Getting keyWindow. */
|
|
-(UIWindow *)keyWindow
|
|
{
|
|
if (_textFieldView.window)
|
|
{
|
|
return _textFieldView.window;
|
|
}
|
|
else
|
|
{
|
|
static UIWindow *_keyWindow = nil;
|
|
|
|
/* (Bug ID: #23, #25, #73) */
|
|
UIWindow *originalKeyWindow = [[UIApplication sharedApplication] keyWindow];
|
|
|
|
//If original key window is not nil and the cached keywindow is also not original keywindow then changing keywindow.
|
|
if (originalKeyWindow != nil && _keyWindow != originalKeyWindow) _keyWindow = originalKeyWindow;
|
|
|
|
//Return KeyWindow
|
|
return _keyWindow;
|
|
}
|
|
}
|
|
|
|
/* Helper function to manipulate RootViewController's frame with animation. */
|
|
-(void)setRootViewFrame:(CGRect)frame
|
|
{
|
|
// Getting topMost ViewController.
|
|
UIViewController *controller = [_textFieldView topMostController];
|
|
if (controller == nil) controller = [[self keyWindow] topMostController];
|
|
|
|
//frame size needs to be adjusted on iOS8 due to orientation API changes.
|
|
if (IQ_IS_IOS8_OR_GREATER)
|
|
{
|
|
frame.size = controller.view.frame.size;
|
|
}
|
|
|
|
// If can't get rootViewController then printing warning to user.
|
|
if (controller == nil)
|
|
_IQShowLog(IQLocalizedString(@"You must set UIWindow.rootViewController in your AppDelegate to work with IQKeyboardManager", nil));
|
|
|
|
//Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
// Setting it's new frame
|
|
[controller.view setFrame:frame];
|
|
|
|
//Animating content (Bug ID: #160)
|
|
[controller.view setNeedsLayout];
|
|
[controller.view layoutIfNeeded];
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Set %@ frame to : %@",[controller _IQDescription],NSStringFromCGRect(frame)]);
|
|
} completion:NULL];
|
|
}
|
|
|
|
/* Adjusting RootViewController's frame according to device orientation. */
|
|
-(void)adjustFrame
|
|
{
|
|
// We are unable to get textField object while keyboard showing on UIWebView's textField. (Bug ID: #11)
|
|
if (_textFieldView == nil) return;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]);
|
|
|
|
// Boolean to know keyboard is showing/hiding
|
|
_keyboardManagerFlags.isKeyboardShowing = YES;
|
|
|
|
// Getting KeyWindow object.
|
|
UIWindow *keyWindow = [self keyWindow];
|
|
|
|
// Getting RootViewController. (Bug ID: #1, #4)
|
|
UIViewController *rootController = [_textFieldView topMostController];
|
|
if (rootController == nil) rootController = [keyWindow topMostController];
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
//If it's iOS8 then we should do calculations according to portrait orientations. // (Bug ID: #64, #66)
|
|
UIInterfaceOrientation interfaceOrientation = IQ_IS_IOS8_OR_GREATER ? UIInterfaceOrientationPortrait : [rootController interfaceOrientation];
|
|
#pragma GCC diagnostic pop
|
|
|
|
// Converting Rectangle according to window bounds.
|
|
CGRect textFieldViewRect = [[_textFieldView superview] convertRect:_textFieldView.frame toView:keyWindow];
|
|
// Getting RootViewRect.
|
|
CGRect rootViewRect = [[rootController view] frame];
|
|
//Getting statusBarFrame
|
|
CGFloat topLayoutGuide = 0;
|
|
|
|
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
|
|
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
topLayoutGuide = CGRectGetWidth(statusBarFrame);
|
|
break;
|
|
case UIInterfaceOrientationPortrait:
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
topLayoutGuide = CGRectGetHeight(statusBarFrame);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
CGFloat move = 0;
|
|
// Move positive = textField is hidden.
|
|
// Move negative = textField is showing.
|
|
|
|
// Calculating move position. Common for both normal and special cases.
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
move = MIN(CGRectGetMinX(textFieldViewRect)-(topLayoutGuide+5), CGRectGetMaxX(textFieldViewRect)-(CGRectGetWidth(keyWindow.frame)-_kbSize.width));
|
|
break;
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
move = MIN(CGRectGetWidth(keyWindow.frame)-CGRectGetMaxX(textFieldViewRect)-(topLayoutGuide+5), _kbSize.width-CGRectGetMinX(textFieldViewRect));
|
|
break;
|
|
case UIInterfaceOrientationPortrait:
|
|
move = MIN(CGRectGetMinY(textFieldViewRect)-(topLayoutGuide+5), CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-_kbSize.height));
|
|
break;
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
move = MIN(CGRectGetHeight(keyWindow.frame)-CGRectGetMaxY(textFieldViewRect)-(topLayoutGuide+5), _kbSize.height-CGRectGetMinY(textFieldViewRect));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Need to move: %.2f",move]);
|
|
|
|
// Getting it's superScrollView. // (Enhancement ID: #21, #24)
|
|
UIScrollView *superScrollView = (UIScrollView*)[_textFieldView superviewOfClassType:[UIScrollView class]];
|
|
|
|
//If there was a lastScrollView. // (Bug ID: #34)
|
|
if (_lastScrollView)
|
|
{
|
|
//If we can't find current superScrollView, then setting lastScrollView to it's original form.
|
|
if (superScrollView == nil)
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]);
|
|
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
[_lastScrollView setContentInset:_startingContentInsets];
|
|
} completion:NULL];
|
|
|
|
if (_shouldRestoreScrollViewContentOffset)
|
|
{
|
|
[_lastScrollView setContentOffset:_startingContentOffset animated:YES];
|
|
}
|
|
|
|
_startingContentInsets = UIEdgeInsetsZero;
|
|
_startingContentOffset = CGPointZero;
|
|
_lastScrollView = nil;
|
|
}
|
|
//If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
|
|
else if (superScrollView != _lastScrollView)
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]);
|
|
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
[_lastScrollView setContentInset:_startingContentInsets];
|
|
} completion:NULL];
|
|
|
|
if (_shouldRestoreScrollViewContentOffset)
|
|
{
|
|
[_lastScrollView setContentOffset:_startingContentOffset animated:YES];
|
|
}
|
|
|
|
_lastScrollView = superScrollView;
|
|
_startingContentInsets = superScrollView.contentInset;
|
|
_startingContentOffset = superScrollView.contentOffset;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Saving New %@ contentInset: %@ and contentOffset : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]);
|
|
}
|
|
//Else the case where superScrollView == lastScrollView means we are on same scrollView after switching to different textField. So doing nothing
|
|
}
|
|
//If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
|
|
else if(superScrollView)
|
|
{
|
|
_lastScrollView = superScrollView;
|
|
_startingContentInsets = superScrollView.contentInset;
|
|
_startingContentOffset = superScrollView.contentOffset;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Saving %@ contentInset: %@ and contentOffset : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]);
|
|
}
|
|
|
|
// Special case for ScrollView.
|
|
{
|
|
// If we found lastScrollView then setting it's contentOffset to show textField.
|
|
if (_lastScrollView)
|
|
{
|
|
//Saving
|
|
UIView *lastView = _textFieldView;
|
|
UIScrollView *superScrollView = _lastScrollView;
|
|
|
|
//Looping in upper hierarchy until we don't found any scrollView in it's upper hirarchy till UIWindow object.
|
|
while (superScrollView && (move>0?(move > (-superScrollView.contentOffset.y-superScrollView.contentInset.top)):superScrollView.contentOffset.y>0) )
|
|
{
|
|
//Getting lastViewRect.
|
|
CGRect lastViewRect = [[lastView superview] convertRect:lastView.frame toView:superScrollView];
|
|
|
|
//Calculating the expected Y offset from move and scrollView's contentOffset.
|
|
CGFloat shouldOffsetY = superScrollView.contentOffset.y - MIN(superScrollView.contentOffset.y,-move);
|
|
|
|
//Rearranging the expected Y offset according to the view.
|
|
shouldOffsetY = MIN(shouldOffsetY, lastViewRect.origin.y/*-5*/); //-5 is for good UI.//Commenting -5 (Bug ID: #69)
|
|
|
|
//[superScrollView superviewOfClassType:[UIScrollView class]] == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.)
|
|
//[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
|
|
//shouldOffsetY > 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
|
|
if ([_textFieldView isKindOfClass:[UITextView class]] && [superScrollView superviewOfClassType:[UIScrollView class]] == nil && shouldOffsetY > 0)
|
|
{
|
|
CGFloat maintainTopLayout = 0;
|
|
|
|
//When uncommenting this, each calculation goes to well, but don't know why scrollView doesn't adjusting it's contentOffset at bottom
|
|
// if ([_textFieldView.viewController respondsToSelector:@selector(topLayoutGuide)])
|
|
// maintainTopLayout = [_textFieldView.viewController.topLayoutGuide length];
|
|
// else
|
|
maintainTopLayout = CGRectGetMaxY(_textFieldView.viewController.navigationController.navigationBar.frame);
|
|
|
|
maintainTopLayout+= 10; //For good UI
|
|
|
|
// Converting Rectangle according to window bounds.
|
|
CGRect currentTextFieldViewRect = [[_textFieldView superview] convertRect:_textFieldView.frame toView:keyWindow];
|
|
CGFloat expectedFixDistance = shouldOffsetY;
|
|
|
|
//Calculating expected fix distance which needs to be managed from navigation bar
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
expectedFixDistance = CGRectGetMinX(currentTextFieldViewRect) - maintainTopLayout;
|
|
break;
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
expectedFixDistance = (CGRectGetWidth(keyWindow.frame)-CGRectGetMaxX(currentTextFieldViewRect)) - maintainTopLayout;
|
|
break;
|
|
case UIInterfaceOrientationPortrait:
|
|
expectedFixDistance = CGRectGetMinY(currentTextFieldViewRect) - maintainTopLayout;
|
|
break;
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
expectedFixDistance = (CGRectGetHeight(keyWindow.frame)-CGRectGetMaxY(currentTextFieldViewRect)) - maintainTopLayout;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//Now if expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance) is lower than current shouldOffsetY, which means we're in a position where navigationBar up and hide, then reducing shouldOffsetY with expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance)
|
|
shouldOffsetY = MIN(shouldOffsetY, superScrollView.contentOffset.y + expectedFixDistance);
|
|
|
|
//Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic.
|
|
move = 0;
|
|
}
|
|
else
|
|
{
|
|
//Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
|
|
move -= (shouldOffsetY-superScrollView.contentOffset.y);
|
|
}
|
|
|
|
|
|
//Getting problem while using `setContentOffset:animated:`, So I used animation API.
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Adjusting %.2f to %@ ContentOffset",(superScrollView.contentOffset.y-shouldOffsetY),[superScrollView _IQDescription]]);
|
|
_IQShowLog([NSString stringWithFormat:@"Remaining Move: %.2f",move]);
|
|
|
|
superScrollView.contentOffset = CGPointMake(superScrollView.contentOffset.x, shouldOffsetY);
|
|
|
|
} completion:NULL];
|
|
|
|
// Getting next lastView & superScrollView.
|
|
lastView = superScrollView;
|
|
superScrollView = (UIScrollView*)[lastView superviewOfClassType:[UIScrollView class]];
|
|
}
|
|
|
|
//Updating contentInset
|
|
{
|
|
|
|
CGFloat bottom = 0;
|
|
|
|
CGRect lastScrollViewRect = [[_lastScrollView superview] convertRect:_lastScrollView.frame toView:keyWindow];
|
|
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
bottom = _kbSize.width-(CGRectGetWidth(keyWindow.frame)-CGRectGetMaxX(lastScrollViewRect));
|
|
break;
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
bottom = _kbSize.width-CGRectGetMinX(lastScrollViewRect);
|
|
break;
|
|
case UIInterfaceOrientationPortrait:
|
|
bottom = _kbSize.height-(CGRectGetHeight(keyWindow.frame)-CGRectGetMaxY(lastScrollViewRect));
|
|
break;
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
bottom = _kbSize.height-CGRectGetMinY(lastScrollViewRect);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view.
|
|
UIEdgeInsets movedInsets = _lastScrollView.contentInset;
|
|
|
|
movedInsets.bottom = MAX(_startingContentInsets.bottom, bottom);
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"%@ old ContentInset : %@",[_lastScrollView _IQDescription], NSStringFromUIEdgeInsets(_lastScrollView.contentInset)]);
|
|
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
_lastScrollView.contentInset = movedInsets;
|
|
} completion:NULL];
|
|
|
|
if (_lastScrollView.contentSize.height<_lastScrollView.frame.size.height)
|
|
{
|
|
CGSize contentSize = _lastScrollView.contentSize;
|
|
contentSize.height = _lastScrollView.frame.size.height;
|
|
_lastScrollView.contentSize = contentSize;
|
|
}
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"%@ new ContentInset : %@",[_lastScrollView _IQDescription], NSStringFromUIEdgeInsets(_lastScrollView.contentInset)]);
|
|
}
|
|
}
|
|
//Going ahead. No else if.
|
|
}
|
|
|
|
//Special case for UITextView(Readjusting the move variable when textView hight is too big to fit on screen)
|
|
//_canAdjustTextView If we have permission to adjust the textView, then let's do it on behalf of user (Enhancement ID: #15)
|
|
//_lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
|
|
//[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
|
|
//_isTextFieldViewFrameChanged If frame is not change by library in past (Bug ID: #92)
|
|
if (_canAdjustTextView && (_lastScrollView == NO) && [_textFieldView isKindOfClass:[UITextView class]] && _keyboardManagerFlags.isTextFieldViewFrameChanged == NO)
|
|
{
|
|
CGFloat textViewHeight = CGRectGetHeight(_textFieldView.frame);
|
|
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
textViewHeight = MIN(textViewHeight, (CGRectGetWidth(keyWindow.frame)-_kbSize.width-(topLayoutGuide+5)));
|
|
break;
|
|
case UIInterfaceOrientationPortrait:
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
textViewHeight = MIN(textViewHeight, (CGRectGetHeight(keyWindow.frame)-_kbSize.height-(topLayoutGuide+5)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"%@ Old Frame : %@",[_textFieldView _IQDescription], NSStringFromCGRect(_textFieldView.frame)]);
|
|
|
|
CGRect textFieldViewRect = _textFieldView.frame;
|
|
textFieldViewRect.size.height = textViewHeight;
|
|
_textFieldView.frame = textFieldViewRect;
|
|
_keyboardManagerFlags.isTextFieldViewFrameChanged = YES;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"%@ New Frame : %@",[_textFieldView _IQDescription], NSStringFromCGRect(_textFieldView.frame)]);
|
|
|
|
} completion:NULL];
|
|
}
|
|
|
|
// Special case for iPad modalPresentationStyle.
|
|
if ([rootController modalPresentationStyle] == UIModalPresentationFormSheet ||
|
|
[rootController modalPresentationStyle] == UIModalPresentationPageSheet)
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"Found Special case for Model Presentation Style: %ld",(long)(rootController.modalPresentationStyle)]);
|
|
|
|
// Positive or zero.
|
|
if (move>=0)
|
|
{
|
|
// We should only manipulate y.
|
|
rootViewRect.origin.y -= move;
|
|
|
|
// From now prevent keyboard manager to slide up the rootView to more than keyboard height. (Bug ID: #93)
|
|
if (_preventShowingBottomBlankSpace == YES)
|
|
{
|
|
CGFloat minimumY = 0;
|
|
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
minimumY = CGRectGetWidth(keyWindow.frame)-rootViewRect.size.height-topLayoutGuide-(_kbSize.width-_keyboardDistanceFromTextField); break;
|
|
case UIInterfaceOrientationPortrait:
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
minimumY = (CGRectGetHeight(keyWindow.frame)-rootViewRect.size.height-topLayoutGuide)/2-(_kbSize.height-_keyboardDistanceFromTextField); break;
|
|
default: break;
|
|
}
|
|
|
|
rootViewRect.origin.y = MAX(rootViewRect.origin.y, minimumY);
|
|
}
|
|
|
|
_IQShowLog(@"Moving Upward");
|
|
// Setting adjusted rootViewRect
|
|
[self setRootViewFrame:rootViewRect];
|
|
}
|
|
// Negative
|
|
else
|
|
{
|
|
// Calculating disturbed distance. Pull Request #3
|
|
CGFloat disturbDistance = CGRectGetMinY(rootViewRect)-CGRectGetMinY(_topViewBeginRect);
|
|
|
|
// disturbDistance Negative = frame disturbed.
|
|
// disturbDistance positive = frame not disturbed.
|
|
if(disturbDistance<0)
|
|
{
|
|
// We should only manipulate y.
|
|
rootViewRect.origin.y -= MAX(move, disturbDistance);
|
|
|
|
_IQShowLog(@"Moving Downward");
|
|
// Setting adjusted rootViewRect
|
|
[self setRootViewFrame:rootViewRect];
|
|
}
|
|
}
|
|
}
|
|
//If presentation style is neither UIModalPresentationFormSheet nor UIModalPresentationPageSheet then going ahead.(General case)
|
|
else
|
|
{
|
|
// Positive or zero.
|
|
if (move>=0)
|
|
{
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft: rootViewRect.origin.x -= move; break;
|
|
case UIInterfaceOrientationLandscapeRight: rootViewRect.origin.x += move; break;
|
|
case UIInterfaceOrientationPortrait: rootViewRect.origin.y -= move; break;
|
|
case UIInterfaceOrientationPortraitUpsideDown: rootViewRect.origin.y += move; break;
|
|
default: break;
|
|
}
|
|
|
|
// From now prevent keyboard manager to slide up the rootView to more than keyboard height. (Bug ID: #93)
|
|
if (_preventShowingBottomBlankSpace == YES)
|
|
{
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft: rootViewRect.origin.x = MAX(rootViewRect.origin.x, MIN(0,-_kbSize.width+_keyboardDistanceFromTextField)); break;
|
|
case UIInterfaceOrientationLandscapeRight: rootViewRect.origin.x = MIN(rootViewRect.origin.x, +_kbSize.width-_keyboardDistanceFromTextField); break;
|
|
case UIInterfaceOrientationPortrait: rootViewRect.origin.y = MAX(rootViewRect.origin.y, MIN(0, -_kbSize.height+_keyboardDistanceFromTextField)); break;
|
|
case UIInterfaceOrientationPortraitUpsideDown: rootViewRect.origin.y = MIN(rootViewRect.origin.y, +_kbSize.height-_keyboardDistanceFromTextField); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
_IQShowLog(@"Moving Upward");
|
|
// Setting adjusted rootViewRect
|
|
[self setRootViewFrame:rootViewRect];
|
|
}
|
|
// Negative
|
|
else
|
|
{
|
|
CGFloat disturbDistance = 0;
|
|
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
disturbDistance = CGRectGetMinX(rootViewRect)-CGRectGetMinX(_topViewBeginRect);
|
|
break;
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
disturbDistance = CGRectGetMinX(_topViewBeginRect)-CGRectGetMinX(rootViewRect);
|
|
break;
|
|
case UIInterfaceOrientationPortrait:
|
|
disturbDistance = CGRectGetMinY(rootViewRect)-CGRectGetMinY(_topViewBeginRect);
|
|
break;
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
disturbDistance = CGRectGetMinY(_topViewBeginRect)-CGRectGetMinY(rootViewRect);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// disturbDistance Negative = frame disturbed. Pull Request #3
|
|
// disturbDistance positive = frame not disturbed.
|
|
if(disturbDistance<0)
|
|
{
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft: rootViewRect.origin.x -= MAX(move, disturbDistance); break;
|
|
case UIInterfaceOrientationLandscapeRight: rootViewRect.origin.x += MAX(move, disturbDistance); break;
|
|
case UIInterfaceOrientationPortrait: rootViewRect.origin.y -= MAX(move, disturbDistance); break;
|
|
case UIInterfaceOrientationPortraitUpsideDown: rootViewRect.origin.y += MAX(move, disturbDistance); break;
|
|
default: break;
|
|
}
|
|
|
|
_IQShowLog(@"Moving Downward");
|
|
// Setting adjusted rootViewRect
|
|
[self setRootViewFrame:rootViewRect];
|
|
}
|
|
}
|
|
}
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
}
|
|
|
|
#pragma mark - UIKeyboad Notification methods
|
|
/* UIKeyboardWillShowNotification. */
|
|
-(void)keyboardWillShow:(NSNotification*)aNotification
|
|
{
|
|
_kbShowNotification = aNotification;
|
|
|
|
if (_enable == NO) return;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]);
|
|
|
|
//Due to orientation callback we need to resave it's original frame. // (Bug ID: #46)
|
|
//Added _isTextFieldViewFrameChanged check. Saving textFieldView current frame to use it with canAdjustTextView if textViewFrame has already not been changed. (Bug ID: #92)
|
|
if (_keyboardManagerFlags.isTextFieldViewFrameChanged == NO && _textFieldView)
|
|
{
|
|
_textFieldViewIntialFrame = _textFieldView.frame;
|
|
_IQShowLog([NSString stringWithFormat:@"Saving %@ Initial frame :%@",[_textFieldView _IQDescription],NSStringFromCGRect(_textFieldViewIntialFrame)]);
|
|
}
|
|
|
|
if (CGRectEqualToRect(_topViewBeginRect, CGRectZero)) // (Bug ID: #5)
|
|
{
|
|
// keyboard is not showing(At the beginning only). We should save rootViewRect.
|
|
_rootViewController = [_textFieldView topMostController];
|
|
if (_rootViewController == nil) _rootViewController = [[self keyWindow] topMostController];
|
|
|
|
_topViewBeginRect = _rootViewController.view.frame;
|
|
_IQShowLog([NSString stringWithFormat:@"Saving %@ beginning Frame: %@",[_rootViewController _IQDescription] ,NSStringFromCGRect(_topViewBeginRect)]);
|
|
}
|
|
|
|
if (_shouldAdoptDefaultKeyboardAnimation)
|
|
{
|
|
// Getting keyboard animation.
|
|
_animationCurve = [[[aNotification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
|
|
_animationCurve = _animationCurve<<16;
|
|
}
|
|
else
|
|
{
|
|
_animationCurve = 0;
|
|
}
|
|
|
|
// Getting keyboard animation duration
|
|
CGFloat duration = [[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
|
|
|
|
//Saving animation duration
|
|
if (duration != 0.0) _animationDuration = duration;
|
|
|
|
CGSize oldKBSize = _kbSize;
|
|
|
|
// Getting UIKeyboardSize.
|
|
// CGRect screenRect = [self keyWindow].bounds;
|
|
CGRect kbFrame = [[[aNotification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
|
_kbSize = kbFrame.size;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"UIKeyboard Size : %@",NSStringFromCGSize(_kbSize)]);
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
//If it's iOS8 then we should do calculations according to portrait orientations. // (Bug ID: #64, #66)
|
|
UIViewController *topMostController = [_textFieldView topMostController];
|
|
if (topMostController == nil) topMostController = [[self keyWindow] topMostController];
|
|
|
|
UIInterfaceOrientation interfaceOrientation = IQ_IS_IOS8_OR_GREATER ? UIInterfaceOrientationPortrait : [topMostController interfaceOrientation];
|
|
#pragma GCC diagnostic pop
|
|
|
|
switch (interfaceOrientation)
|
|
{
|
|
case UIInterfaceOrientationLandscapeLeft:
|
|
// _kbSize.width = screenRect.size.width - kbFrame.origin.x;
|
|
_kbSize.width += _keyboardDistanceFromTextField;
|
|
break;
|
|
case UIInterfaceOrientationLandscapeRight:
|
|
// _kbSize.width = screenRect.size.width - kbFrame.origin.x;
|
|
_kbSize.width += _keyboardDistanceFromTextField;
|
|
break;
|
|
case UIInterfaceOrientationPortrait:
|
|
// _kbSize.height = screenRect.size.height - kbFrame.origin.y;
|
|
_kbSize.height += _keyboardDistanceFromTextField;
|
|
break;
|
|
case UIInterfaceOrientationPortraitUpsideDown:
|
|
// _kbSize.height = screenRect.size.height - kbFrame.origin.y;
|
|
_kbSize.height += _keyboardDistanceFromTextField;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//If last restored keyboard size is different(any orientation accure), then refresh. otherwise not.
|
|
if (!CGSizeEqualToSize(_kbSize, oldKBSize))
|
|
{
|
|
//If _textFieldView is inside ignored responder then do nothing. (Bug ID: #37, #74, #76)
|
|
//See notes:- https://developer.apple.com/Library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html. If it is UIAlertView textField then do not affect anything (Bug ID: #70).
|
|
if (_textFieldView != nil && [_textFieldView isAlertViewTextField] == NO)
|
|
{
|
|
UIViewController *textFieldViewController = [_textFieldView viewController];
|
|
|
|
BOOL shouldIgnore = NO;
|
|
|
|
for (Class disabledClass in _disabledClasses)
|
|
{
|
|
if ([textFieldViewController isKindOfClass:disabledClass])
|
|
{
|
|
shouldIgnore = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldIgnore == NO)
|
|
{
|
|
[self adjustFrame];
|
|
}
|
|
}
|
|
}
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
}
|
|
|
|
/* UIKeyboardWillHideNotification. So setting rootViewController to it's default frame. */
|
|
- (void)keyboardWillHide:(NSNotification*)aNotification
|
|
{
|
|
//If it's not a fake notification generated by [self setEnable:NO].
|
|
if (aNotification != nil) _kbShowNotification = nil;
|
|
|
|
//If not enabled then do nothing.
|
|
if (_enable == NO) return;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]);
|
|
|
|
//Commented due to #56. Added all the conditions below to handle UIWebView's textFields. (Bug ID: #56)
|
|
// We are unable to get textField object while keyboard showing on UIWebView's textField. (Bug ID: #11)
|
|
// if (_textFieldView == nil) return;
|
|
|
|
// Boolean to know keyboard is showing/hiding
|
|
_keyboardManagerFlags.isKeyboardShowing = NO;
|
|
|
|
// Getting keyboard animation duration
|
|
CGFloat aDuration = [[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
|
|
if (aDuration!= 0.0f)
|
|
{
|
|
// Setitng keyboard animation duration
|
|
_animationDuration = aDuration;
|
|
}
|
|
|
|
//Restoring the contentOffset of the lastScrollView
|
|
if (_lastScrollView)
|
|
{
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
_lastScrollView.contentInset = _startingContentInsets;
|
|
|
|
if (_shouldRestoreScrollViewContentOffset)
|
|
{
|
|
_lastScrollView.contentOffset = _startingContentOffset;
|
|
}
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]);
|
|
|
|
// TODO: restore scrollView state
|
|
// This is temporary solution. Have to implement the save and restore scrollView state
|
|
UIScrollView *superscrollView = _lastScrollView;
|
|
while ((superscrollView = (UIScrollView*)[superscrollView superviewOfClassType:[UIScrollView class]]))
|
|
{
|
|
MAX(superscrollView.contentSize.height, CGRectGetHeight(superscrollView.frame));
|
|
|
|
CGSize contentSize = CGSizeMake(MAX(superscrollView.contentSize.width, CGRectGetWidth(superscrollView.frame)), MAX(superscrollView.contentSize.height, CGRectGetHeight(superscrollView.frame)));
|
|
|
|
CGFloat minimumY = contentSize.height-CGRectGetHeight(superscrollView.frame);
|
|
|
|
if (minimumY<superscrollView.contentOffset.y)
|
|
{
|
|
superscrollView.contentOffset = CGPointMake(superscrollView.contentOffset.x, minimumY);
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Restoring %@ contentOffset to : %@",[superscrollView _IQDescription],NSStringFromCGPoint(superscrollView.contentOffset)]);
|
|
}
|
|
}
|
|
|
|
} completion:NULL];
|
|
}
|
|
|
|
// Setting rootViewController frame to it's original position. // (Bug ID: #18)
|
|
if (!CGRectEqualToRect(_topViewBeginRect, CGRectZero) && _rootViewController)
|
|
{
|
|
//frame size needs to be adjusted on iOS8 due to orientation API changes.
|
|
if (IQ_IS_IOS8_OR_GREATER)
|
|
{
|
|
_topViewBeginRect.size = _rootViewController.view.frame.size;
|
|
}
|
|
|
|
//Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Restoring %@ frame to : %@",[_rootViewController _IQDescription],NSStringFromCGRect(_topViewBeginRect)]);
|
|
// Setting it's new frame
|
|
[_rootViewController.view setFrame:_topViewBeginRect];
|
|
|
|
//Animating content (Bug ID: #160)
|
|
[_rootViewController.view setNeedsLayout];
|
|
[_rootViewController.view layoutIfNeeded];
|
|
|
|
} completion:NULL];
|
|
_rootViewController = nil;
|
|
}
|
|
|
|
//Reset all values
|
|
_lastScrollView = nil;
|
|
_kbSize = CGSizeZero;
|
|
_startingContentInsets = UIEdgeInsetsZero;
|
|
_startingContentOffset = CGPointZero;
|
|
// topViewBeginRect = CGRectZero; //Commented due to #82
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
}
|
|
|
|
/* UIKeyboardDidHideNotification. So topViewBeginRect can be set to CGRectZero. */
|
|
- (void)keyboardDidHide:(NSNotification*)aNotification
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]);
|
|
|
|
_topViewBeginRect = CGRectZero;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
}
|
|
|
|
#pragma mark - UITextFieldView Delegate methods
|
|
/** UITextFieldTextDidBeginEditingNotification, UITextViewTextDidBeginEditingNotification. Fetching UITextFieldView object. */
|
|
-(void)textFieldViewDidBeginEditing:(NSNotification*)notification
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]);
|
|
|
|
// Getting object
|
|
_textFieldView = notification.object;
|
|
|
|
if (_overrideKeyboardAppearance == YES)
|
|
{
|
|
UITextField *textField = (UITextField*)_textFieldView;
|
|
|
|
//If keyboard appearance is not like the provided appearance
|
|
if (textField.keyboardAppearance != _keyboardAppearance)
|
|
{
|
|
//Setting textField keyboard appearance and reloading inputViews.
|
|
textField.keyboardAppearance = _keyboardAppearance;
|
|
[textField reloadInputViews];
|
|
}
|
|
}
|
|
|
|
// Saving textFieldView current frame to use it with canAdjustTextView if textViewFrame has already not been changed.
|
|
//Added _isTextFieldViewFrameChanged check. (Bug ID: #92)
|
|
if (_keyboardManagerFlags.isTextFieldViewFrameChanged == NO && _textFieldView)
|
|
{
|
|
_textFieldViewIntialFrame = _textFieldView.frame;
|
|
_IQShowLog([NSString stringWithFormat:@"Saving %@ Initial frame :%@",[_textFieldView _IQDescription],NSStringFromCGRect(_textFieldViewIntialFrame)]);
|
|
}
|
|
|
|
//If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required.
|
|
if (_enableAutoToolbar)
|
|
{
|
|
_IQShowLog(@"adding UIToolbars if required");
|
|
|
|
//UITextView special case. Keyboard Notification is firing before textView notification so we need to reload it's inputViews.
|
|
if ([_textFieldView isKindOfClass:[UITextView class]] && _textFieldView.inputAccessoryView == nil)
|
|
{
|
|
[UIView animateWithDuration:0.00001 delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
[self addToolbarIfRequired];
|
|
} completion:^(BOOL finished) {
|
|
|
|
//On textView toolbar didn't appear on first time, so forcing textView to reload it's inputViews.
|
|
[_textFieldView reloadInputViews];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
//Adding toolbar
|
|
[self addToolbarIfRequired];
|
|
}
|
|
}
|
|
|
|
if (_enable == NO)
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
return;
|
|
}
|
|
|
|
//Adding Geture recognizer to window (Enhancement ID: #14)
|
|
[_textFieldView.window addGestureRecognizer:_tapGesture];
|
|
|
|
if (_keyboardManagerFlags.isKeyboardShowing == NO) // (Bug ID: #5)
|
|
{
|
|
// keyboard is not showing(At the beginning only). We should save rootViewRect.
|
|
_rootViewController = [_textFieldView topMostController];
|
|
if (_rootViewController == nil) _rootViewController = [[self keyWindow] topMostController];
|
|
|
|
_topViewBeginRect = _rootViewController.view.frame;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Saving %@ beginning Frame: %@",[_rootViewController _IQDescription], NSStringFromCGRect(_topViewBeginRect)]);
|
|
}
|
|
|
|
//If _textFieldView is inside ignored responder then do nothing. (Bug ID: #37, #74, #76)
|
|
//See notes:- https://developer.apple.com/Library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html. If it is UIAlertView textField then do not affect anything (Bug ID: #70).
|
|
if (_textFieldView != nil && [_textFieldView isAlertViewTextField] == NO)
|
|
{
|
|
//Getting textField viewController
|
|
UIViewController *textFieldViewController = [_textFieldView viewController];
|
|
|
|
BOOL shouldIgnore = NO;
|
|
|
|
for (Class disabledClass in _disabledClasses)
|
|
{
|
|
//If viewController is kind of disabled viewController class, then ignoring to adjust view.
|
|
if ([textFieldViewController isKindOfClass:disabledClass])
|
|
{
|
|
shouldIgnore = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//If shouldn't ignore.
|
|
if (shouldIgnore == NO)
|
|
{
|
|
// keyboard is already showing. adjust frame.
|
|
[self adjustFrame];
|
|
}
|
|
}
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
}
|
|
|
|
/** UITextFieldTextDidEndEditingNotification, UITextViewTextDidEndEditingNotification. Removing fetched object. */
|
|
-(void)textFieldViewDidEndEditing:(NSNotification*)notification
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]);
|
|
|
|
//Removing gesture recognizer (Enhancement ID: #14)
|
|
[_textFieldView.window removeGestureRecognizer:_tapGesture];
|
|
|
|
// We check if there's a change in original frame or not.
|
|
if(_keyboardManagerFlags.isTextFieldViewFrameChanged == YES)
|
|
{
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
_keyboardManagerFlags.isTextFieldViewFrameChanged = NO;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Restoring %@ frame to : %@",[_textFieldView _IQDescription],NSStringFromCGRect(_textFieldViewIntialFrame)]);
|
|
|
|
//Setting textField to it's initial frame
|
|
_textFieldView.frame = _textFieldViewIntialFrame;
|
|
|
|
} completion:NULL];
|
|
}
|
|
|
|
//Setting object to nil
|
|
_textFieldView = nil;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
}
|
|
|
|
/* UITextViewTextDidChangeNotificationBug, fix for iOS 7.0.x - http://stackoverflow.com/questions/18966675/uitextview-in-ios7-clips-the-last-line-of-text-string */
|
|
-(void)textFieldViewDidChange:(NSNotification*)notification // (Bug ID: #18)
|
|
{
|
|
if (_shouldFixTextViewClip == YES)
|
|
{
|
|
UITextView *textView = (UITextView *)notification.object;
|
|
CGRect line = [textView caretRectForPosition: textView.selectedTextRange.start];
|
|
CGFloat overflow = CGRectGetMaxY(line) - (textView.contentOffset.y + CGRectGetHeight(textView.bounds) - textView.contentInset.bottom - textView.contentInset.top);
|
|
|
|
//Added overflow conditions (Bug ID: 95)
|
|
if ( overflow > 0 && overflow < FLT_MAX)
|
|
{
|
|
// We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it)
|
|
// Scroll caret to visible area
|
|
CGPoint offset = textView.contentOffset;
|
|
offset.y += overflow + 7; // leave 7 pixels margin
|
|
|
|
// Cannot animate with setContentOffset:animated: or caret will not appear
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
[textView setContentOffset:offset];
|
|
} completion:NULL];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - UIInterfaceOrientation Change notification methods
|
|
/** UIApplicationWillChangeStatusBarOrientationNotification. Need to set the textView to it's original position. If any frame changes made. (Bug ID: #92)*/
|
|
- (void)willChangeStatusBarOrientation:(NSNotification*)aNotification
|
|
{
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]);
|
|
|
|
//If textFieldViewInitialRect is saved then restore it.(UITextView case @canAdjustTextView)
|
|
if (_keyboardManagerFlags.isTextFieldViewFrameChanged == YES)
|
|
{
|
|
//Due to orientation callback we need to set it's original position.
|
|
[UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
|
|
_keyboardManagerFlags.isTextFieldViewFrameChanged = NO;
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Restoring %@ frame to : %@",[_textFieldView _IQDescription],NSStringFromCGRect(_textFieldViewIntialFrame)]);
|
|
|
|
//Setting textField to it's initial frame
|
|
_textFieldView.frame = _textFieldViewIntialFrame;
|
|
} completion:NULL];
|
|
}
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"****** %@ ended ******",NSStringFromSelector(_cmd)]);
|
|
}
|
|
|
|
#pragma mark AutoResign methods
|
|
|
|
/** Resigning on tap gesture. */
|
|
- (void)tapRecognized:(UITapGestureRecognizer*)gesture // (Enhancement ID: #14)
|
|
{
|
|
if (gesture.state == UIGestureRecognizerStateEnded)
|
|
{
|
|
//Resigning currently responder textField.
|
|
[self resignFirstResponder];
|
|
}
|
|
}
|
|
|
|
/** Note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES. */
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
/** To not detect touch events in a subclass of UIControl, these may have added their own selector for specific work */
|
|
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
|
|
{
|
|
// Should not recognize gesture if the clicked view is either UIControl or UINavigationBar(<Back button etc...) (Bug ID: #145)
|
|
return ([[touch view] isKindOfClass:[UIControl class]] || [[touch view] isKindOfClass:[UINavigationBar class]]) ? NO : YES;
|
|
}
|
|
|
|
/** Resigning textField. */
|
|
- (void)resignFirstResponder
|
|
{
|
|
if (_textFieldView)
|
|
{
|
|
// Retaining textFieldView
|
|
UIView *textFieldRetain = _textFieldView;
|
|
|
|
//Resigning first responder
|
|
BOOL isResignFirstResponder = [_textFieldView resignFirstResponder];
|
|
|
|
// If it refuses then becoming it as first responder again. (Bug ID: #96)
|
|
if (isResignFirstResponder == NO)
|
|
{
|
|
//If it refuses to resign then becoming it first responder again for getting notifications callback.
|
|
[textFieldRetain becomeFirstResponder];
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Refuses to Resign first responder: %@",[_textFieldView _IQDescription]]);
|
|
}
|
|
else if (textFieldRetain.doneInvocation)
|
|
{
|
|
[textFieldRetain.doneInvocation invoke];
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns YES if can navigate to previous responder textField/textView, otherwise NO. */
|
|
-(BOOL)canGoPrevious
|
|
{
|
|
//Getting all responder view's.
|
|
NSArray *textFields = [self responderViews];
|
|
|
|
if ([textFields containsObject:_textFieldView])
|
|
{
|
|
//Getting index of current textField.
|
|
NSUInteger index = [textFields indexOfObject:_textFieldView];
|
|
|
|
//If it is not first textField. then it's previous object can becomeFirstResponder.
|
|
if (index > 0)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
/** Returns YES if can navigate to next responder textField/textView, otherwise NO. */
|
|
-(BOOL)canGoNext
|
|
{
|
|
//Getting all responder view's.
|
|
NSArray *textFields = [self responderViews];
|
|
|
|
if ([textFields containsObject:_textFieldView])
|
|
{
|
|
//Getting index of current textField.
|
|
NSUInteger index = [textFields indexOfObject:_textFieldView];
|
|
|
|
//If it is not last textField. then it's next object becomeFirstResponder.
|
|
if (index < textFields.count-1)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
/** Navigate to previous responder textField/textView. */
|
|
-(void)goPrevious
|
|
{
|
|
//Getting all responder view's.
|
|
NSArray *textFields = [self responderViews];
|
|
|
|
if ([textFields containsObject:_textFieldView])
|
|
{
|
|
//Getting index of current textField.
|
|
NSUInteger index = [textFields indexOfObject:_textFieldView];
|
|
|
|
//If it is not first textField. then it's previous object becomeFirstResponder.
|
|
if (index > 0)
|
|
{
|
|
UITextField *nextTextField = [textFields objectAtIndex:index-1];
|
|
|
|
// Retaining textFieldView
|
|
UIView *textFieldRetain = _textFieldView;
|
|
|
|
BOOL isAcceptAsFirstResponder = [nextTextField becomeFirstResponder];
|
|
|
|
// If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
|
|
if (isAcceptAsFirstResponder == NO)
|
|
{
|
|
//If next field refuses to become first responder then restoring old textField as first responder.
|
|
[textFieldRetain becomeFirstResponder];
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Refuses to become first responder: %@",[nextTextField _IQDescription]]);
|
|
}
|
|
else if (textFieldRetain.previousInvocation)
|
|
{
|
|
[textFieldRetain.previousInvocation invoke];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Navigate to next responder textField/textView. */
|
|
-(void)goNext
|
|
{
|
|
//Getting all responder view's.
|
|
NSArray *textFields = [self responderViews];
|
|
|
|
if ([textFields containsObject:_textFieldView])
|
|
{
|
|
//Getting index of current textField.
|
|
NSUInteger index = [textFields indexOfObject:_textFieldView];
|
|
|
|
//If it is not last textField. then it's next object becomeFirstResponder.
|
|
if (index < textFields.count-1)
|
|
{
|
|
UITextField *nextTextField = [textFields objectAtIndex:index+1];
|
|
|
|
// Retaining textFieldView
|
|
UIView *textFieldRetain = _textFieldView;
|
|
|
|
BOOL isAcceptAsFirstResponder = [nextTextField becomeFirstResponder];
|
|
|
|
// If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
|
|
if (isAcceptAsFirstResponder == NO)
|
|
{
|
|
//If next field refuses to become first responder then restoring old textField as first responder.
|
|
[textFieldRetain becomeFirstResponder];
|
|
|
|
_IQShowLog([NSString stringWithFormat:@"Refuses to become first responder: %@",[nextTextField _IQDescription]]);
|
|
}
|
|
else if (textFieldRetain.nextInvocation)
|
|
{
|
|
[textFieldRetain.nextInvocation invoke];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark AutoToolbar methods
|
|
|
|
/** Get all UITextField/UITextView siblings of textFieldView. */
|
|
-(NSArray*)responderViews
|
|
{
|
|
UIView *superConsideredView;
|
|
|
|
//If find any consider responderView in it's upper hierarchy then will get deepResponderView.
|
|
for (Class consideredClass in _toolbarPreviousNextConsideredClass)
|
|
{
|
|
superConsideredView = [_textFieldView superviewOfClassType:consideredClass];
|
|
|
|
if (superConsideredView != nil)
|
|
break;
|
|
}
|
|
|
|
//If there is a superConsideredView in view's hierarchy, then fetching all it's subview that responds. No sorting for superConsideredView, it's by subView position. (Enhancement ID: #22)
|
|
if (superConsideredView)
|
|
{
|
|
return [superConsideredView deepResponderViews];
|
|
}
|
|
//Otherwise fetching all the siblings
|
|
else
|
|
{
|
|
NSArray *textFields = [_textFieldView responderSiblings];
|
|
|
|
//Sorting textFields according to behaviour
|
|
switch (_toolbarManageBehaviour)
|
|
{
|
|
//If autoToolbar behaviour is bySubviews, then returning it.
|
|
case IQAutoToolbarBySubviews:
|
|
return textFields;
|
|
break;
|
|
|
|
//If autoToolbar behaviour is by tag, then sorting it according to tag property.
|
|
case IQAutoToolbarByTag:
|
|
return [textFields sortedArrayByTag];
|
|
break;
|
|
|
|
//If autoToolbar behaviour is by tag, then sorting it according to tag property.
|
|
case IQAutoToolbarByPosition:
|
|
return [textFields sortedArrayByPosition];
|
|
break;
|
|
default:
|
|
return nil;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Add toolbar if it is required to add on textFields and it's siblings. */
|
|
-(void)addToolbarIfRequired
|
|
{
|
|
UIViewController *textFieldViewController = [_textFieldView viewController];
|
|
|
|
//If found any toolbar disabled classes then return. Will not add any toolbar.
|
|
for (Class disabledToolbarClass in _disabledToolbarClasses)
|
|
if ([textFieldViewController isKindOfClass:disabledToolbarClass])
|
|
return;
|
|
|
|
// Getting all the sibling textFields.
|
|
NSArray *siblings = [self responderViews];
|
|
|
|
// If only one object is found, then adding only Done button.
|
|
if (siblings.count==1)
|
|
{
|
|
UITextField *textField = nil;
|
|
|
|
if ([siblings count])
|
|
textField = [siblings objectAtIndex:0];
|
|
|
|
|
|
//Either there is no inputAccessoryView or if accessoryView is not appropriate for current situation(There is Previous/Next/Done toolbar).
|
|
if (![textField inputAccessoryView] || ([[textField inputAccessoryView] tag] == kIQPreviousNextButtonToolbarTag))
|
|
{
|
|
static UIView *doneToolbar = nil;
|
|
|
|
if (doneToolbar == nil)
|
|
{
|
|
//Now adding textField placeholder text as title of IQToolbar (Enhancement ID: #27)
|
|
[textField addDoneOnKeyboardWithTarget:self action:@selector(doneAction:) shouldShowPlaceholder:_shouldShowTextFieldPlaceholder];
|
|
doneToolbar = textField.inputAccessoryView;
|
|
doneToolbar.tag = kIQDoneButtonToolbarTag; // (Bug ID: #78)
|
|
}
|
|
else
|
|
{
|
|
textField.inputAccessoryView = doneToolbar;
|
|
}
|
|
}
|
|
|
|
if ([textField.inputAccessoryView isKindOfClass:[IQToolbar class]] && textField.inputAccessoryView.tag == kIQDoneButtonToolbarTag)
|
|
{
|
|
IQToolbar *toolbar = (IQToolbar*)[textField inputAccessoryView];
|
|
|
|
//Bar style according to keyboard appearance
|
|
if (IQ_IS_IOS7_OR_GREATER && [textField respondsToSelector:@selector(keyboardAppearance)])
|
|
{
|
|
switch ([(UITextField*)textField keyboardAppearance])
|
|
{
|
|
case UIKeyboardAppearanceAlert:
|
|
{
|
|
toolbar.barStyle = UIBarStyleBlack;
|
|
if ([toolbar respondsToSelector:@selector(tintColor)])
|
|
[toolbar setTintColor:[UIColor whiteColor]];
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
toolbar.barStyle = UIBarStyleDefault;
|
|
|
|
#ifdef NSFoundationVersionNumber_iOS_6_1
|
|
if ([toolbar respondsToSelector:@selector(tintColor)])
|
|
[toolbar setTintColor:_shouldToolbarUsesTextFieldTintColor?[textField tintColor]:_defaultToolbarTintColor];
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//If need to show placeholder
|
|
if (_shouldShowTextFieldPlaceholder)
|
|
{
|
|
//Updating placeholder font to toolbar. //(Bug ID: #148)
|
|
if ([textField respondsToSelector:@selector(placeholder)] && [toolbar.title isEqualToString:textField.placeholder] == NO)
|
|
[toolbar setTitle:textField.placeholder];
|
|
|
|
//Setting toolbar title font. // (Enhancement ID: #30)
|
|
if (_placeholderFont && [_placeholderFont isKindOfClass:[UIFont class]])
|
|
[toolbar setTitleFont:_placeholderFont];
|
|
}
|
|
}
|
|
}
|
|
else if(siblings.count)
|
|
{
|
|
// If more than 1 textField is found. then adding previous/next/done buttons on it.
|
|
for (UITextField *textField in siblings)
|
|
{
|
|
//Either there is no inputAccessoryView or if accessoryView is not appropriate for current situation(There is Done toolbar).
|
|
if (![textField inputAccessoryView] || [[textField inputAccessoryView] tag] == kIQDoneButtonToolbarTag)
|
|
{
|
|
//Now adding textField placeholder text as title of IQToolbar (Enhancement ID: #27)
|
|
[textField addPreviousNextDoneOnKeyboardWithTarget:self previousAction:@selector(previousAction:) nextAction:@selector(nextAction:) doneAction:@selector(doneAction:) shouldShowPlaceholder:_shouldShowTextFieldPlaceholder];
|
|
textField.inputAccessoryView.tag = kIQPreviousNextButtonToolbarTag; // (Bug ID: #78)
|
|
}
|
|
|
|
if ([textField.inputAccessoryView isKindOfClass:[IQToolbar class]] && textField.inputAccessoryView.tag == kIQPreviousNextButtonToolbarTag)
|
|
{
|
|
IQToolbar *toolbar = (IQToolbar*)[textField inputAccessoryView];
|
|
|
|
//Bar style according to keyboard appearance
|
|
if (IQ_IS_IOS7_OR_GREATER && [textField respondsToSelector:@selector(keyboardAppearance)])
|
|
{
|
|
switch ([(UITextField*)textField keyboardAppearance])
|
|
{
|
|
case UIKeyboardAppearanceAlert:
|
|
{
|
|
toolbar.barStyle = UIBarStyleBlack;
|
|
if ([toolbar respondsToSelector:@selector(tintColor)])
|
|
[toolbar setTintColor:[UIColor whiteColor]];
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
toolbar.barStyle = UIBarStyleDefault;
|
|
|
|
#ifdef NSFoundationVersionNumber_iOS_6_1
|
|
//Setting toolbar tintColor // (Enhancement ID: #30)
|
|
if ([toolbar respondsToSelector:@selector(tintColor)])
|
|
[toolbar setTintColor:_shouldToolbarUsesTextFieldTintColor?[textField tintColor]:_defaultToolbarTintColor];
|
|
#endif
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//In case of UITableView (Special), the next/previous buttons has to be refreshed everytime. (Bug ID: #56)
|
|
// If firstTextField, then previous should not be enabled.
|
|
if ([siblings objectAtIndex:0] == textField)
|
|
{
|
|
[textField setEnablePrevious:NO next:YES];
|
|
}
|
|
// If lastTextField then next should not be enaled.
|
|
else if ([siblings lastObject] == textField)
|
|
{
|
|
[textField setEnablePrevious:YES next:NO];
|
|
}
|
|
else
|
|
{
|
|
[textField setEnablePrevious:YES next:YES];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Remove any toolbar if it is IQToolbar. */
|
|
-(void)removeToolbarIfRequired // (Bug ID: #18)
|
|
{
|
|
// Getting all the sibling textFields.
|
|
NSArray *siblings = [self responderViews];
|
|
|
|
for (UITextField *textField in siblings)
|
|
{
|
|
UIView *toolbar = [textField inputAccessoryView];
|
|
|
|
// (Bug ID: #78)
|
|
if ([toolbar isKindOfClass:[IQToolbar class]] && (toolbar.tag == kIQDoneButtonToolbarTag || toolbar.tag == kIQPreviousNextButtonToolbarTag))
|
|
{
|
|
textField.inputAccessoryView = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark previous/next/done functionality
|
|
/** previousAction. */
|
|
-(void)previousAction:(id)segmentedControl
|
|
{
|
|
//If user wants to play input Click sound.
|
|
if (_shouldPlayInputClicks)
|
|
{
|
|
//Play Input Click Sound.
|
|
[[UIDevice currentDevice] playInputClick];
|
|
}
|
|
|
|
if ([self canGoPrevious])
|
|
{
|
|
[self goPrevious];
|
|
}
|
|
}
|
|
|
|
/** nextAction. */
|
|
-(void)nextAction:(id)segmentedControl
|
|
{
|
|
//If user wants to play input Click sound.
|
|
if (_shouldPlayInputClicks)
|
|
{
|
|
//Play Input Click Sound.
|
|
[[UIDevice currentDevice] playInputClick];
|
|
}
|
|
|
|
if ([self canGoNext])
|
|
{
|
|
[self goNext];
|
|
}
|
|
}
|
|
|
|
/** doneAction. Resigning current textField. */
|
|
-(void)doneAction:(IQBarButtonItem*)barButton
|
|
{
|
|
//If user wants to play input Click sound.
|
|
if (_shouldPlayInputClicks)
|
|
{
|
|
//Play Input Click Sound.
|
|
[[UIDevice currentDevice] playInputClick];
|
|
}
|
|
|
|
[self resignFirstResponder];
|
|
}
|
|
|
|
#pragma mark - Tracking untracking
|
|
|
|
-(void)disableInViewControllerClass:(Class)disabledClass
|
|
{
|
|
[_disabledClasses addObject:disabledClass];
|
|
}
|
|
|
|
-(void)removeDisableInViewControllerClass:(Class)disabledClass
|
|
{
|
|
[_disabledClasses removeObject:disabledClass];
|
|
}
|
|
|
|
-(void)disableToolbarInViewControllerClass:(Class)toolbarDisabledClass
|
|
{
|
|
[_disabledToolbarClasses addObject:toolbarDisabledClass];
|
|
}
|
|
|
|
-(void)removeDisableToolbarInViewControllerClass:(Class)toolbarDisabledClass
|
|
{
|
|
[_disabledToolbarClasses removeObject:toolbarDisabledClass];
|
|
}
|
|
|
|
-(void)considerToolbarPreviousNextInViewClass:(Class)toolbarPreviousNextConsideredClass
|
|
{
|
|
[_toolbarPreviousNextConsideredClass addObject:toolbarPreviousNextConsideredClass];
|
|
}
|
|
|
|
-(void)removeConsiderToolbarPreviousNextInViewClass:(Class)toolbarPreviousNextConsideredClass
|
|
{
|
|
[_toolbarPreviousNextConsideredClass removeObject:toolbarPreviousNextConsideredClass];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
void _IQShowLog(NSString *logString)
|
|
{
|
|
#if IQKEYBOARDMANAGER_DEBUG
|
|
NSLog(@"IQKeyboardManager: %@",logString);
|
|
#endif
|
|
}
|