12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240 |
- //
- // IQKeyboardManager.m
- // https://github.com/hackiftekhar/IQKeyboardManager
- // Copyright (c) 2013-16 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 "IQKeyboardManagerConstantsInternal.h"
- #import "IQUIScrollView+Additions.h"
- #import "IQUITextFieldView+Additions.h"
- #import "IQUIViewController+Additions.h"
- #import "IQPreviousNextView.h"
- #import <QuartzCore/CABase.h>
- #import <objc/runtime.h>
- #import <UIKit/UIAlertController.h>
- #import <UIKit/UISearchBar.h>
- #import <UIKit/UIScreen.h>
- #import <UIKit/UINavigationBar.h>
- #import <UIKit/UITapGestureRecognizer.h>
- #import <UIKit/UITextField.h>
- #import <UIKit/UITextView.h>
- #import <UIKit/UITableViewController.h>
- #import <UIKit/UICollectionViewController.h>
- #import <UIKit/UINavigationController.h>
- #import <UIKit/UITouch.h>
- #import <UIKit/NSLayoutConstraint.h>
- NSInteger const kIQDoneButtonToolbarTag = -1002;
- NSInteger const kIQPreviousNextButtonToolbarTag = -1005;
- @interface IQKeyboardManager()<UIGestureRecognizerDelegate>
- /*******************************************/
- /** used to adjust contentInset of UITextView. */
- @property(nonatomic, assign) UIEdgeInsets startingTextViewContentInsets;
- /** used to adjust scrollIndicatorInsets of UITextView. */
- @property(nonatomic, assign) UIEdgeInsets startingTextViewScrollIndicatorInsets;
- /** used with textView to detect a textFieldView contentInset is changed or not. (Bug ID: #92)*/
- @property(nonatomic, assign) BOOL isTextViewContentInsetChanged;
- /*******************************************/
- /** To save UITextField/UITextView object voa textField/textView notifications. */
- @property(nonatomic, weak) UIView *textFieldView;
- /** To save rootViewController.view.frame. */
- @property(nonatomic, assign) CGRect topViewBeginRect;
- /** To save rootViewController */
- @property(nonatomic, weak) UIViewController *rootViewController;
- #ifdef __IPHONE_11_0
- /** To save additionalSafeAreaInsets of rootViewController to tweak iOS11 Safe Area */
- @property(nonatomic, assign) UIEdgeInsets initialAdditionalSafeAreaInsets;
- #endif
- /** To save topBottomLayoutConstraint original constant */
- @property(nonatomic, assign) CGFloat layoutGuideConstraintInitialConstant;
- /** To save topBottomLayoutConstraint original constraint reference */
- @property(nonatomic, weak) NSLayoutConstraint *layoutGuideConstraint;
- /*******************************************/
- /** Variable to save lastScrollView that was scrolled. */
- @property(nonatomic, weak) UIScrollView *lastScrollView;
- /** LastScrollView's initial contentInsets. */
- @property(nonatomic, assign) UIEdgeInsets startingContentInsets;
- /** LastScrollView's initial scrollIndicatorInsets. */
- @property(nonatomic, assign) UIEdgeInsets startingScrollIndicatorInsets;
- /** LastScrollView's initial contentOffset. */
- @property(nonatomic, assign) CGPoint startingContentOffset;
- /*******************************************/
- /** To save keyboard animation duration. */
- @property(nonatomic, assign) CGFloat animationDuration;
- /** To mimic the keyboard animation */
- @property(nonatomic, assign) NSInteger animationCurve;
- /*******************************************/
- /** TapGesture to resign keyboard on view's touch. It's a readonly property and exposed only for adding/removing dependencies if your added gesture does have collision with this one */
- @property(nonnull, nonatomic, strong, readwrite) UITapGestureRecognizer *resignFirstResponderGesture;
- /**
- moved distance to the top used to maintain distance between keyboard and textField. Most of the time this will be a positive value.
- */
- @property(nonatomic, assign, readwrite) CGFloat movedDistance;
- /*******************************************/
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *registeredClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *disabledDistanceHandlingClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *enabledDistanceHandlingClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *disabledToolbarClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *enabledToolbarClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *toolbarPreviousNextAllowedClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *disabledTouchResignedClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *enabledTouchResignedClasses;
- @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *touchResignedGestureIgnoreClasses;
- /*******************************************/
- @end
- @implementation IQKeyboardManager
- {
- @package
- /*******************************************/
-
- /** To save keyboardWillShowNotification. Needed for enable keyboard functionality. */
- NSNotification *_kbShowNotification;
-
- /** To save keyboard size. */
- CGSize _kbSize;
-
- /** To save Status Bar size. */
- CGRect _statusBarFrame;
-
- /*******************************************/
- }
- //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;
- @synthesize shouldToolbarUsesTextFieldTintColor = _shouldToolbarUsesTextFieldTintColor;
- @synthesize toolbarTintColor = _toolbarTintColor;
- @synthesize toolbarBarTintColor = _toolbarBarTintColor;
- @dynamic shouldShowTextFieldPlaceholder;
- @synthesize shouldShowToolbarPlaceholder = _shouldShowToolbarPlaceholder;
- @synthesize placeholderFont = _placeholderFont;
- //Resign handling
- @synthesize shouldResignOnTouchOutside = _shouldResignOnTouchOutside;
- @synthesize resignFirstResponderGesture = _resignFirstResponderGesture;
- //Sound handling
- @synthesize shouldPlayInputClicks = _shouldPlayInputClicks;
- //Animation handling
- @synthesize layoutIfNeededOnUpdate = _layoutIfNeededOnUpdate;
- #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
- {
- //Enabling IQKeyboardManager. Loading asynchronous on main thread
- [self performSelectorOnMainThread:@selector(sharedManager) withObject:nil waitUntilDone:NO];
- }
- /* Singleton Object Initialization. */
- -(instancetype)init
- {
- if (self = [super init])
- {
- __weak typeof(self) weakSelf = self;
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
-
- __strong typeof(self) strongSelf = weakSelf;
- strongSelf.registeredClasses = [[NSMutableSet alloc] init];
- [strongSelf registerAllNotifications];
- //Creating gesture for @shouldResignOnTouchOutside. (Enhancement ID: #14)
- strongSelf.resignFirstResponderGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized:)];
- strongSelf.resignFirstResponderGesture.cancelsTouchesInView = NO;
- [strongSelf.resignFirstResponderGesture setDelegate:self];
- strongSelf.resignFirstResponderGesture.enabled = strongSelf.shouldResignOnTouchOutside;
- //Setting it's initial values
- strongSelf.animationDuration = 0.25;
- strongSelf.animationCurve = UIViewAnimationCurveEaseInOut;
- [self setEnable:YES];
- [self setKeyboardDistanceFromTextField:10.0];
- [self setShouldPlayInputClicks:YES];
- [self setShouldResignOnTouchOutside:NO];
- [self setOverrideKeyboardAppearance:NO];
- [self setKeyboardAppearance:UIKeyboardAppearanceDefault];
- [self setEnableAutoToolbar:YES];
- [self setPreventShowingBottomBlankSpace:YES];
- [self setShouldShowToolbarPlaceholder:YES];
- [self setToolbarManageBehaviour:IQAutoToolbarBySubviews];
- [self setLayoutIfNeededOnUpdate:NO];
- [self setShouldFixInteractivePopGestureRecognizer:YES];
-
- //Loading IQToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay (Bug ID: #550)
- {
- UITextField *view = [[UITextField alloc] init];
- [view addDoneOnKeyboardWithTarget:nil action:nil];
- [view addPreviousNextDoneOnKeyboardWithTarget:nil previousAction:nil nextAction:nil doneAction:nil];
- }
-
- //Initializing disabled classes Set.
- strongSelf.disabledDistanceHandlingClasses = [[NSMutableSet alloc] initWithObjects:[UITableViewController class],[UIAlertController class], nil];
- strongSelf.enabledDistanceHandlingClasses = [[NSMutableSet alloc] init];
-
- strongSelf.disabledToolbarClasses = [[NSMutableSet alloc] initWithObjects:[UIAlertController class], nil];
- strongSelf.enabledToolbarClasses = [[NSMutableSet alloc] init];
-
- strongSelf.toolbarPreviousNextAllowedClasses = [[NSMutableSet alloc] initWithObjects:[UITableView class],[UICollectionView class],[IQPreviousNextView class], nil];
-
- strongSelf.disabledTouchResignedClasses = [[NSMutableSet alloc] initWithObjects:[UIAlertController class], nil];
- strongSelf.enabledTouchResignedClasses = [[NSMutableSet alloc] init];
- strongSelf.touchResignedGestureIgnoreClasses = [[NSMutableSet alloc] initWithObjects:[UIControl class],[UINavigationBar class], nil];
-
- [self setShouldToolbarUsesTextFieldTintColor:NO];
- });
- }
- return self;
- }
- /* Automatically called from the `+(void)load` method. */
- + (IQKeyboardManager*)sharedManager
- {
- //Singleton instance
- static IQKeyboardManager *kbManager;
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
-
- kbManager = [[self alloc] init];
- });
-
- 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];
- [self showLog:@"Enabled"];
- }
- //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;
-
- [self showLog:@"Disabled"];
- }
- //If already disabled.
- else if (enable == NO &&
- _enable == NO)
- {
- [self showLog:@"Already Disabled"];
- }
- //If already enabled.
- else if (enable == YES &&
- _enable == YES)
- {
- [self showLog:@"Already Enabled"];
- }
- }
- -(BOOL)privateIsEnabled
- {
- BOOL enable = _enable;
-
- UIViewController *textFieldViewController = [_textFieldView viewController];
-
- if (textFieldViewController)
- {
- if (enable == NO)
- {
- //If viewController is kind of enable viewController class, then assuming it's enabled.
- for (Class enabledClass in _enabledDistanceHandlingClasses)
- {
- if ([textFieldViewController isKindOfClass:enabledClass])
- {
- enable = YES;
- break;
- }
- }
- }
-
- if (enable)
- {
- //If viewController is kind of disable viewController class, then assuming it's disable.
- for (Class disabledClass in _disabledDistanceHandlingClasses)
- {
- if ([textFieldViewController isKindOfClass:disabledClass])
- {
- enable = NO;
- break;
- }
- }
-
- //Special Controllers
- if (enable == YES)
- {
- NSString *classNameString = NSStringFromClass([textFieldViewController class]);
- //_UIAlertControllerTextFieldViewController
- if ([classNameString containsString:@"UIAlertController"] && [classNameString hasSuffix:@"TextFieldViewController"])
- {
- enable = NO;
- }
- }
- }
- }
-
- return enable;
- }
- -(BOOL)shouldShowTextFieldPlaceholder
- {
- return _shouldShowToolbarPlaceholder;
- }
- -(void)setShouldShowTextFieldPlaceholder:(BOOL)shouldShowTextFieldPlaceholder
- {
- _shouldShowToolbarPlaceholder = shouldShowTextFieldPlaceholder;
- }
- // Setting keyboard distance from text field.
- -(void)setKeyboardDistanceFromTextField:(CGFloat)keyboardDistanceFromTextField
- {
- //Can't be less than zero. Minimum is zero.
- _keyboardDistanceFromTextField = MAX(keyboardDistanceFromTextField, 0);
- [self showLog:[NSString stringWithFormat:@"keyboardDistanceFromTextField: %.2f",_keyboardDistanceFromTextField]];
- }
- /** Enabling/disable gesture on touching. */
- -(void)setShouldResignOnTouchOutside:(BOOL)shouldResignOnTouchOutside
- {
- [self showLog:[NSString stringWithFormat:@"shouldResignOnTouchOutside: %@",shouldResignOnTouchOutside?@"Yes":@"No"]];
-
- _shouldResignOnTouchOutside = shouldResignOnTouchOutside;
-
- //Enable/Disable gesture recognizer (Enhancement ID: #14)
- [_resignFirstResponderGesture setEnabled:[self privateShouldResignOnTouchOutside]];
- }
- -(BOOL)privateShouldResignOnTouchOutside
- {
- BOOL shouldResignOnTouchOutside = _shouldResignOnTouchOutside;
-
- UIViewController *textFieldViewController = [_textFieldView viewController];
-
- if (textFieldViewController)
- {
- if (shouldResignOnTouchOutside == NO)
- {
- //If viewController is kind of enable viewController class, then assuming shouldResignOnTouchOutside is enabled.
- for (Class enabledClass in _enabledTouchResignedClasses)
- {
- if ([textFieldViewController isKindOfClass:enabledClass])
- {
- shouldResignOnTouchOutside = YES;
- break;
- }
- }
- }
-
- if (shouldResignOnTouchOutside)
- {
- //If viewController is kind of disable viewController class, then assuming shouldResignOnTouchOutside is disable.
- for (Class disabledClass in _disabledTouchResignedClasses)
- {
- if ([textFieldViewController isKindOfClass:disabledClass])
- {
- shouldResignOnTouchOutside = NO;
- break;
- }
- }
- //Special Controllers
- if (shouldResignOnTouchOutside == YES)
- {
- NSString *classNameString = NSStringFromClass([textFieldViewController class]);
-
- //_UIAlertControllerTextFieldViewController
- if ([classNameString containsString:@"UIAlertController"] && [classNameString hasSuffix:@"TextFieldViewController"])
- {
- shouldResignOnTouchOutside = NO;
- }
- }
- }
- }
-
- return shouldResignOnTouchOutside;
- }
- /** Enable/disable autotoolbar. Adding and removing toolbar if required. */
- -(void)setEnableAutoToolbar:(BOOL)enableAutoToolbar
- {
- _enableAutoToolbar = enableAutoToolbar;
-
- [self showLog:[NSString stringWithFormat:@"enableAutoToolbar: %@",enableAutoToolbar?@"Yes":@"No"]];
- //If enabled then adding toolbar.
- if ([self privateIsEnableAutoToolbar] == YES)
- {
- [self addToolbarIfRequired];
- }
- //Else removing toolbar.
- else
- {
- [self removeToolbarIfRequired];
- }
- }
- -(BOOL)privateIsEnableAutoToolbar
- {
- BOOL enableAutoToolbar = _enableAutoToolbar;
-
- UIViewController *textFieldViewController = [_textFieldView viewController];
-
- if (textFieldViewController)
- {
- if (enableAutoToolbar == NO)
- {
- //If found any toolbar enabled classes then return.
- for (Class enabledToolbarClass in _enabledToolbarClasses)
- {
- if ([textFieldViewController isKindOfClass:enabledToolbarClass])
- {
- enableAutoToolbar = YES;
- break;
- }
- }
- }
-
- if (enableAutoToolbar)
- {
- //If found any toolbar disabled classes then return.
- for (Class disabledToolbarClass in _disabledToolbarClasses)
- {
- if ([textFieldViewController isKindOfClass:disabledToolbarClass])
- {
- enableAutoToolbar = NO;
- break;
- }
- }
-
-
- //Special Controllers
- if (enableAutoToolbar == YES)
- {
- NSString *classNameString = NSStringFromClass([textFieldViewController class]);
-
- //_UIAlertControllerTextFieldViewController
- if ([classNameString containsString:@"UIAlertController"] && [classNameString hasSuffix:@"TextFieldViewController"])
- {
- enableAutoToolbar = NO;
- }
- }
- }
- }
-
- return enableAutoToolbar;
- }
- #pragma mark - Private Methods
- /** Getting keyWindow. */
- -(UIWindow *)keyWindow
- {
- if (_textFieldView.window)
- {
- return _textFieldView.window;
- }
- else
- {
- static __weak 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;
- }
- }
- /* 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] topMostWindowController];
-
- //frame size needs to be adjusted on iOS8 due to orientation API changes.
- frame.size = controller.view.frame.size;
- // If can't get rootViewController then printing warning to user.
- if (controller == nil)
- [self showLog:@"You must set UIWindow.rootViewController in your AppDelegate to work with IQKeyboardManager"];
-
- __weak typeof(self) weakSelf = self;
-
- #ifdef __IPHONE_11_0
- UIEdgeInsets safeAreaNewInset = UIEdgeInsetsZero;
-
- if (self.canAdjustAdditionalSafeAreaInsets == YES)
- {
- if (@available(iOS 11.0, *)) {
- safeAreaNewInset = self.initialAdditionalSafeAreaInsets;
- CGFloat viewMovement = CGRectGetMaxY(_topViewBeginRect)-CGRectGetMaxY(frame);
-
- //Maintain keyboardDistanceFromTextField
- CGFloat specialKeyboardDistanceFromTextField = _textFieldView.keyboardDistanceFromTextField;
-
- if (_textFieldView.isSearchBarTextField)
- {
- UISearchBar *searchBar = (UISearchBar*)[_textFieldView superviewOfClassType:[UISearchBar class]];
- specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField;
- }
-
- CGFloat keyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance)?_keyboardDistanceFromTextField:specialKeyboardDistanceFromTextField;
-
- CGFloat textFieldDistance = _textFieldView.frame.size.height + keyboardDistanceFromTextField;
- safeAreaNewInset.bottom += MIN(viewMovement, textFieldDistance);
- }
- }
- #endif
- //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
- __strong typeof(self) strongSelf = weakSelf;
- #ifdef __IPHONE_11_0
- if (self.canAdjustAdditionalSafeAreaInsets == YES)
- {
- if (@available(iOS 11.0, *)) {
- controller.additionalSafeAreaInsets = safeAreaNewInset;
- }
- }
- #endif
- // Setting it's new frame
- [controller.view setFrame:frame];
-
- //Animating content if needed (Bug ID: #204)
- if (strongSelf.layoutIfNeededOnUpdate)
- {
- //Animating content (Bug ID: #160)
- [controller.view setNeedsLayout];
- [controller.view layoutIfNeeded];
- }
-
- [self showLog:[NSString stringWithFormat:@"Set %@ frame to : %@",[controller _IQDescription],NSStringFromCGRect(frame)]];
- } completion:NULL];
- }
- /* Adjusting RootViewController's frame according to interface orientation. */
- -(void)adjustFrame
- {
- // We are unable to get textField object while keyboard showing on UIWebView's textField. (Bug ID: #11)
- if (_textFieldView == nil) return;
-
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- // Getting KeyWindow object.
- UIWindow *keyWindow = [self keyWindow];
-
- // Getting RootViewController. (Bug ID: #1, #4)
- UIViewController *rootController = [_textFieldView topMostController];
- if (rootController == nil) rootController = [keyWindow topMostWindowController];
-
- // Converting Rectangle according to window bounds.
- CGRect textFieldViewRect = [[_textFieldView superview] convertRect:_textFieldView.frame toView:keyWindow];
- // Getting RootViewRect.
- CGRect rootViewRect = [[rootController view] frame];
- //Getting statusBarFrame
- //Maintain keyboardDistanceFromTextField
- CGFloat specialKeyboardDistanceFromTextField = _textFieldView.keyboardDistanceFromTextField;
-
- if (_textFieldView.isSearchBarTextField)
- {
- UISearchBar *searchBar = (UISearchBar*)[_textFieldView superviewOfClassType:[UISearchBar class]];
- specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField;
- }
-
- CGFloat keyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance)?_keyboardDistanceFromTextField:specialKeyboardDistanceFromTextField;
- CGSize kbSize = _kbSize;
- kbSize.height += keyboardDistanceFromTextField;
- CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
-
- // (Bug ID: #250)
- IQLayoutGuidePosition layoutGuidePosition = IQLayoutGuidePositionNone;
-
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- //If topLayoutGuide constraint
- if (_layoutGuideConstraint && (_layoutGuideConstraint.firstItem == [[_textFieldView viewController] topLayoutGuide] ||
- _layoutGuideConstraint.secondItem == [[_textFieldView viewController] topLayoutGuide]))
- {
- layoutGuidePosition = IQLayoutGuidePositionTop;
- }
- //If bottomLayoutGuice constraint
- else if (_layoutGuideConstraint && (_layoutGuideConstraint.firstItem == [[_textFieldView viewController] bottomLayoutGuide] ||
- _layoutGuideConstraint.secondItem == [[_textFieldView viewController] bottomLayoutGuide]))
- {
- layoutGuidePosition = IQLayoutGuidePositionBottom;
- }
- #pragma clang diagnostic pop
- CGFloat topLayoutGuide = CGRectGetHeight(statusBarFrame);
- CGFloat move = 0;
- // +Move positive = textField is hidden.
- // -Move negative = textField is showing.
-
- // Checking if there is bottomLayoutGuide attached (Bug ID: #250)
- if (layoutGuidePosition == IQLayoutGuidePositionBottom)
- {
- // Calculating move position.
- move = CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-kbSize.height);
- }
- else
- {
- // Calculating move position. Common for both normal and special cases.
- move = MIN(CGRectGetMinY(textFieldViewRect)-(topLayoutGuide+5), CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-kbSize.height));
- }
-
- [self showLog:[NSString stringWithFormat:@"Need to move: %.2f",move]];
- UIScrollView *superScrollView = nil;
- UIScrollView *superView = (UIScrollView*)[_textFieldView superviewOfClassType:[UIScrollView class]];
- //Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
- while (superView)
- {
- if (superView.isScrollEnabled && superView.shouldIgnoreScrollingAdjustment == NO)
- {
- superScrollView = superView;
- break;
- }
- else
- {
- // Getting it's superScrollView. // (Enhancement ID: #21, #24)
- superView = (UIScrollView*)[superView 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)
- {
- [self showLog:[NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]];
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
-
- __strong typeof(self) strongSelf = weakSelf;
- [strongSelf.lastScrollView setContentInset:strongSelf.startingContentInsets];
- strongSelf.lastScrollView.scrollIndicatorInsets = strongSelf.startingScrollIndicatorInsets;
- } completion:NULL];
-
- if (_lastScrollView.shouldRestoreScrollViewContentOffset)
- {
- [_lastScrollView setContentOffset:_startingContentOffset animated:YES];
- }
- _startingContentInsets = UIEdgeInsetsZero;
- _startingScrollIndicatorInsets = 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)
- {
- [self showLog:[NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]];
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
-
- __strong typeof(self) strongSelf = weakSelf;
- [strongSelf.lastScrollView setContentInset:strongSelf.startingContentInsets];
- strongSelf.lastScrollView.scrollIndicatorInsets = strongSelf.startingScrollIndicatorInsets;
- } completion:NULL];
- if (_lastScrollView.shouldRestoreScrollViewContentOffset)
- {
- [_lastScrollView setContentOffset:_startingContentOffset animated:YES];
- }
-
- _lastScrollView = superScrollView;
- _startingContentInsets = superScrollView.contentInset;
- _startingScrollIndicatorInsets = superScrollView.scrollIndicatorInsets;
- _startingContentOffset = superScrollView.contentOffset;
- [self showLog:[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;
- _startingScrollIndicatorInsets = superScrollView.scrollIndicatorInsets;
- [self showLog:[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) )
- {
- UIScrollView *nextScrollView = nil;
- UIScrollView *tempScrollView = (UIScrollView*)[superScrollView superviewOfClassType:[UIScrollView class]];
-
- //Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
- while (tempScrollView)
- {
- if (tempScrollView.isScrollEnabled && tempScrollView.shouldIgnoreScrollingAdjustment == NO)
- {
- nextScrollView = tempScrollView;
- break;
- }
- else
- {
- // Getting it's superScrollView. // (Enhancement ID: #21, #24)
- tempScrollView = (UIScrollView*)[tempScrollView superviewOfClassType:[UIScrollView class]];
- }
- }
- //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)
-
- //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
- //[superScrollView superviewOfClassType:[UIScrollView class]] == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.)
- //shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
- if ([_textFieldView isKindOfClass:[UITextView class]] &&
- nextScrollView == 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];
-
- //Calculating expected fix distance which needs to be managed from navigation bar
- CGFloat expectedFixDistance = CGRectGetMinY(currentTextFieldViewRect) - maintainTopLayout;
-
- //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:^{
-
- [self showLog:[NSString stringWithFormat:@"Adjusting %.2f to %@ ContentOffset",(superScrollView.contentOffset.y-shouldOffsetY),[superScrollView _IQDescription]]];
- [self showLog:[NSString stringWithFormat:@"Remaining Move: %.2f",move]];
- superScrollView.contentOffset = CGPointMake(superScrollView.contentOffset.x, shouldOffsetY);
- } completion:NULL];
- // Getting next lastView & superScrollView.
- lastView = superScrollView;
- superScrollView = nextScrollView;
- }
-
- //Updating contentInset
- {
- CGRect lastScrollViewRect = [[_lastScrollView superview] convertRect:_lastScrollView.frame toView:keyWindow];
- CGFloat bottom = kbSize.height-keyboardDistanceFromTextField-(CGRectGetHeight(keyWindow.frame)-CGRectGetMaxY(lastScrollViewRect));
- // 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);
-
- [self showLog:[NSString stringWithFormat:@"%@ old ContentInset : %@",[_lastScrollView _IQDescription], NSStringFromUIEdgeInsets(_lastScrollView.contentInset)]];
-
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
-
- __strong typeof(self) strongSelf = weakSelf;
- strongSelf.lastScrollView.contentInset = movedInsets;
-
- UIEdgeInsets newInset = strongSelf.lastScrollView.scrollIndicatorInsets;
- newInset.bottom = movedInsets.bottom;
- strongSelf.lastScrollView.scrollIndicatorInsets = newInset;
- } completion:NULL];
- [self showLog:[NSString stringWithFormat:@"%@ new ContentInset : %@",[_lastScrollView _IQDescription], NSStringFromUIEdgeInsets(_lastScrollView.contentInset)]];
- }
- }
- //Going ahead. No else if.
- }
-
- if (layoutGuidePosition == IQLayoutGuidePositionTop)
- {
- CGFloat constant = MIN(_layoutGuideConstraintInitialConstant, _layoutGuideConstraint.constant-move);
-
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
- __strong typeof(self) strongSelf = weakSelf;
- weakSelf.layoutGuideConstraint.constant = constant;
- [strongSelf.rootViewController.view setNeedsLayout];
- [strongSelf.rootViewController.view layoutIfNeeded];
- } completion:NULL];
- }
- //If bottomLayoutGuice constraint
- else if (layoutGuidePosition == IQLayoutGuidePositionBottom)
- {
- CGFloat constant = MAX(_layoutGuideConstraintInitialConstant, _layoutGuideConstraint.constant+move);
-
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
- __strong typeof(self) strongSelf = weakSelf;
- weakSelf.layoutGuideConstraint.constant = constant;
- [strongSelf.rootViewController.view setNeedsLayout];
- [strongSelf.rootViewController.view layoutIfNeeded];
- } completion:NULL];
- }
- //If not constraint
- else
- {
- //Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen)
- //_lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
- //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
- if ([_textFieldView isKindOfClass:[UITextView class]])
- {
- UITextView *textView = (UITextView*)_textFieldView;
- CGFloat textViewHeight = MIN(CGRectGetHeight(_textFieldView.frame), (CGRectGetHeight(keyWindow.frame)-kbSize.height-(topLayoutGuide)));
-
- if (_textFieldView.frame.size.height-textView.contentInset.bottom>textViewHeight)
- {
- __weak typeof(self) weakSelf = self;
-
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
-
- __strong typeof(self) strongSelf = weakSelf;
-
- [self showLog:[NSString stringWithFormat:@"%@ Old UITextView.contentInset : %@",[strongSelf.textFieldView _IQDescription], NSStringFromUIEdgeInsets(textView.contentInset)]];
-
- //_isTextViewContentInsetChanged, If frame is not change by library in past, then saving user textView properties (Bug ID: #92)
- if (strongSelf.isTextViewContentInsetChanged == NO)
- {
- strongSelf.startingTextViewContentInsets = textView.contentInset;
- strongSelf.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets;
- }
-
- UIEdgeInsets newContentInset = textView.contentInset;
- newContentInset.bottom = strongSelf.textFieldView.frame.size.height-textViewHeight;
- textView.contentInset = newContentInset;
- textView.scrollIndicatorInsets = newContentInset;
- strongSelf.isTextViewContentInsetChanged = YES;
-
- [self showLog:[NSString stringWithFormat:@"%@ New UITextView.contentInset : %@",[strongSelf.textFieldView _IQDescription], NSStringFromUIEdgeInsets(textView.contentInset)]];
-
- } completion:NULL];
- }
- }
- // Special case for iPad modalPresentationStyle.
- if ([rootController modalPresentationStyle] == UIModalPresentationFormSheet ||
- [rootController modalPresentationStyle] == UIModalPresentationPageSheet)
- {
- [self showLog:[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 = (CGRectGetHeight(keyWindow.frame)-rootViewRect.size.height-topLayoutGuide)/2-(kbSize.height-keyboardDistanceFromTextField);
-
- rootViewRect.origin.y = MAX(rootViewRect.origin.y, minimumY);
- }
-
- [self showLog:@"Moving Upward"];
- // Setting adjusted rootViewRect
- [self setRootViewFrame:rootViewRect];
- _movedDistance = (_topViewBeginRect.origin.y-rootViewRect.origin.y);
- }
- // -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);
-
- [self showLog:@"Moving Downward"];
- // Setting adjusted rootViewRect
- [self setRootViewFrame:rootViewRect];
- _movedDistance = (_topViewBeginRect.origin.y-rootViewRect.origin.y);
- }
- }
- }
- //If presentation style is neither UIModalPresentationFormSheet nor UIModalPresentationPageSheet then going ahead.(General case)
- else
- {
- // +Positive or zero.
- if (move>=0)
- {
- 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)
- {
- rootViewRect.origin.y = MAX(rootViewRect.origin.y, MIN(0, -kbSize.height+keyboardDistanceFromTextField));
- }
-
- [self showLog:@"Moving Upward"];
- // Setting adjusted rootViewRect
- [self setRootViewFrame:rootViewRect];
- _movedDistance = (_topViewBeginRect.origin.y-rootViewRect.origin.y);
- }
- // -Negative
- else
- {
- CGFloat disturbDistance = CGRectGetMinY(rootViewRect)-CGRectGetMinY(_topViewBeginRect);
-
- // disturbDistance Negative = frame disturbed. Pull Request #3
- // disturbDistance positive = frame not disturbed.
- if(disturbDistance<=0)
- {
- rootViewRect.origin.y -= MAX(move, disturbDistance);
-
- [self showLog:@"Moving Downward"];
- // Setting adjusted rootViewRect
- [self setRootViewFrame:rootViewRect];
- _movedDistance = (_topViewBeginRect.origin.y-rootViewRect.origin.y);
- }
- }
- }
- }
-
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- #pragma mark - Public Methods
- /* Refreshes textField/textView position if any external changes is explicitly made by user. */
- - (void)reloadLayoutIfNeeded
- {
- if ([self privateIsEnabled] == YES)
- {
- if (_textFieldView != nil &&
- _keyboardShowing == YES &&
- CGRectEqualToRect(_topViewBeginRect, CGRectZero) == false &&
- [_textFieldView isAlertViewTextField] == NO)
- {
- [self adjustFrame];
- }
- }
- }
- #pragma mark - UIKeyboad Notification methods
- /* UIKeyboardWillShowNotification. */
- -(void)keyboardWillShow:(NSNotification*)aNotification
- {
- _kbShowNotification = aNotification;
-
- // Boolean to know keyboard is showing/hiding
- _keyboardShowing = YES;
-
- // Getting keyboard animation.
- NSInteger curve = [[aNotification userInfo][UIKeyboardAnimationCurveUserInfoKey] integerValue];
- _animationCurve = curve<<16;
- // Getting keyboard animation duration
- CGFloat duration = [[aNotification userInfo][UIKeyboardAnimationDurationUserInfoKey] floatValue];
-
- //Saving animation duration
- if (duration != 0.0) _animationDuration = duration;
-
- CGSize oldKBSize = _kbSize;
-
- // Getting UIKeyboardSize.
- CGRect kbFrame = [[aNotification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
- CGRect screenSize = [[UIScreen mainScreen] bounds];
- //Calculating actual keyboard displayed size, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381)
- CGRect intersectRect = CGRectIntersection(kbFrame, screenSize);
- if (CGRectIsNull(intersectRect))
- {
- _kbSize = CGSizeMake(screenSize.size.width, 0);
- }
- else
- {
- _kbSize = intersectRect.size;
- }
-
- if ([self privateIsEnabled] == NO) return;
-
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- if (_textFieldView != nil && CGRectEqualToRect(_topViewBeginRect, CGRectZero)) // (Bug ID: #5)
- {
- // keyboard is not showing(At the beginning only). We should save rootViewRect and _layoutGuideConstraintInitialConstant.
- _layoutGuideConstraint = [[_textFieldView viewController] IQLayoutGuideConstraint];
- _layoutGuideConstraintInitialConstant = [_layoutGuideConstraint constant];
- // keyboard is not showing(At the beginning only). We should save rootViewRect.
- _rootViewController = [_textFieldView topMostController];
- if (_rootViewController == nil) _rootViewController = [[self keyWindow] topMostWindowController];
- _topViewBeginRect = _rootViewController.view.frame;
-
- #ifdef __IPHONE_11_0
- if (@available(iOS 11.0, *)) {
- self.initialAdditionalSafeAreaInsets = _rootViewController.additionalSafeAreaInsets;
- }
- #endif
-
- if (_topViewBeginRect.origin.y != 0 &&
- _shouldFixInteractivePopGestureRecognizer &&
- [_rootViewController isKindOfClass:[UINavigationController class]] &&
- [_rootViewController modalPresentationStyle] != UIModalPresentationFormSheet &&
- [_rootViewController modalPresentationStyle] != UIModalPresentationPageSheet)
- {
- UIWindow *window = [self keyWindow];
- if (window)
- {
- _topViewBeginRect.origin.y = window.frame.size.height-_rootViewController.view.frame.size.height;
- }
- else
- {
- _topViewBeginRect.origin.y = 0;
- }
- }
- [self showLog:[NSString stringWithFormat:@"Saving %@ beginning Frame: %@",[_rootViewController _IQDescription] ,NSStringFromCGRect(_topViewBeginRect)]];
- }
- //If last restored keyboard size is different(any orientation accure), then refresh. otherwise not.
- if (!CGSizeEqualToSize(_kbSize, oldKBSize))
- {
- //If _textFieldView is inside UIAlertView 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 (_keyboardShowing == YES &&
- _textFieldView != nil &&
- [_textFieldView isAlertViewTextField] == NO)
- {
- [self adjustFrame];
- }
- }
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- /* UIKeyboardDidShowNotification. */
- - (void)keyboardDidShow:(NSNotification*)aNotification
- {
- if ([self privateIsEnabled] == NO) return;
-
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
-
- // Getting topMost ViewController.
- UIViewController *controller = [_textFieldView topMostController];
- if (controller == nil) controller = [[self keyWindow] topMostWindowController];
- //If _textFieldView viewController is presented as formSheet, then adjustFrame again because iOS internally update formSheet frame on keyboardShown. (Bug ID: #37, #74, #76)
- if (_keyboardShowing == YES &&
- _textFieldView != nil &&
- (controller.modalPresentationStyle == UIModalPresentationFormSheet || controller.modalPresentationStyle == UIModalPresentationPageSheet) &&
- [_textFieldView isAlertViewTextField] == NO)
- {
- //In case of form sheet or page sheet, we'll add adjustFrame call in main queue to perform it when UI thread will do all framing updation so adjustFrame will be executed after all internal operations.
- [[NSOperationQueue mainQueue] addOperationWithBlock:^{
- [self adjustFrame];
- }];
- }
-
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- /* 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;
-
- // Boolean to know keyboard is showing/hiding
- _keyboardShowing = NO;
-
- // Getting keyboard animation duration
- CGFloat aDuration = [[aNotification userInfo][UIKeyboardAnimationDurationUserInfoKey] floatValue];
- if (aDuration!= 0.0f)
- {
- _animationDuration = aDuration;
- }
-
- //If not enabled then do nothing.
- if ([self privateIsEnabled] == NO) return;
-
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[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;
- //Restoring the contentOffset of the lastScrollView
- if (_lastScrollView)
- {
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
-
- __strong typeof(self) strongSelf = weakSelf;
- strongSelf.lastScrollView.contentInset = strongSelf.startingContentInsets;
- strongSelf.lastScrollView.scrollIndicatorInsets = strongSelf.startingScrollIndicatorInsets;
-
- if (strongSelf.lastScrollView.shouldRestoreScrollViewContentOffset)
- {
- strongSelf.lastScrollView.contentOffset = strongSelf.startingContentOffset;
- }
- [self showLog:[NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[strongSelf.lastScrollView _IQDescription],NSStringFromUIEdgeInsets(strongSelf.startingContentInsets),NSStringFromCGPoint(strongSelf.startingContentOffset)]];
-
- // TODO: restore scrollView state
- // This is temporary solution. Have to implement the save and restore scrollView state
- UIScrollView *superscrollView = strongSelf.lastScrollView;
- do
- {
- 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);
-
- [self showLog:[NSString stringWithFormat:@"Restoring %@ contentOffset to : %@",[superscrollView _IQDescription],NSStringFromCGPoint(superscrollView.contentOffset)]];
- }
- } while ((superscrollView = (UIScrollView*)[superscrollView superviewOfClassType:[UIScrollView class]]));
- } 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.
- _topViewBeginRect.size = _rootViewController.view.frame.size;
-
- __weak typeof(self) weakSelf = self;
- //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
- __strong typeof(self) strongSelf = weakSelf;
- //If done LayoutGuide tweak
- if (weakSelf.layoutGuideConstraint)
- {
- weakSelf.layoutGuideConstraint.constant = strongSelf.layoutGuideConstraintInitialConstant;
- [strongSelf.rootViewController.view setNeedsLayout];
- [strongSelf.rootViewController.view layoutIfNeeded];
- }
- else
- {
- [self showLog:[NSString stringWithFormat:@"Restoring %@ frame to : %@",[strongSelf.rootViewController _IQDescription],NSStringFromCGRect(strongSelf.topViewBeginRect)]];
- // Setting it's new frame
- [strongSelf.rootViewController.view setFrame:strongSelf.topViewBeginRect];
- #ifdef __IPHONE_11_0
- if (@available(iOS 11.0, *)) {
- strongSelf.rootViewController.additionalSafeAreaInsets = strongSelf.initialAdditionalSafeAreaInsets;
- }
- #endif
- strongSelf.movedDistance = 0;
- //Animating content if needed (Bug ID: #204)
- if (strongSelf.layoutIfNeededOnUpdate)
- {
- //Animating content (Bug ID: #160)
- [strongSelf.rootViewController.view setNeedsLayout];
- [strongSelf.rootViewController.view layoutIfNeeded];
- }
- }
- } completion:NULL];
- _rootViewController = nil;
- }
- //If done LayoutGuide tweak
- else if (_layoutGuideConstraint)
- {
- __weak typeof(self) weakSelf = self;
-
- //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
- __strong typeof(self) strongSelf = weakSelf;
- weakSelf.layoutGuideConstraint.constant = strongSelf.layoutGuideConstraintInitialConstant;
- [strongSelf.rootViewController.view setNeedsLayout];
- [strongSelf.rootViewController.view layoutIfNeeded];
- } completion:NULL];
- }
- //Reset all values
- _layoutGuideConstraint = nil;
- _layoutGuideConstraintInitialConstant = 0;
- _lastScrollView = nil;
- _kbSize = CGSizeZero;
- _startingContentInsets = UIEdgeInsetsZero;
- _startingScrollIndicatorInsets = UIEdgeInsetsZero;
- _startingContentOffset = CGPointZero;
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- /* UIKeyboardDidHideNotification. So topViewBeginRect can be set to CGRectZero. */
- - (void)keyboardDidHide:(NSNotification*)aNotification
- {
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- _topViewBeginRect = CGRectZero;
- #ifdef __IPHONE_11_0
- if (@available(iOS 11.0, *)) {
- self.initialAdditionalSafeAreaInsets = UIEdgeInsetsZero;
- }
- #endif
-
- _kbSize = CGSizeZero;
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- #pragma mark - UITextFieldView Delegate methods
- /** UITextFieldTextDidBeginEditingNotification, UITextViewTextDidBeginEditingNotification. Fetching UITextFieldView object. */
- -(void)textFieldViewDidBeginEditing:(NSNotification*)notification
- {
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- // Getting object
- _textFieldView = notification.object;
-
- if (_overrideKeyboardAppearance == YES)
- {
- UITextField *textField = (UITextField*)_textFieldView;
-
- if ([textField respondsToSelector:@selector(keyboardAppearance)])
- {
- //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];
- }
- }
- }
-
- //If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required.
- if ([self privateIsEnableAutoToolbar])
- {
- //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)
- {
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:0.00001 delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
- [self addToolbarIfRequired];
- } completion:^(BOOL finished) {
- __strong typeof(self) strongSelf = weakSelf;
- //On textView toolbar didn't appear on first time, so forcing textView to reload it's inputViews.
- [strongSelf.textFieldView reloadInputViews];
- }];
- }
- //Else adding toolbar
- else
- {
- [self addToolbarIfRequired];
- }
- }
- else
- {
- [self removeToolbarIfRequired];
- }
-
- //Adding Geture recognizer to window (Enhancement ID: #14)
- [_resignFirstResponderGesture setEnabled:[self privateShouldResignOnTouchOutside]];
- [_textFieldView.window addGestureRecognizer:_resignFirstResponderGesture];
- if ([self privateIsEnabled] == YES)
- {
- if (CGRectEqualToRect(_topViewBeginRect, CGRectZero)) // (Bug ID: #5)
- {
- // keyboard is not showing(At the beginning only). We should save rootViewRect and _layoutGuideConstraintInitialConstant.
- _layoutGuideConstraint = [[_textFieldView viewController] IQLayoutGuideConstraint];
- _layoutGuideConstraintInitialConstant = [_layoutGuideConstraint constant];
-
- _rootViewController = [_textFieldView topMostController];
- if (_rootViewController == nil) _rootViewController = [[self keyWindow] topMostWindowController];
-
- _topViewBeginRect = _rootViewController.view.frame;
-
- #ifdef __IPHONE_11_0
- if (@available(iOS 11.0, *)) {
- self.initialAdditionalSafeAreaInsets = _rootViewController.additionalSafeAreaInsets;
- }
- #endif
- if (_topViewBeginRect.origin.y != 0 &&
- _shouldFixInteractivePopGestureRecognizer &&
- [_rootViewController isKindOfClass:[UINavigationController class]] &&
- [_rootViewController modalPresentationStyle] != UIModalPresentationFormSheet &&
- [_rootViewController modalPresentationStyle] != UIModalPresentationPageSheet)
- {
- UIWindow *window = [self keyWindow];
- if (window)
- {
- _topViewBeginRect.origin.y = window.frame.size.height-_rootViewController.view.frame.size.height;
- }
- else
- {
- _topViewBeginRect.origin.y = 0;
- }
- }
-
- [self showLog:[NSString stringWithFormat:@"Saving %@ beginning Frame: %@",[_rootViewController _IQDescription], NSStringFromCGRect(_topViewBeginRect)]];
- }
-
- //If _textFieldView is inside UIAlertView 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 (_keyboardShowing == YES &&
- _textFieldView != nil &&
- [_textFieldView isAlertViewTextField] == NO)
- {
- // keyboard is already showing. adjust frame.
- [self adjustFrame];
- }
- }
-
- // if ([_textFieldView isKindOfClass:[UITextField class]])
- // {
- // [(UITextField*)_textFieldView addTarget:self action:@selector(editingDidEndOnExit:) forControlEvents:UIControlEventEditingDidEndOnExit];
- // }
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- /** UITextFieldTextDidEndEditingNotification, UITextViewTextDidEndEditingNotification. Removing fetched object. */
- -(void)textFieldViewDidEndEditing:(NSNotification*)notification
- {
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- //Removing gesture recognizer (Enhancement ID: #14)
- [_textFieldView.window removeGestureRecognizer:_resignFirstResponderGesture];
-
- // if ([_textFieldView isKindOfClass:[UITextField class]])
- // {
- // [(UITextField*)_textFieldView removeTarget:self action:@selector(editingDidEndOnExit:) forControlEvents:UIControlEventEditingDidEndOnExit];
- // }
- // We check if there's a change in original frame or not.
- if(_isTextViewContentInsetChanged == YES &&
- [_textFieldView isKindOfClass:[UITextView class]])
- {
- UITextView *textView = (UITextView*)_textFieldView;
- __weak typeof(self) weakSelf = self;
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
-
- __strong typeof(self) strongSelf = weakSelf;
- strongSelf.isTextViewContentInsetChanged = NO;
- [self showLog:[NSString stringWithFormat:@"Restoring %@ textView.contentInset to : %@",[strongSelf.textFieldView _IQDescription],NSStringFromUIEdgeInsets(strongSelf.startingTextViewContentInsets)]];
- //Setting textField to it's initial contentInset
- textView.contentInset = strongSelf.startingTextViewContentInsets;
- textView.scrollIndicatorInsets = strongSelf.startingTextViewScrollIndicatorInsets;
- } completion:NULL];
- }
-
- //Setting object to nil
- _textFieldView = nil;
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- //-(void)editingDidEndOnExit:(UITextField*)textField
- //{
- // [self showLog:[NSString stringWithFormat:@"ReturnKey %@",NSStringFromSelector(_cmd)]];
- //}
- #pragma mark - UIStatusBar 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
- {
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- //If textViewContentInsetChanged is changed then restore it.
- if (_isTextViewContentInsetChanged == YES &&
- [_textFieldView isKindOfClass:[UITextView class]])
- {
- UITextView *textView = (UITextView*)_textFieldView;
- __weak typeof(self) weakSelf = self;
- //Due to orientation callback we need to set it's original position.
- [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
-
- __strong typeof(self) strongSelf = weakSelf;
- strongSelf.isTextViewContentInsetChanged = NO;
- [self showLog:[NSString stringWithFormat:@"Restoring %@ textView.contentInset to : %@",[strongSelf.textFieldView _IQDescription],NSStringFromUIEdgeInsets(strongSelf.startingTextViewContentInsets)]];
-
- //Setting textField to it's initial contentInset
- textView.contentInset = strongSelf.startingTextViewContentInsets;
- textView.scrollIndicatorInsets = strongSelf.startingTextViewScrollIndicatorInsets;
- } completion:NULL];
- }
- if ([self privateIsEnabled] == NO) return;
- if (_rootViewController)
- {
- #ifdef __IPHONE_11_0
- if (@available(iOS 11.0, *)) {
- if (UIEdgeInsetsEqualToEdgeInsets(self.initialAdditionalSafeAreaInsets, _rootViewController.additionalSafeAreaInsets) == NO)
- {
- _rootViewController.additionalSafeAreaInsets = self.initialAdditionalSafeAreaInsets;
- }
- }
- #endif
- }
-
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- /** UIApplicationDidChangeStatusBarFrameNotification. Need to refresh view position and update _topViewBeginRect. (Bug ID: #446)*/
- - (void)didChangeStatusBarFrame:(NSNotification*)aNotification
- {
- CGRect oldStatusBarFrame = _statusBarFrame;
-
- // Getting UIStatusBarFrame
- _statusBarFrame = [[aNotification userInfo][UIApplicationStatusBarFrameUserInfoKey] CGRectValue];
-
- if ([self privateIsEnabled] == NO) return;
-
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- if (_rootViewController &&
- !CGRectEqualToRect(_topViewBeginRect, _rootViewController.view.frame))
- {
- _topViewBeginRect = _rootViewController.view.frame;
- #ifdef __IPHONE_11_0
- if (@available(iOS 11.0, *)) {
- self.initialAdditionalSafeAreaInsets = _rootViewController.additionalSafeAreaInsets;
- }
- #endif
- if (_topViewBeginRect.origin.y != 0 &&
- _shouldFixInteractivePopGestureRecognizer &&
- [_rootViewController isKindOfClass:[UINavigationController class]] &&
- [_rootViewController modalPresentationStyle] != UIModalPresentationFormSheet &&
- [_rootViewController modalPresentationStyle] != UIModalPresentationPageSheet)
- {
- UIWindow *window = [self keyWindow];
- if (window)
- {
- _topViewBeginRect.origin.y = window.frame.size.height-_rootViewController.view.frame.size.height;
- }
- else
- {
- _topViewBeginRect.origin.y = 0;
- }
- }
-
- [self showLog:[NSString stringWithFormat:@"Saving %@ beginning Frame: %@",[_rootViewController _IQDescription] ,NSStringFromCGRect(_topViewBeginRect)]];
- }
-
- //If _textFieldView is inside UIAlertView 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 (_keyboardShowing == YES &&
- _textFieldView != nil &&
- CGSizeEqualToSize(_statusBarFrame.size, oldStatusBarFrame.size) == NO &&
- [_textFieldView isAlertViewTextField] == NO)
- {
- [self adjustFrame];
- }
-
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- #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)
- for (Class aClass in self.touchResignedGestureIgnoreClasses)
- {
- if ([[touch view] isKindOfClass:aClass])
- {
- return NO;
- }
- }
- return YES;
- }
- /** Resigning textField. */
- - (BOOL)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];
-
- [self showLog:[NSString stringWithFormat:@"Refuses to Resign first responder: %@",[_textFieldView _IQDescription]]];
- }
-
- return isResignFirstResponder;
- }
- else
- {
- return NO;
- }
- }
- /** Returns YES if can navigate to previous responder textField/textView, otherwise NO. */
- -(BOOL)canGoPrevious
- {
- //Getting all responder view's.
- NSArray *textFields = [self responderViews];
- //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 != NSNotFound &&
- index > 0)
- {
- return YES;
- }
- else
- {
- 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];
-
- //Getting index of current textField.
- NSUInteger index = [textFields indexOfObject:_textFieldView];
-
- //If it is not last textField. then it's next object becomeFirstResponder.
- if (index != NSNotFound &&
- index < textFields.count-1)
- {
- return YES;
- }
- else
- {
- return NO;
- }
- }
- /** Navigate to previous responder textField/textView. */
- -(BOOL)goPrevious
- {
- //Getting all responder view's.
- NSArray *textFields = [self responderViews];
-
- //Getting index of current textField.
- NSUInteger index = [textFields indexOfObject:_textFieldView];
-
- //If it is not first textField. then it's previous object becomeFirstResponder.
- if (index != NSNotFound &&
- index > 0)
- {
- UITextField *nextTextField = textFields[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];
-
- [self showLog:[NSString stringWithFormat:@"Refuses to become first responder: %@",[nextTextField _IQDescription]]];
- }
-
- return isAcceptAsFirstResponder;
- }
- else
- {
- return NO;
- }
- }
- /** Navigate to next responder textField/textView. */
- -(BOOL)goNext
- {
- //Getting all responder view's.
- NSArray *textFields = [self responderViews];
-
- //Getting index of current textField.
- NSUInteger index = [textFields indexOfObject:_textFieldView];
-
- //If it is not last textField. then it's next object becomeFirstResponder.
- if (index != NSNotFound &&
- index < textFields.count-1)
- {
- UITextField *nextTextField = textFields[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];
-
- [self showLog:[NSString stringWithFormat:@"Refuses to become first responder: %@",[nextTextField _IQDescription]]];
- }
-
- return isAcceptAsFirstResponder;
- }
- else
- {
- return NO;
- }
- }
- #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 _toolbarPreviousNextAllowedClasses)
- {
- 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
- {
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
-
- // Getting all the sibling textFields.
- NSArray *siblings = [self responderViews];
-
- [self showLog:[NSString stringWithFormat:@"Found %lu responder sibling(s)",(unsigned long)siblings.count]];
- //Either there is no inputAccessoryView or if accessoryView is not appropriate for current situation(There is Previous/Next/Done toolbar).
- //setInputAccessoryView: check (Bug ID: #307)
- if ([_textFieldView respondsToSelector:@selector(setInputAccessoryView:)])
- {
- if ([_textFieldView inputAccessoryView] == nil ||
- [[_textFieldView inputAccessoryView] tag] == kIQPreviousNextButtonToolbarTag ||
- [[_textFieldView inputAccessoryView] tag] == kIQDoneButtonToolbarTag)
- {
- UITextField *textField = (UITextField*)_textFieldView;
- // If only one object is found, then adding only Done button.
- if ((siblings.count==1 && self.previousNextDisplayMode == IQPreviousNextDisplayModeDefault) || self.previousNextDisplayMode == IQPreviousNextDisplayModeAlwaysHide)
- {
- //Supporting Custom Done button image (Enhancement ID: #366)
- if (_toolbarDoneBarButtonItemImage)
- {
- [textField addRightButtonOnKeyboardWithImage:_toolbarDoneBarButtonItemImage target:self action:@selector(doneAction:) shouldShowPlaceholder:_shouldShowToolbarPlaceholder];
- }
- //Supporting Custom Done button text (Enhancement ID: #209, #411, Bug ID: #376)
- else if (_toolbarDoneBarButtonItemText)
- {
- [textField addRightButtonOnKeyboardWithText:_toolbarDoneBarButtonItemText target:self action:@selector(doneAction:) shouldShowPlaceholder:_shouldShowToolbarPlaceholder];
- }
- else
- {
- //Now adding textField placeholder text as title of IQToolbar (Enhancement ID: #27)
- [textField addDoneOnKeyboardWithTarget:self action:@selector(doneAction:) shouldShowPlaceholder:_shouldShowToolbarPlaceholder];
- }
- textField.inputAccessoryView.tag = kIQDoneButtonToolbarTag; // (Bug ID: #78)
- }
- //If there is multiple siblings of textField
- else if ((siblings.count && self.previousNextDisplayMode == IQPreviousNextDisplayModeDefault) || self.previousNextDisplayMode == IQPreviousNextDisplayModeAlwaysShow)
- {
- //Supporting Custom Done button image (Enhancement ID: #366)
- if (_toolbarDoneBarButtonItemImage)
- {
- [textField addPreviousNextRightOnKeyboardWithTarget:self rightButtonImage:_toolbarDoneBarButtonItemImage previousAction:@selector(previousAction:) nextAction:@selector(nextAction:) rightButtonAction:@selector(doneAction:) shouldShowPlaceholder:_shouldShowToolbarPlaceholder];
- }
- //Supporting Custom Done button text (Enhancement ID: #209, #411, Bug ID: #376)
- else if (_toolbarDoneBarButtonItemText)
- {
- [textField addPreviousNextRightOnKeyboardWithTarget:self rightButtonTitle:_toolbarDoneBarButtonItemText previousAction:@selector(previousAction:) nextAction:@selector(nextAction:) rightButtonAction:@selector(doneAction:) shouldShowPlaceholder:_shouldShowToolbarPlaceholder];
- }
- else
- {
- //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:_shouldShowToolbarPlaceholder];
- }
- textField.inputAccessoryView.tag = kIQPreviousNextButtonToolbarTag; // (Bug ID: #78)
- }
-
- IQToolbar *toolbar = textField.keyboardToolbar;
-
- //Bar style according to keyboard appearance
- if ([textField respondsToSelector:@selector(keyboardAppearance)])
- {
- switch ([textField keyboardAppearance])
- {
- case UIKeyboardAppearanceAlert:
- {
- toolbar.barStyle = UIBarStyleBlack;
- [toolbar setTintColor:[UIColor whiteColor]];
- [toolbar setBarTintColor:nil];
- }
- break;
- default:
- {
- toolbar.barStyle = UIBarStyleDefault;
- toolbar.barTintColor = _toolbarBarTintColor;
- //Setting toolbar tintColor // (Enhancement ID: #30)
- if (_shouldToolbarUsesTextFieldTintColor)
- {
- toolbar.tintColor = [textField tintColor];
- }
- else if (_toolbarTintColor)
- {
- toolbar.tintColor = _toolbarTintColor;
- }
- else
- {
- toolbar.tintColor = [UIColor blackColor];
- }
- }
- break;
- }
-
- //If need to show placeholder
- if (_shouldShowToolbarPlaceholder &&
- textField.shouldHideToolbarPlaceholder == NO)
- {
- //Updating placeholder //(Bug ID: #148, #272)
- if (toolbar.titleBarButton.title == nil ||
- [toolbar.titleBarButton.title isEqualToString:textField.drawingToolbarPlaceholder] == NO)
- {
- [toolbar.titleBarButton setTitle:textField.drawingToolbarPlaceholder];
- }
-
- //Setting toolbar title font. // (Enhancement ID: #30)
- if (_placeholderFont &&
- [_placeholderFont isKindOfClass:[UIFont class]])
- {
- [toolbar.titleBarButton setTitleFont:_placeholderFont];
- }
- }
- else
- {
- //Updating placeholder //(Bug ID: #272)
- toolbar.titleBarButton.title = nil;
- }
- }
- //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.firstObject == textField)
- {
- if (siblings.count == 1)
- {
- textField.keyboardToolbar.previousBarButton.enabled = NO;
- textField.keyboardToolbar.nextBarButton.enabled = NO;
- }
- else
- {
- textField.keyboardToolbar.previousBarButton.enabled = NO;
- textField.keyboardToolbar.nextBarButton.enabled = YES;
- }
- }
- // If lastTextField then next should not be enaled.
- else if ([siblings lastObject] == textField)
- {
- textField.keyboardToolbar.previousBarButton.enabled = YES;
- textField.keyboardToolbar.nextBarButton.enabled = NO;
- }
- else
- {
- textField.keyboardToolbar.previousBarButton.enabled = YES;
- textField.keyboardToolbar.nextBarButton.enabled = YES;
- }
- }
- }
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- /** Remove any toolbar if it is IQToolbar. */
- -(void)removeToolbarIfRequired // (Bug ID: #18)
- {
- CFTimeInterval startTime = CACurrentMediaTime();
- [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
- // Getting all the sibling textFields.
- NSArray *siblings = [self responderViews];
-
- [self showLog:[NSString stringWithFormat:@"Found %lu responder sibling(s)",(unsigned long)siblings.count]];
- for (UITextField *textField in siblings)
- {
- UIView *toolbar = [textField inputAccessoryView];
-
- // (Bug ID: #78)
- //setInputAccessoryView: check (Bug ID: #307)
- if ([textField respondsToSelector:@selector(setInputAccessoryView:)] &&
- ([toolbar isKindOfClass:[IQToolbar class]] && (toolbar.tag == kIQDoneButtonToolbarTag || toolbar.tag == kIQPreviousNextButtonToolbarTag)))
- {
- textField.inputAccessoryView = nil;
- [textField reloadInputViews];
- }
- }
- CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
- [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******",NSStringFromSelector(_cmd),elapsedTime]];
- }
- /** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
- - (void)reloadInputViews
- {
- //If enabled then adding toolbar.
- if ([self privateIsEnableAutoToolbar] == YES)
- {
- [self addToolbarIfRequired];
- }
- //Else removing toolbar.
- else
- {
- [self removeToolbarIfRequired];
- }
- }
- #pragma mark previous/next/done functionality
- /** previousAction. */
- -(void)previousAction:(IQBarButtonItem*)barButton
- {
- //If user wants to play input Click sound. Then Play Input Click Sound.
- if (_shouldPlayInputClicks)
- {
- [[UIDevice currentDevice] playInputClick];
- }
- if ([self canGoPrevious])
- {
- UIView *currentTextFieldView = _textFieldView;
- BOOL isAcceptAsFirstResponder = [self goPrevious];
-
- if (isAcceptAsFirstResponder == YES && barButton.invocation)
- {
- if (barButton.invocation.methodSignature.numberOfArguments > 2)
- {
- [barButton.invocation setArgument:¤tTextFieldView atIndex:2];
- }
- [barButton.invocation invoke];
- }
- }
- }
- /** nextAction. */
- -(void)nextAction:(IQBarButtonItem*)barButton
- {
- //If user wants to play input Click sound. Then Play Input Click Sound.
- if (_shouldPlayInputClicks)
- {
- [[UIDevice currentDevice] playInputClick];
- }
- if ([self canGoNext])
- {
- UIView *currentTextFieldView = _textFieldView;
- BOOL isAcceptAsFirstResponder = [self goNext];
-
- if (isAcceptAsFirstResponder == YES && barButton.invocation)
- {
- if (barButton.invocation.methodSignature.numberOfArguments > 2)
- {
- [barButton.invocation setArgument:¤tTextFieldView atIndex:2];
- }
- [barButton.invocation invoke];
- }
- }
- }
- /** doneAction. Resigning current textField. */
- -(void)doneAction:(IQBarButtonItem*)barButton
- {
- //If user wants to play input Click sound. Then Play Input Click Sound.
- if (_shouldPlayInputClicks)
- {
- [[UIDevice currentDevice] playInputClick];
- }
- UIView *currentTextFieldView = _textFieldView;
- BOOL isResignedFirstResponder = [self resignFirstResponder];
-
- if (isResignedFirstResponder == YES && barButton.invocation)
- {
- if (barButton.invocation.methodSignature.numberOfArguments > 2)
- {
- [barButton.invocation setArgument:¤tTextFieldView atIndex:2];
- }
- [barButton.invocation invoke];
- }
- }
- #pragma mark - Customised textField/textView support.
- /**
- Add customised Notification for third party customised TextField/TextView.
- */
- -(void)registerTextFieldViewClass:(nonnull Class)aClass
- didBeginEditingNotificationName:(nonnull NSString *)didBeginEditingNotificationName
- didEndEditingNotificationName:(nonnull NSString *)didEndEditingNotificationName
- {
- [_registeredClasses addObject:aClass];
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidBeginEditing:) name:didBeginEditingNotificationName object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidEndEditing:) name:didEndEditingNotificationName object:nil];
- }
- /**
- Remove customised Notification for third party customised TextField/TextView.
- */
- -(void)unregisterTextFieldViewClass:(nonnull Class)aClass
- didBeginEditingNotificationName:(nonnull NSString *)didBeginEditingNotificationName
- didEndEditingNotificationName:(nonnull NSString *)didEndEditingNotificationName
- {
- [_registeredClasses removeObject:aClass];
-
- [[NSNotificationCenter defaultCenter] removeObserver:self name:didBeginEditingNotificationName object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:didEndEditingNotificationName object:nil];
- }
- -(void)registerAllNotifications
- {
- // Registering for keyboard notification.
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification 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 UITextField notification.
- [self registerTextFieldViewClass:[UITextField class]
- didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification
- didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];
-
- // Registering for UITextView notification.
- [self registerTextFieldViewClass:[UITextView class]
- didBeginEditingNotificationName:UITextViewTextDidBeginEditingNotification
- didEndEditingNotificationName:UITextViewTextDidEndEditingNotification];
-
- // Registering for orientation changes notification
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willChangeStatusBarOrientation:) name:UIApplicationWillChangeStatusBarOrientationNotification object:[UIApplication sharedApplication]];
-
- // Registering for status bar frame change notification
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:[UIApplication sharedApplication]];
- }
- -(void)unregisterAllNotifications
- {
- // Unregistering for keyboard notification.
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
- // Unregistering for UITextField notification.
- [self unregisterTextFieldViewClass:[UITextField class]
- didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification
- didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];
-
- // Unregistering for UITextView notification.
- [self unregisterTextFieldViewClass:[UITextView class]
- didBeginEditingNotificationName:UITextViewTextDidBeginEditingNotification
- didEndEditingNotificationName:UITextViewTextDidEndEditingNotification];
-
- // Unregistering for orientation changes notification
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillChangeStatusBarOrientationNotification object:[UIApplication sharedApplication]];
-
- // Unregistering for status bar frame change notification
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarFrameNotification object:[UIApplication sharedApplication]];
- }
- -(void)showLog:(NSString*)logString
- {
- if (_enableDebugging)
- {
- NSLog(@"IQKeyboardManager: %@",logString);
- }
- }
- @end
|