Nessuna descrizione

IQKeyboardManager.m 87KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106
  1. //
  2. // IQKeyboardManager.m
  3. // https://github.com/hackiftekhar/IQKeyboardManager
  4. // Copyright (c) 2013-16 Iftekhar Qurashi.
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. #import "IQKeyboardManager.h"
  24. #import "IQUIView+Hierarchy.h"
  25. #import "IQUIView+IQKeyboardToolbar.h"
  26. #import "IQNSArray+Sort.h"
  27. #import "IQKeyboardManagerConstantsInternal.h"
  28. #import "IQUIScrollView+Additions.h"
  29. #import "IQUITextFieldView+Additions.h"
  30. #import "IQUIViewController+Additions.h"
  31. #import "IQPreviousNextView.h"
  32. #import <QuartzCore/CABase.h>
  33. #import <objc/runtime.h>
  34. #import <UIKit/UIAlertController.h>
  35. #import <UIKit/UISearchBar.h>
  36. #import <UIKit/UIScreen.h>
  37. #import <UIKit/UINavigationBar.h>
  38. #import <UIKit/UITapGestureRecognizer.h>
  39. #import <UIKit/UITextField.h>
  40. #import <UIKit/UITextView.h>
  41. #import <UIKit/UITableViewController.h>
  42. #import <UIKit/UICollectionViewController.h>
  43. #import <UIKit/UINavigationController.h>
  44. #import <UIKit/UITouch.h>
  45. #import <UIKit/UIWindow.h>
  46. #import <UIKit/NSLayoutConstraint.h>
  47. NSInteger const kIQDoneButtonToolbarTag = -1002;
  48. NSInteger const kIQPreviousNextButtonToolbarTag = -1005;
  49. #define kIQCGPointInvalid CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX)
  50. @interface IQKeyboardManager()<UIGestureRecognizerDelegate>
  51. /*******************************************/
  52. /** used to adjust contentInset of UITextView. */
  53. @property(nonatomic, assign) UIEdgeInsets startingTextViewContentInsets;
  54. /** used to adjust scrollIndicatorInsets of UITextView. */
  55. @property(nonatomic, assign) UIEdgeInsets startingTextViewScrollIndicatorInsets;
  56. /** used with textView to detect a textFieldView contentInset is changed or not. (Bug ID: #92)*/
  57. @property(nonatomic, assign) BOOL isTextViewContentInsetChanged;
  58. /*******************************************/
  59. /** To save UITextField/UITextView object voa textField/textView notifications. */
  60. @property(nonatomic, weak) UIView *textFieldView;
  61. /** To save rootViewController.view.frame.origin. */
  62. @property(nonatomic, assign) CGPoint topViewBeginOrigin;
  63. /** To save rootViewController */
  64. @property(nonatomic, weak) UIViewController *rootViewController;
  65. /** To overcome with popGestureRecognizer issue Bug ID: #1361 */
  66. @property(nonatomic, weak) UIViewController *rootViewControllerWhilePopGestureRecognizerActive;
  67. @property(nonatomic, assign) CGPoint topViewBeginOriginWhilePopGestureRecognizerActive;
  68. /** To know if we have any pending request to adjust view position. */
  69. @property(nonatomic, assign) BOOL hasPendingAdjustRequest;
  70. /*******************************************/
  71. /** Variable to save lastScrollView that was scrolled. */
  72. @property(nonatomic, weak) UIScrollView *lastScrollView;
  73. /** LastScrollView's initial contentInsets. */
  74. @property(nonatomic, assign) UIEdgeInsets startingContentInsets;
  75. /** LastScrollView's initial scrollIndicatorInsets. */
  76. @property(nonatomic, assign) UIEdgeInsets startingScrollIndicatorInsets;
  77. /** LastScrollView's initial contentOffset. */
  78. @property(nonatomic, assign) CGPoint startingContentOffset;
  79. /*******************************************/
  80. /** To save keyboard animation duration. */
  81. @property(nonatomic, assign) CGFloat animationDuration;
  82. /** To mimic the keyboard animation */
  83. @property(nonatomic, assign) NSInteger animationCurve;
  84. /*******************************************/
  85. /** 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 */
  86. @property(nonnull, nonatomic, strong, readwrite) UITapGestureRecognizer *resignFirstResponderGesture;
  87. /**
  88. moved distance to the top used to maintain distance between keyboard and textField. Most of the time this will be a positive value.
  89. */
  90. @property(nonatomic, assign, readwrite) CGFloat movedDistance;
  91. /*******************************************/
  92. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *registeredClasses;
  93. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *disabledDistanceHandlingClasses;
  94. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *enabledDistanceHandlingClasses;
  95. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *disabledToolbarClasses;
  96. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *enabledToolbarClasses;
  97. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *toolbarPreviousNextAllowedClasses;
  98. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *disabledTouchResignedClasses;
  99. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *enabledTouchResignedClasses;
  100. @property(nonatomic, strong, nonnull, readwrite) NSMutableSet<Class> *touchResignedGestureIgnoreClasses;
  101. /*******************************************/
  102. @end
  103. @implementation IQKeyboardManager
  104. {
  105. @package
  106. /*******************************************/
  107. /** To save keyboardWillShowNotification. Needed for enable keyboard functionality. */
  108. NSNotification *_kbShowNotification;
  109. /** To save keyboard size. */
  110. CGSize _kbSize;
  111. /*******************************************/
  112. }
  113. //UIKeyboard handling
  114. @synthesize enable = _enable;
  115. @synthesize keyboardDistanceFromTextField = _keyboardDistanceFromTextField;
  116. //Keyboard Appearance handling
  117. @synthesize overrideKeyboardAppearance = _overrideKeyboardAppearance;
  118. @synthesize keyboardAppearance = _keyboardAppearance;
  119. //IQToolbar handling
  120. @synthesize enableAutoToolbar = _enableAutoToolbar;
  121. @synthesize toolbarManageBehaviour = _toolbarManageBehaviour;
  122. @synthesize shouldToolbarUsesTextFieldTintColor = _shouldToolbarUsesTextFieldTintColor;
  123. @synthesize toolbarTintColor = _toolbarTintColor;
  124. @synthesize toolbarBarTintColor = _toolbarBarTintColor;
  125. @dynamic shouldShowTextFieldPlaceholder;
  126. @synthesize shouldShowToolbarPlaceholder = _shouldShowToolbarPlaceholder;
  127. @synthesize placeholderFont = _placeholderFont;
  128. @synthesize placeholderColor = _placeholderColor;
  129. @synthesize placeholderButtonColor = _placeholderButtonColor;
  130. //Resign handling
  131. @synthesize shouldResignOnTouchOutside = _shouldResignOnTouchOutside;
  132. @synthesize resignFirstResponderGesture = _resignFirstResponderGesture;
  133. //Sound handling
  134. @synthesize shouldPlayInputClicks = _shouldPlayInputClicks;
  135. //Animation handling
  136. @synthesize layoutIfNeededOnUpdate = _layoutIfNeededOnUpdate;
  137. #pragma mark - Initializing functions
  138. /** Override +load method to enable KeyboardManager when class loader load IQKeyboardManager. Enabling when app starts (No need to write any code) */
  139. +(void)load
  140. {
  141. //Enabling IQKeyboardManager. Loading asynchronous on main thread
  142. [self performSelectorOnMainThread:@selector(sharedManager) withObject:nil waitUntilDone:NO];
  143. }
  144. /* Singleton Object Initialization. */
  145. -(instancetype)init
  146. {
  147. if (self = [super init])
  148. {
  149. __weak typeof(self) weakSelf = self;
  150. static dispatch_once_t onceToken;
  151. dispatch_once(&onceToken, ^{
  152. __strong typeof(self) strongSelf = weakSelf;
  153. strongSelf.registeredClasses = [[NSMutableSet alloc] init];
  154. [strongSelf registerAllNotifications];
  155. //Creating gesture for @shouldResignOnTouchOutside. (Enhancement ID: #14)
  156. strongSelf.resignFirstResponderGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized:)];
  157. strongSelf.resignFirstResponderGesture.cancelsTouchesInView = NO;
  158. [strongSelf.resignFirstResponderGesture setDelegate:self];
  159. strongSelf.resignFirstResponderGesture.enabled = strongSelf.shouldResignOnTouchOutside;
  160. strongSelf.topViewBeginOrigin = kIQCGPointInvalid;
  161. strongSelf.topViewBeginOriginWhilePopGestureRecognizerActive = kIQCGPointInvalid;
  162. //Setting it's initial values
  163. strongSelf.animationDuration = 0.25;
  164. strongSelf.animationCurve = UIViewAnimationCurveEaseInOut;
  165. [self setEnable:YES];
  166. [self setKeyboardDistanceFromTextField:10.0];
  167. [self setShouldPlayInputClicks:YES];
  168. [self setShouldResignOnTouchOutside:NO];
  169. [self setOverrideKeyboardAppearance:NO];
  170. [self setKeyboardAppearance:UIKeyboardAppearanceDefault];
  171. [self setEnableAutoToolbar:YES];
  172. [self setShouldShowToolbarPlaceholder:YES];
  173. [self setToolbarManageBehaviour:IQAutoToolbarBySubviews];
  174. [self setLayoutIfNeededOnUpdate:NO];
  175. //Loading IQToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay (Bug ID: #550)
  176. {
  177. //If you experience exception breakpoint issue at below line then try these solutions https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator
  178. UITextField *view = [[UITextField alloc] init];
  179. [view addDoneOnKeyboardWithTarget:nil action:nil];
  180. [view addPreviousNextDoneOnKeyboardWithTarget:nil previousAction:nil nextAction:nil doneAction:nil];
  181. }
  182. //Initializing disabled classes Set.
  183. strongSelf.disabledDistanceHandlingClasses = [[NSMutableSet alloc] initWithObjects:[UITableViewController class],[UIAlertController class], nil];
  184. strongSelf.enabledDistanceHandlingClasses = [[NSMutableSet alloc] init];
  185. strongSelf.disabledToolbarClasses = [[NSMutableSet alloc] initWithObjects:[UIAlertController class], nil];
  186. strongSelf.enabledToolbarClasses = [[NSMutableSet alloc] init];
  187. strongSelf.toolbarPreviousNextAllowedClasses = [[NSMutableSet alloc] initWithObjects:[UITableView class],[UICollectionView class],[IQPreviousNextView class], nil];
  188. strongSelf.disabledTouchResignedClasses = [[NSMutableSet alloc] initWithObjects:[UIAlertController class], nil];
  189. strongSelf.enabledTouchResignedClasses = [[NSMutableSet alloc] init];
  190. strongSelf.touchResignedGestureIgnoreClasses = [[NSMutableSet alloc] initWithObjects:[UIControl class],[UINavigationBar class], nil];
  191. [self setShouldToolbarUsesTextFieldTintColor:NO];
  192. });
  193. }
  194. return self;
  195. }
  196. /* Automatically called from the `+(void)load` method. */
  197. + (IQKeyboardManager*)sharedManager
  198. {
  199. //Singleton instance
  200. static IQKeyboardManager *kbManager;
  201. static dispatch_once_t onceToken;
  202. dispatch_once(&onceToken, ^{
  203. kbManager = [[self alloc] init];
  204. });
  205. return kbManager;
  206. }
  207. #pragma mark - Dealloc
  208. -(void)dealloc
  209. {
  210. // Disable the keyboard manager.
  211. [self setEnable:NO];
  212. //Removing notification observers on dealloc.
  213. [[NSNotificationCenter defaultCenter] removeObserver:self];
  214. }
  215. #pragma mark - Property functions
  216. -(void)setEnable:(BOOL)enable
  217. {
  218. // If not enabled, enable it.
  219. if (enable == YES &&
  220. _enable == NO)
  221. {
  222. //Setting NO to _enable.
  223. _enable = enable;
  224. //If keyboard is currently showing. Sending a fake notification for keyboardWillShow to adjust view according to keyboard.
  225. if (_kbShowNotification) [self keyboardWillShow:_kbShowNotification];
  226. [self showLog:@"Enabled"];
  227. }
  228. //If not disable, desable it.
  229. else if (enable == NO &&
  230. _enable == YES)
  231. {
  232. //Sending a fake notification for keyboardWillHide to retain view's original position.
  233. [self keyboardWillHide:nil];
  234. //Setting NO to _enable.
  235. _enable = enable;
  236. [self showLog:@"Disabled"];
  237. }
  238. //If already disabled.
  239. else if (enable == NO &&
  240. _enable == NO)
  241. {
  242. [self showLog:@"Already Disabled"];
  243. }
  244. //If already enabled.
  245. else if (enable == YES &&
  246. _enable == YES)
  247. {
  248. [self showLog:@"Already Enabled"];
  249. }
  250. }
  251. -(BOOL)privateIsEnabled
  252. {
  253. BOOL enable = _enable;
  254. // IQEnableMode enableMode = _textFieldView.enableMode;
  255. //
  256. // if (enableMode == IQEnableModeEnabled)
  257. // {
  258. // enable = YES;
  259. // }
  260. // else if (enableMode == IQEnableModeDisabled)
  261. // {
  262. // enable = NO;
  263. // }
  264. // else
  265. {
  266. UIViewController *textFieldViewController = [_textFieldView viewContainingController];
  267. if (textFieldViewController)
  268. {
  269. if (enable == NO)
  270. {
  271. //If viewController is kind of enable viewController class, then assuming it's enabled.
  272. for (Class enabledClass in _enabledDistanceHandlingClasses)
  273. {
  274. if ([textFieldViewController isKindOfClass:enabledClass])
  275. {
  276. enable = YES;
  277. break;
  278. }
  279. }
  280. }
  281. if (enable)
  282. {
  283. //If viewController is kind of disable viewController class, then assuming it's disable.
  284. for (Class disabledClass in _disabledDistanceHandlingClasses)
  285. {
  286. if ([textFieldViewController isKindOfClass:disabledClass])
  287. {
  288. enable = NO;
  289. break;
  290. }
  291. }
  292. //Special Controllers
  293. if (enable == YES)
  294. {
  295. NSString *classNameString = NSStringFromClass([textFieldViewController class]);
  296. //_UIAlertControllerTextFieldViewController
  297. if ([classNameString containsString:@"UIAlertController"] && [classNameString hasSuffix:@"TextFieldViewController"])
  298. {
  299. enable = NO;
  300. }
  301. }
  302. }
  303. }
  304. }
  305. return enable;
  306. }
  307. -(BOOL)shouldShowTextFieldPlaceholder
  308. {
  309. return _shouldShowToolbarPlaceholder;
  310. }
  311. -(void)setShouldShowTextFieldPlaceholder:(BOOL)shouldShowTextFieldPlaceholder
  312. {
  313. _shouldShowToolbarPlaceholder = shouldShowTextFieldPlaceholder;
  314. }
  315. // Setting keyboard distance from text field.
  316. -(void)setKeyboardDistanceFromTextField:(CGFloat)keyboardDistanceFromTextField
  317. {
  318. //Can't be less than zero. Minimum is zero.
  319. _keyboardDistanceFromTextField = MAX(keyboardDistanceFromTextField, 0);
  320. [self showLog:[NSString stringWithFormat:@"keyboardDistanceFromTextField: %.2f",_keyboardDistanceFromTextField]];
  321. }
  322. /** Enabling/disable gesture on touching. */
  323. -(void)setShouldResignOnTouchOutside:(BOOL)shouldResignOnTouchOutside
  324. {
  325. [self showLog:[NSString stringWithFormat:@"shouldResignOnTouchOutside: %@",shouldResignOnTouchOutside?@"Yes":@"No"]];
  326. _shouldResignOnTouchOutside = shouldResignOnTouchOutside;
  327. //Enable/Disable gesture recognizer (Enhancement ID: #14)
  328. [_resignFirstResponderGesture setEnabled:[self privateShouldResignOnTouchOutside]];
  329. }
  330. -(BOOL)privateShouldResignOnTouchOutside
  331. {
  332. BOOL shouldResignOnTouchOutside = _shouldResignOnTouchOutside;
  333. UIView *textFieldView = _textFieldView;
  334. IQEnableMode enableMode = textFieldView.shouldResignOnTouchOutsideMode;
  335. if (enableMode == IQEnableModeEnabled)
  336. {
  337. shouldResignOnTouchOutside = YES;
  338. }
  339. else if (enableMode == IQEnableModeDisabled)
  340. {
  341. shouldResignOnTouchOutside = NO;
  342. }
  343. else
  344. {
  345. UIViewController *textFieldViewController = [textFieldView viewContainingController];
  346. if (textFieldViewController)
  347. {
  348. if (shouldResignOnTouchOutside == NO)
  349. {
  350. //If viewController is kind of enable viewController class, then assuming shouldResignOnTouchOutside is enabled.
  351. for (Class enabledClass in _enabledTouchResignedClasses)
  352. {
  353. if ([textFieldViewController isKindOfClass:enabledClass])
  354. {
  355. shouldResignOnTouchOutside = YES;
  356. break;
  357. }
  358. }
  359. }
  360. if (shouldResignOnTouchOutside)
  361. {
  362. //If viewController is kind of disable viewController class, then assuming shouldResignOnTouchOutside is disable.
  363. for (Class disabledClass in _disabledTouchResignedClasses)
  364. {
  365. if ([textFieldViewController isKindOfClass:disabledClass])
  366. {
  367. shouldResignOnTouchOutside = NO;
  368. break;
  369. }
  370. }
  371. //Special Controllers
  372. if (shouldResignOnTouchOutside == YES)
  373. {
  374. NSString *classNameString = NSStringFromClass([textFieldViewController class]);
  375. //_UIAlertControllerTextFieldViewController
  376. if ([classNameString containsString:@"UIAlertController"] && [classNameString hasSuffix:@"TextFieldViewController"])
  377. {
  378. shouldResignOnTouchOutside = NO;
  379. }
  380. }
  381. }
  382. }
  383. }
  384. return shouldResignOnTouchOutside;
  385. }
  386. /** Enable/disable autotoolbar. Adding and removing toolbar if required. */
  387. -(void)setEnableAutoToolbar:(BOOL)enableAutoToolbar
  388. {
  389. _enableAutoToolbar = enableAutoToolbar;
  390. [self showLog:[NSString stringWithFormat:@"enableAutoToolbar: %@",enableAutoToolbar?@"Yes":@"No"]];
  391. //If enabled then adding toolbar.
  392. if ([self privateIsEnableAutoToolbar] == YES)
  393. {
  394. [self addToolbarIfRequired];
  395. }
  396. //Else removing toolbar.
  397. else
  398. {
  399. [self removeToolbarIfRequired];
  400. }
  401. }
  402. -(BOOL)privateIsEnableAutoToolbar
  403. {
  404. BOOL enableAutoToolbar = _enableAutoToolbar;
  405. UIViewController *textFieldViewController = [_textFieldView viewContainingController];
  406. if (textFieldViewController)
  407. {
  408. if (enableAutoToolbar == NO)
  409. {
  410. //If found any toolbar enabled classes then return.
  411. for (Class enabledToolbarClass in _enabledToolbarClasses)
  412. {
  413. if ([textFieldViewController isKindOfClass:enabledToolbarClass])
  414. {
  415. enableAutoToolbar = YES;
  416. break;
  417. }
  418. }
  419. }
  420. if (enableAutoToolbar)
  421. {
  422. //If found any toolbar disabled classes then return.
  423. for (Class disabledToolbarClass in _disabledToolbarClasses)
  424. {
  425. if ([textFieldViewController isKindOfClass:disabledToolbarClass])
  426. {
  427. enableAutoToolbar = NO;
  428. break;
  429. }
  430. }
  431. //Special Controllers
  432. if (enableAutoToolbar == YES)
  433. {
  434. NSString *classNameString = NSStringFromClass([textFieldViewController class]);
  435. //_UIAlertControllerTextFieldViewController
  436. if ([classNameString containsString:@"UIAlertController"] && [classNameString hasSuffix:@"TextFieldViewController"])
  437. {
  438. enableAutoToolbar = NO;
  439. }
  440. }
  441. }
  442. }
  443. return enableAutoToolbar;
  444. }
  445. #pragma mark - Private Methods
  446. /** Getting keyWindow. */
  447. -(UIWindow *)keyWindow
  448. {
  449. UIView *textFieldView = _textFieldView;
  450. if (textFieldView.window)
  451. {
  452. return textFieldView.window;
  453. }
  454. else
  455. {
  456. static __weak UIWindow *_keyWindow = nil;
  457. /* (Bug ID: #23, #25, #73) */
  458. UIWindow *originalKeyWindow = [[UIApplication sharedApplication] keyWindow];
  459. //If original key window is not nil and the cached keywindow is also not original keywindow then changing keywindow.
  460. if (originalKeyWindow &&
  461. _keyWindow != originalKeyWindow)
  462. {
  463. _keyWindow = originalKeyWindow;
  464. }
  465. return _keyWindow;
  466. }
  467. }
  468. -(void)optimizedAdjustPosition
  469. {
  470. if (_hasPendingAdjustRequest == NO)
  471. {
  472. _hasPendingAdjustRequest = YES;
  473. __weak typeof(self) weakSelf = self;
  474. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  475. [self adjustPosition];
  476. weakSelf.hasPendingAdjustRequest = NO;
  477. }];
  478. }
  479. }
  480. /* Adjusting RootViewController's frame according to interface orientation. */
  481. -(void)adjustPosition
  482. {
  483. UIView *textFieldView = _textFieldView;
  484. // Getting RootViewController. (Bug ID: #1, #4)
  485. UIViewController *rootController = _rootViewController;
  486. // Getting KeyWindow object.
  487. UIWindow *keyWindow = [self keyWindow];
  488. // We are unable to get textField object while keyboard showing on UIWebView's textField. (Bug ID: #11)
  489. if (_hasPendingAdjustRequest == NO ||
  490. textFieldView == nil ||
  491. rootController == nil ||
  492. keyWindow == nil)
  493. return;
  494. CFTimeInterval startTime = CACurrentMediaTime();
  495. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  496. // Converting Rectangle according to window bounds.
  497. CGRect textFieldViewRectInWindow = [[textFieldView superview] convertRect:textFieldView.frame toView:keyWindow];
  498. CGRect textFieldViewRectInRootSuperview = [[textFieldView superview] convertRect:textFieldView.frame toView:rootController.view.superview];
  499. // Getting RootView origin.
  500. CGPoint rootViewOrigin = rootController.view.frame.origin;
  501. //Maintain keyboardDistanceFromTextField
  502. CGFloat specialKeyboardDistanceFromTextField = textFieldView.keyboardDistanceFromTextField;
  503. {
  504. UISearchBar *searchBar = textFieldView.searchBar;
  505. if (searchBar)
  506. {
  507. specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField;
  508. }
  509. }
  510. CGFloat keyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance)?_keyboardDistanceFromTextField:specialKeyboardDistanceFromTextField;
  511. CGSize kbSize = _kbSize;
  512. kbSize.height += keyboardDistanceFromTextField;
  513. CGFloat navigationBarAreaHeight = [[UIApplication sharedApplication] statusBarFrame].size.height + rootController.navigationController.navigationBar.frame.size.height;
  514. CGFloat layoutAreaHeight = rootController.view.layoutMargins.top;
  515. CGFloat topLayoutGuide = MAX(navigationBarAreaHeight, layoutAreaHeight) + 5;
  516. CGFloat bottomLayoutGuide = [textFieldView isKindOfClass:[UITextView class]] ? 0 : rootController.view.layoutMargins.bottom; //Validation of textView for case where there is a tab bar at the bottom or running on iPhone X and textView is at the bottom.
  517. // +Move positive = textField is hidden.
  518. // -Move negative = textField is showing.
  519. // Calculating move position. Common for both normal and special cases.
  520. CGFloat move = MIN(CGRectGetMinY(textFieldViewRectInRootSuperview)-topLayoutGuide, CGRectGetMaxY(textFieldViewRectInWindow)-(CGRectGetHeight(keyWindow.frame)-kbSize.height)+bottomLayoutGuide);
  521. [self showLog:[NSString stringWithFormat:@"Need to move: %.2f",move]];
  522. UIScrollView *superScrollView = nil;
  523. UIScrollView *superView = (UIScrollView*)[textFieldView superviewOfClassType:[UIScrollView class]];
  524. //Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
  525. while (superView)
  526. {
  527. if (superView.isScrollEnabled && superView.shouldIgnoreScrollingAdjustment == NO)
  528. {
  529. superScrollView = superView;
  530. break;
  531. }
  532. else
  533. {
  534. // Getting it's superScrollView. // (Enhancement ID: #21, #24)
  535. superView = (UIScrollView*)[superView superviewOfClassType:[UIScrollView class]];
  536. }
  537. }
  538. //If there was a lastScrollView. // (Bug ID: #34)
  539. if (_lastScrollView)
  540. {
  541. //If we can't find current superScrollView, then setting lastScrollView to it's original form.
  542. if (superScrollView == nil)
  543. {
  544. [self showLog:[NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]];
  545. __weak typeof(self) weakSelf = self;
  546. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  547. __strong typeof(self) strongSelf = weakSelf;
  548. UIScrollView *strongLastScrollView = strongSelf.lastScrollView;
  549. [strongLastScrollView setContentInset:strongSelf.startingContentInsets];
  550. strongLastScrollView.scrollIndicatorInsets = strongSelf.startingScrollIndicatorInsets;
  551. } completion:NULL];
  552. if (_lastScrollView.shouldRestoreScrollViewContentOffset)
  553. {
  554. [_lastScrollView setContentOffset:_startingContentOffset animated:UIView.areAnimationsEnabled];
  555. }
  556. _startingContentInsets = UIEdgeInsetsZero;
  557. _startingScrollIndicatorInsets = UIEdgeInsetsZero;
  558. _startingContentOffset = CGPointZero;
  559. _lastScrollView = nil;
  560. }
  561. //If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
  562. else if (superScrollView != _lastScrollView)
  563. {
  564. [self showLog:[NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]];
  565. __weak typeof(self) weakSelf = self;
  566. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  567. __strong typeof(self) strongSelf = weakSelf;
  568. UIScrollView *strongLastScrollView = strongSelf.lastScrollView;
  569. [strongLastScrollView setContentInset:strongSelf.startingContentInsets];
  570. strongLastScrollView.scrollIndicatorInsets = strongSelf.startingScrollIndicatorInsets;
  571. } completion:NULL];
  572. if (_lastScrollView.shouldRestoreScrollViewContentOffset)
  573. {
  574. [_lastScrollView setContentOffset:_startingContentOffset animated:UIView.areAnimationsEnabled];
  575. }
  576. _lastScrollView = superScrollView;
  577. _startingContentInsets = superScrollView.contentInset;
  578. _startingScrollIndicatorInsets = superScrollView.scrollIndicatorInsets;
  579. _startingContentOffset = superScrollView.contentOffset;
  580. [self showLog:[NSString stringWithFormat:@"Saving New %@ contentInset: %@ and contentOffset : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]];
  581. }
  582. //Else the case where superScrollView == lastScrollView means we are on same scrollView after switching to different textField. So doing nothing
  583. }
  584. //If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
  585. else if(superScrollView)
  586. {
  587. _lastScrollView = superScrollView;
  588. _startingContentInsets = superScrollView.contentInset;
  589. _startingContentOffset = superScrollView.contentOffset;
  590. _startingScrollIndicatorInsets = superScrollView.scrollIndicatorInsets;
  591. [self showLog:[NSString stringWithFormat:@"Saving %@ contentInset: %@ and contentOffset : %@",[_lastScrollView _IQDescription],NSStringFromUIEdgeInsets(_startingContentInsets),NSStringFromCGPoint(_startingContentOffset)]];
  592. }
  593. // Special case for ScrollView.
  594. {
  595. // If we found lastScrollView then setting it's contentOffset to show textField.
  596. if (_lastScrollView)
  597. {
  598. //Saving
  599. UIView *lastView = textFieldView;
  600. superScrollView = _lastScrollView;
  601. //Looping in upper hierarchy until we don't found any scrollView in it's upper hirarchy till UIWindow object.
  602. while (superScrollView &&
  603. (move>0?(move > (-superScrollView.contentOffset.y-superScrollView.contentInset.top)):superScrollView.contentOffset.y>0) )
  604. {
  605. UIScrollView *nextScrollView = nil;
  606. UIScrollView *tempScrollView = (UIScrollView*)[superScrollView superviewOfClassType:[UIScrollView class]];
  607. //Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
  608. while (tempScrollView)
  609. {
  610. if (tempScrollView.isScrollEnabled && tempScrollView.shouldIgnoreScrollingAdjustment == NO)
  611. {
  612. nextScrollView = tempScrollView;
  613. break;
  614. }
  615. else
  616. {
  617. // Getting it's superScrollView. // (Enhancement ID: #21, #24)
  618. tempScrollView = (UIScrollView*)[tempScrollView superviewOfClassType:[UIScrollView class]];
  619. }
  620. }
  621. //Getting lastViewRect.
  622. CGRect lastViewRect = [[lastView superview] convertRect:lastView.frame toView:superScrollView];
  623. //Calculating the expected Y offset from move and scrollView's contentOffset.
  624. CGFloat shouldOffsetY = superScrollView.contentOffset.y - MIN(superScrollView.contentOffset.y,-move);
  625. //Rearranging the expected Y offset according to the view.
  626. shouldOffsetY = MIN(shouldOffsetY, lastViewRect.origin.y);
  627. //[textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
  628. //[superScrollView superviewOfClassType:[UIScrollView class]] == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierarchy.)
  629. //shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
  630. if ([textFieldView isKindOfClass:[UITextView class]] &&
  631. nextScrollView == nil &&
  632. (shouldOffsetY >= 0))
  633. {
  634. // Converting Rectangle according to window bounds.
  635. CGRect currentTextFieldViewRect = [[textFieldView superview] convertRect:textFieldView.frame toView:keyWindow];
  636. //Calculating expected fix distance which needs to be managed from navigation bar
  637. CGFloat expectedFixDistance = CGRectGetMinY(currentTextFieldViewRect) - topLayoutGuide;
  638. //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)
  639. shouldOffsetY = MIN(shouldOffsetY, superScrollView.contentOffset.y + expectedFixDistance);
  640. //Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic.
  641. move = 0;
  642. }
  643. else
  644. {
  645. //Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
  646. move -= (shouldOffsetY-superScrollView.contentOffset.y);
  647. }
  648. //Getting problem while using `setContentOffset:animated:`, So I used animation API.
  649. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  650. [self showLog:[NSString stringWithFormat:@"Adjusting %.2f to %@ ContentOffset",(superScrollView.contentOffset.y-shouldOffsetY),[superScrollView _IQDescription]]];
  651. [self showLog:[NSString stringWithFormat:@"Remaining Move: %.2f",move]];
  652. superScrollView.contentOffset = CGPointMake(superScrollView.contentOffset.x, shouldOffsetY);
  653. } completion:NULL];
  654. // Getting next lastView & superScrollView.
  655. lastView = superScrollView;
  656. superScrollView = nextScrollView;
  657. }
  658. //Updating contentInset
  659. {
  660. CGRect lastScrollViewRect = [[_lastScrollView superview] convertRect:_lastScrollView.frame toView:keyWindow];
  661. CGFloat bottom = (kbSize.height-keyboardDistanceFromTextField)-(CGRectGetHeight(keyWindow.frame)-CGRectGetMaxY(lastScrollViewRect));
  662. // Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view.
  663. UIEdgeInsets movedInsets = _lastScrollView.contentInset;
  664. movedInsets.bottom = MAX(_startingContentInsets.bottom, bottom);
  665. [self showLog:[NSString stringWithFormat:@"%@ old ContentInset : %@",[_lastScrollView _IQDescription], NSStringFromUIEdgeInsets(_lastScrollView.contentInset)]];
  666. __weak typeof(self) weakSelf = self;
  667. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  668. __strong typeof(self) strongSelf = weakSelf;
  669. UIScrollView *strongLastScrollView = strongSelf.lastScrollView;
  670. strongLastScrollView.contentInset = movedInsets;
  671. UIEdgeInsets newInset = strongLastScrollView.scrollIndicatorInsets;
  672. newInset.bottom = movedInsets.bottom;
  673. strongLastScrollView.scrollIndicatorInsets = newInset;
  674. } completion:NULL];
  675. [self showLog:[NSString stringWithFormat:@"%@ new ContentInset : %@",[_lastScrollView _IQDescription], NSStringFromUIEdgeInsets(_lastScrollView.contentInset)]];
  676. }
  677. }
  678. //Going ahead. No else if.
  679. }
  680. {
  681. //Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen)
  682. //_lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
  683. //[textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
  684. if ([textFieldView isKindOfClass:[UITextView class]])
  685. {
  686. UITextView *textView = (UITextView*)textFieldView;
  687. CGFloat keyboardYPosition = CGRectGetHeight(keyWindow.frame)-(kbSize.height-keyboardDistanceFromTextField);
  688. CGRect rootSuperViewFrameInWindow = [rootController.view.superview convertRect:rootController.view.superview.bounds toView:keyWindow];
  689. CGFloat keyboardOverlapping = CGRectGetMaxY(rootSuperViewFrameInWindow) - keyboardYPosition;
  690. CGFloat textViewHeight = MIN(CGRectGetHeight(textFieldView.frame), (CGRectGetHeight(rootSuperViewFrameInWindow)-topLayoutGuide-keyboardOverlapping));
  691. if (textFieldView.frame.size.height-textView.contentInset.bottom>textViewHeight)
  692. {
  693. __weak typeof(self) weakSelf = self;
  694. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  695. __strong typeof(self) strongSelf = weakSelf;
  696. UIView *strongTextFieldView = strongSelf.textFieldView;
  697. [self showLog:[NSString stringWithFormat:@"%@ Old UITextView.contentInset : %@",[strongTextFieldView _IQDescription], NSStringFromUIEdgeInsets(textView.contentInset)]];
  698. //_isTextViewContentInsetChanged, If frame is not change by library in past, then saving user textView properties (Bug ID: #92)
  699. if (strongSelf.isTextViewContentInsetChanged == NO)
  700. {
  701. strongSelf.startingTextViewContentInsets = textView.contentInset;
  702. strongSelf.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets;
  703. }
  704. UIEdgeInsets newContentInset = textView.contentInset;
  705. newContentInset.bottom = strongTextFieldView.frame.size.height-textViewHeight;
  706. textView.contentInset = newContentInset;
  707. textView.scrollIndicatorInsets = newContentInset;
  708. strongSelf.isTextViewContentInsetChanged = YES;
  709. [self showLog:[NSString stringWithFormat:@"%@ New UITextView.contentInset : %@",[strongTextFieldView _IQDescription], NSStringFromUIEdgeInsets(textView.contentInset)]];
  710. } completion:NULL];
  711. }
  712. }
  713. {
  714. __weak typeof(self) weakSelf = self;
  715. // +Positive or zero.
  716. if (move>=0)
  717. {
  718. rootViewOrigin.y -= move;
  719. // From now prevent keyboard manager to slide up the rootView to more than keyboard height. (Bug ID: #93)
  720. rootViewOrigin.y = MAX(rootViewOrigin.y, MIN(0, -(kbSize.height-keyboardDistanceFromTextField)));
  721. [self showLog:@"Moving Upward"];
  722. // Setting adjusted rootViewOrigin.ty
  723. //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
  724. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  725. __strong typeof(self) strongSelf = weakSelf;
  726. // Setting it's new frame
  727. CGRect rect = rootController.view.frame;
  728. rect.origin = rootViewOrigin;
  729. rootController.view.frame = rect;
  730. //Animating content if needed (Bug ID: #204)
  731. if (strongSelf.layoutIfNeededOnUpdate)
  732. {
  733. //Animating content (Bug ID: #160)
  734. [rootController.view setNeedsLayout];
  735. [rootController.view layoutIfNeeded];
  736. }
  737. [self showLog:[NSString stringWithFormat:@"Set %@ origin to : %@",[rootController _IQDescription],NSStringFromCGPoint(rootViewOrigin)]];
  738. } completion:NULL];
  739. _movedDistance = (_topViewBeginOrigin.y-rootViewOrigin.y);
  740. }
  741. // -Negative
  742. else
  743. {
  744. CGFloat disturbDistance = rootController.view.frame.origin.y-_topViewBeginOrigin.y;
  745. // disturbDistance Negative = frame disturbed. Pull Request #3
  746. // disturbDistance positive = frame not disturbed.
  747. if(disturbDistance<=0)
  748. {
  749. rootViewOrigin.y -= MAX(move, disturbDistance);
  750. [self showLog:@"Moving Downward"];
  751. // Setting adjusted rootViewRect
  752. //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
  753. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  754. __strong typeof(self) strongSelf = weakSelf;
  755. // Setting it's new frame
  756. CGRect rect = rootController.view.frame;
  757. rect.origin = rootViewOrigin;
  758. rootController.view.frame = rect;
  759. //Animating content if needed (Bug ID: #204)
  760. if (strongSelf.layoutIfNeededOnUpdate)
  761. {
  762. //Animating content (Bug ID: #160)
  763. [rootController.view setNeedsLayout];
  764. [rootController.view layoutIfNeeded];
  765. }
  766. [self showLog:[NSString stringWithFormat:@"Set %@ origin to : %@",[rootController _IQDescription],NSStringFromCGPoint(rootViewOrigin)]];
  767. } completion:NULL];
  768. _movedDistance = (_topViewBeginOrigin.y-rootController.view.frame.origin.y);
  769. }
  770. }
  771. }
  772. }
  773. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  774. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  775. }
  776. -(void)restorePosition
  777. {
  778. _hasPendingAdjustRequest = NO;
  779. // Setting rootViewController frame to it's original position. // (Bug ID: #18)
  780. if (_rootViewController && CGPointEqualToPoint(_topViewBeginOrigin, kIQCGPointInvalid) == false)
  781. {
  782. __weak typeof(self) weakSelf = self;
  783. //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
  784. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  785. __strong typeof(self) strongSelf = weakSelf;
  786. UIViewController *strongRootController = strongSelf.rootViewController;
  787. {
  788. [strongSelf showLog:[NSString stringWithFormat:@"Restoring %@ origin to : %@",[strongRootController _IQDescription],NSStringFromCGPoint(strongSelf.topViewBeginOrigin)]];
  789. //Restoring
  790. CGRect rect = strongRootController.view.frame;
  791. rect.origin = strongSelf.topViewBeginOrigin;
  792. strongRootController.view.frame = rect;
  793. strongSelf.movedDistance = 0;
  794. if (strongRootController.navigationController.interactivePopGestureRecognizer.state == UIGestureRecognizerStateBegan) {
  795. strongSelf.rootViewControllerWhilePopGestureRecognizerActive = strongRootController;
  796. strongSelf.topViewBeginOriginWhilePopGestureRecognizerActive = strongSelf.topViewBeginOrigin;
  797. }
  798. //Animating content if needed (Bug ID: #204)
  799. if (strongSelf.layoutIfNeededOnUpdate)
  800. {
  801. //Animating content (Bug ID: #160)
  802. [strongRootController.view setNeedsLayout];
  803. [strongRootController.view layoutIfNeeded];
  804. }
  805. }
  806. } completion:NULL];
  807. _rootViewController = nil;
  808. }
  809. }
  810. #pragma mark - Public Methods
  811. /* Refreshes textField/textView position if any external changes is explicitly made by user. */
  812. - (void)reloadLayoutIfNeeded
  813. {
  814. if ([self privateIsEnabled] == YES)
  815. {
  816. UIView *textFieldView = _textFieldView;
  817. if (textFieldView &&
  818. _keyboardShowing == YES &&
  819. CGPointEqualToPoint(_topViewBeginOrigin, kIQCGPointInvalid) == false &&
  820. [textFieldView isAlertViewTextField] == NO)
  821. {
  822. [self optimizedAdjustPosition];
  823. }
  824. }
  825. }
  826. #pragma mark - UIKeyboad Notification methods
  827. /* UIKeyboardWillShowNotification. */
  828. -(void)keyboardWillShow:(NSNotification*)aNotification
  829. {
  830. _kbShowNotification = aNotification;
  831. // Boolean to know keyboard is showing/hiding
  832. _keyboardShowing = YES;
  833. // Getting keyboard animation.
  834. NSInteger curve = [[aNotification userInfo][UIKeyboardAnimationCurveUserInfoKey] integerValue];
  835. _animationCurve = curve<<16;
  836. // Getting keyboard animation duration
  837. CGFloat duration = [[aNotification userInfo][UIKeyboardAnimationDurationUserInfoKey] floatValue];
  838. //Saving animation duration
  839. if (duration != 0.0) _animationDuration = duration;
  840. CGSize oldKBSize = _kbSize;
  841. // Getting UIKeyboardSize.
  842. CGRect kbFrame = [[aNotification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
  843. CGRect screenSize = [[UIScreen mainScreen] bounds];
  844. //Calculating actual keyboard displayed size, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381)
  845. CGRect intersectRect = CGRectIntersection(kbFrame, screenSize);
  846. if (CGRectIsNull(intersectRect))
  847. {
  848. _kbSize = CGSizeMake(screenSize.size.width, 0);
  849. }
  850. else
  851. {
  852. _kbSize = intersectRect.size;
  853. }
  854. if ([self privateIsEnabled] == NO) return;
  855. CFTimeInterval startTime = CACurrentMediaTime();
  856. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  857. UIView *textFieldView = _textFieldView;
  858. if (textFieldView && CGPointEqualToPoint(_topViewBeginOrigin, kIQCGPointInvalid)) // (Bug ID: #5)
  859. {
  860. // keyboard is not showing(At the beginning only). We should save rootViewRect.
  861. UIViewController *rootController = [textFieldView parentContainerViewController];
  862. _rootViewController = rootController;
  863. if (_rootViewControllerWhilePopGestureRecognizerActive == _rootViewController)
  864. {
  865. _topViewBeginOrigin = _topViewBeginOriginWhilePopGestureRecognizerActive;
  866. }
  867. else
  868. {
  869. _topViewBeginOrigin = rootController.view.frame.origin;
  870. }
  871. _rootViewControllerWhilePopGestureRecognizerActive = nil;
  872. _topViewBeginOriginWhilePopGestureRecognizerActive = kIQCGPointInvalid;
  873. [self showLog:[NSString stringWithFormat:@"Saving %@ beginning origin: %@",[rootController _IQDescription] ,NSStringFromCGPoint(_topViewBeginOrigin)]];
  874. }
  875. //If last restored keyboard size is different(any orientation accure), then refresh. otherwise not.
  876. if (!CGSizeEqualToSize(_kbSize, oldKBSize))
  877. {
  878. //If _textFieldView is inside UIAlertView then do nothing. (Bug ID: #37, #74, #76)
  879. //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).
  880. if (_keyboardShowing == YES &&
  881. textFieldView &&
  882. [textFieldView isAlertViewTextField] == NO)
  883. {
  884. [self optimizedAdjustPosition];
  885. }
  886. }
  887. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  888. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  889. }
  890. /* UIKeyboardDidShowNotification. */
  891. - (void)keyboardDidShow:(NSNotification*)aNotification
  892. {
  893. if ([self privateIsEnabled] == NO) return;
  894. CFTimeInterval startTime = CACurrentMediaTime();
  895. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  896. UIView *textFieldView = _textFieldView;
  897. // Getting topMost ViewController.
  898. UIViewController *controller = [textFieldView topMostController];
  899. //If _textFieldView viewController is presented as formSheet, then adjustPosition again because iOS internally update formSheet frame on keyboardShown. (Bug ID: #37, #74, #76)
  900. if (_keyboardShowing == YES &&
  901. textFieldView &&
  902. (controller.modalPresentationStyle == UIModalPresentationFormSheet || controller.modalPresentationStyle == UIModalPresentationPageSheet) &&
  903. [textFieldView isAlertViewTextField] == NO)
  904. {
  905. [self optimizedAdjustPosition];
  906. }
  907. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  908. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  909. }
  910. /* UIKeyboardWillHideNotification. So setting rootViewController to it's default frame. */
  911. - (void)keyboardWillHide:(NSNotification*)aNotification
  912. {
  913. //If it's not a fake notification generated by [self setEnable:NO].
  914. if (aNotification) _kbShowNotification = nil;
  915. // Boolean to know keyboard is showing/hiding
  916. _keyboardShowing = NO;
  917. // Getting keyboard animation duration
  918. CGFloat aDuration = [[aNotification userInfo][UIKeyboardAnimationDurationUserInfoKey] floatValue];
  919. if (aDuration!= 0.0f)
  920. {
  921. _animationDuration = aDuration;
  922. }
  923. //If not enabled then do nothing.
  924. if ([self privateIsEnabled] == NO) return;
  925. CFTimeInterval startTime = CACurrentMediaTime();
  926. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  927. //Commented due to #56. Added all the conditions below to handle UIWebView's textFields. (Bug ID: #56)
  928. // We are unable to get textField object while keyboard showing on UIWebView's textField. (Bug ID: #11)
  929. // if (_textFieldView == nil) return;
  930. //Restoring the contentOffset of the lastScrollView
  931. if (_lastScrollView)
  932. {
  933. __weak typeof(self) weakSelf = self;
  934. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  935. __strong typeof(self) strongSelf = weakSelf;
  936. strongSelf.lastScrollView.contentInset = strongSelf.startingContentInsets;
  937. strongSelf.lastScrollView.scrollIndicatorInsets = strongSelf.startingScrollIndicatorInsets;
  938. if (strongSelf.lastScrollView.shouldRestoreScrollViewContentOffset)
  939. {
  940. strongSelf.lastScrollView.contentOffset = strongSelf.startingContentOffset;
  941. }
  942. [self showLog:[NSString stringWithFormat:@"Restoring %@ contentInset to : %@ and contentOffset to : %@",[strongSelf.lastScrollView _IQDescription],NSStringFromUIEdgeInsets(strongSelf.startingContentInsets),NSStringFromCGPoint(strongSelf.startingContentOffset)]];
  943. // TODO: restore scrollView state
  944. // This is temporary solution. Have to implement the save and restore scrollView state
  945. UIScrollView *superscrollView = strongSelf.lastScrollView;
  946. do
  947. {
  948. CGSize contentSize = CGSizeMake(MAX(superscrollView.contentSize.width, CGRectGetWidth(superscrollView.frame)), MAX(superscrollView.contentSize.height, CGRectGetHeight(superscrollView.frame)));
  949. CGFloat minimumY = contentSize.height-CGRectGetHeight(superscrollView.frame);
  950. if (minimumY<superscrollView.contentOffset.y)
  951. {
  952. superscrollView.contentOffset = CGPointMake(superscrollView.contentOffset.x, minimumY);
  953. [self showLog:[NSString stringWithFormat:@"Restoring %@ contentOffset to : %@",[superscrollView _IQDescription],NSStringFromCGPoint(superscrollView.contentOffset)]];
  954. }
  955. } while ((superscrollView = (UIScrollView*)[superscrollView superviewOfClassType:[UIScrollView class]]));
  956. } completion:NULL];
  957. }
  958. [self restorePosition];
  959. //Reset all values
  960. _lastScrollView = nil;
  961. _kbSize = CGSizeZero;
  962. _startingContentInsets = UIEdgeInsetsZero;
  963. _startingScrollIndicatorInsets = UIEdgeInsetsZero;
  964. _startingContentOffset = CGPointZero;
  965. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  966. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  967. }
  968. /* UIKeyboardDidHideNotification. So topViewBeginRect can be set to CGRectZero. */
  969. - (void)keyboardDidHide:(NSNotification*)aNotification
  970. {
  971. CFTimeInterval startTime = CACurrentMediaTime();
  972. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  973. _topViewBeginOrigin = kIQCGPointInvalid;
  974. _kbSize = CGSizeZero;
  975. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  976. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  977. }
  978. #pragma mark - UITextFieldView Delegate methods
  979. /** UITextFieldTextDidBeginEditingNotification, UITextViewTextDidBeginEditingNotification. Fetching UITextFieldView object. */
  980. -(void)textFieldViewDidBeginEditing:(NSNotification*)notification
  981. {
  982. CFTimeInterval startTime = CACurrentMediaTime();
  983. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  984. // Getting object
  985. _textFieldView = notification.object;
  986. UIView *textFieldView = _textFieldView;
  987. if (_overrideKeyboardAppearance == YES)
  988. {
  989. UITextField *textField = (UITextField*)textFieldView;
  990. if ([textField respondsToSelector:@selector(keyboardAppearance)])
  991. {
  992. //If keyboard appearance is not like the provided appearance
  993. if (textField.keyboardAppearance != _keyboardAppearance)
  994. {
  995. //Setting textField keyboard appearance and reloading inputViews.
  996. textField.keyboardAppearance = _keyboardAppearance;
  997. [textField reloadInputViews];
  998. }
  999. }
  1000. }
  1001. //If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required.
  1002. if ([self privateIsEnableAutoToolbar])
  1003. {
  1004. //UITextView special case. Keyboard Notification is firing before textView notification so we need to reload it's inputViews.
  1005. if ([textFieldView isKindOfClass:[UITextView class]] &&
  1006. textFieldView.inputAccessoryView == nil)
  1007. {
  1008. __weak typeof(self) weakSelf = self;
  1009. [UIView animateWithDuration:0.00001 delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  1010. [self addToolbarIfRequired];
  1011. } completion:^(BOOL finished) {
  1012. __strong typeof(self) strongSelf = weakSelf;
  1013. //On textView toolbar didn't appear on first time, so forcing textView to reload it's inputViews.
  1014. [strongSelf.textFieldView reloadInputViews];
  1015. }];
  1016. }
  1017. //Else adding toolbar
  1018. else
  1019. {
  1020. [self addToolbarIfRequired];
  1021. }
  1022. }
  1023. else
  1024. {
  1025. [self removeToolbarIfRequired];
  1026. }
  1027. //Adding Geture recognizer to window (Enhancement ID: #14)
  1028. [_resignFirstResponderGesture setEnabled:[self privateShouldResignOnTouchOutside]];
  1029. [textFieldView.window addGestureRecognizer:_resignFirstResponderGesture];
  1030. if ([self privateIsEnabled] == YES)
  1031. {
  1032. if (CGPointEqualToPoint(_topViewBeginOrigin, kIQCGPointInvalid)) // (Bug ID: #5)
  1033. {
  1034. // keyboard is not showing(At the beginning only).
  1035. UIViewController *rootController = [textFieldView parentContainerViewController];
  1036. _rootViewController = rootController;
  1037. if (_rootViewControllerWhilePopGestureRecognizerActive == _rootViewController)
  1038. {
  1039. _topViewBeginOrigin = _topViewBeginOriginWhilePopGestureRecognizerActive;
  1040. }
  1041. else
  1042. {
  1043. _topViewBeginOrigin = rootController.view.frame.origin;
  1044. }
  1045. _rootViewControllerWhilePopGestureRecognizerActive = nil;
  1046. _topViewBeginOriginWhilePopGestureRecognizerActive = kIQCGPointInvalid;
  1047. [self showLog:[NSString stringWithFormat:@"Saving %@ beginning origin: %@",[rootController _IQDescription], NSStringFromCGPoint(_topViewBeginOrigin)]];
  1048. }
  1049. //If textFieldView is inside UIAlertView then do nothing. (Bug ID: #37, #74, #76)
  1050. //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).
  1051. if (_keyboardShowing == YES &&
  1052. textFieldView &&
  1053. [textFieldView isAlertViewTextField] == NO)
  1054. {
  1055. // keyboard is already showing. adjust frame.
  1056. [self optimizedAdjustPosition];
  1057. }
  1058. }
  1059. // if ([textFieldView isKindOfClass:[UITextField class]])
  1060. // {
  1061. // [(UITextField*)textFieldView addTarget:self action:@selector(editingDidEndOnExit:) forControlEvents:UIControlEventEditingDidEndOnExit];
  1062. // }
  1063. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  1064. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  1065. }
  1066. /** UITextFieldTextDidEndEditingNotification, UITextViewTextDidEndEditingNotification. Removing fetched object. */
  1067. -(void)textFieldViewDidEndEditing:(NSNotification*)notification
  1068. {
  1069. CFTimeInterval startTime = CACurrentMediaTime();
  1070. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  1071. UIView *textFieldView = _textFieldView;
  1072. //Removing gesture recognizer (Enhancement ID: #14)
  1073. [textFieldView.window removeGestureRecognizer:_resignFirstResponderGesture];
  1074. // if ([textFieldView isKindOfClass:[UITextField class]])
  1075. // {
  1076. // [(UITextField*)textFieldView removeTarget:self action:@selector(editingDidEndOnExit:) forControlEvents:UIControlEventEditingDidEndOnExit];
  1077. // }
  1078. // We check if there's a change in original frame or not.
  1079. if(_isTextViewContentInsetChanged == YES &&
  1080. [textFieldView isKindOfClass:[UITextView class]])
  1081. {
  1082. UITextView *textView = (UITextView*)textFieldView;
  1083. __weak typeof(self) weakSelf = self;
  1084. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  1085. __strong typeof(self) strongSelf = weakSelf;
  1086. strongSelf.isTextViewContentInsetChanged = NO;
  1087. [self showLog:[NSString stringWithFormat:@"Restoring %@ textView.contentInset to : %@",[strongSelf.textFieldView _IQDescription],NSStringFromUIEdgeInsets(strongSelf.startingTextViewContentInsets)]];
  1088. //Setting textField to it's initial contentInset
  1089. textView.contentInset = strongSelf.startingTextViewContentInsets;
  1090. textView.scrollIndicatorInsets = strongSelf.startingTextViewScrollIndicatorInsets;
  1091. } completion:NULL];
  1092. }
  1093. //Setting object to nil
  1094. _textFieldView = nil;
  1095. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  1096. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  1097. }
  1098. //-(void)editingDidEndOnExit:(UITextField*)textField
  1099. //{
  1100. // [self showLog:[NSString stringWithFormat:@"ReturnKey %@",NSStringFromSelector(_cmd)]];
  1101. //}
  1102. #pragma mark - UIStatusBar Notification methods
  1103. /** UIApplicationWillChangeStatusBarOrientationNotification. Need to set the textView to it's original position. If any frame changes made. (Bug ID: #92)*/
  1104. - (void)willChangeStatusBarOrientation:(NSNotification*)aNotification
  1105. {
  1106. CFTimeInterval startTime = CACurrentMediaTime();
  1107. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  1108. //If textViewContentInsetChanged is changed then restore it.
  1109. if (_isTextViewContentInsetChanged == YES &&
  1110. [_textFieldView isKindOfClass:[UITextView class]])
  1111. {
  1112. UITextView *textView = (UITextView*)_textFieldView;
  1113. __weak typeof(self) weakSelf = self;
  1114. //Due to orientation callback we need to set it's original position.
  1115. [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
  1116. __strong typeof(self) strongSelf = weakSelf;
  1117. strongSelf.isTextViewContentInsetChanged = NO;
  1118. [self showLog:[NSString stringWithFormat:@"Restoring %@ textView.contentInset to : %@",[strongSelf.textFieldView _IQDescription],NSStringFromUIEdgeInsets(strongSelf.startingTextViewContentInsets)]];
  1119. //Setting textField to it's initial contentInset
  1120. textView.contentInset = strongSelf.startingTextViewContentInsets;
  1121. textView.scrollIndicatorInsets = strongSelf.startingTextViewScrollIndicatorInsets;
  1122. } completion:NULL];
  1123. }
  1124. [self restorePosition];
  1125. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  1126. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  1127. }
  1128. #pragma mark AutoResign methods
  1129. /** Resigning on tap gesture. */
  1130. - (void)tapRecognized:(UITapGestureRecognizer*)gesture // (Enhancement ID: #14)
  1131. {
  1132. if (gesture.state == UIGestureRecognizerStateEnded)
  1133. {
  1134. //Resigning currently responder textField.
  1135. [self resignFirstResponder];
  1136. }
  1137. }
  1138. /** 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. */
  1139. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  1140. {
  1141. return NO;
  1142. }
  1143. /** To not detect touch events in a subclass of UIControl, these may have added their own selector for specific work */
  1144. -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
  1145. {
  1146. // Should not recognize gesture if the clicked view is either UIControl or UINavigationBar(<Back button etc...) (Bug ID: #145)
  1147. for (Class aClass in self.touchResignedGestureIgnoreClasses)
  1148. {
  1149. if ([[touch view] isKindOfClass:aClass])
  1150. {
  1151. return NO;
  1152. }
  1153. }
  1154. return YES;
  1155. }
  1156. /** Resigning textField. */
  1157. - (BOOL)resignFirstResponder
  1158. {
  1159. UIView *textFieldView = _textFieldView;
  1160. if (textFieldView)
  1161. {
  1162. // Retaining textFieldView
  1163. UIView *textFieldRetain = textFieldView;
  1164. //Resigning first responder
  1165. BOOL isResignFirstResponder = [textFieldView resignFirstResponder];
  1166. // If it refuses then becoming it as first responder again. (Bug ID: #96)
  1167. if (isResignFirstResponder == NO)
  1168. {
  1169. //If it refuses to resign then becoming it first responder again for getting notifications callback.
  1170. [textFieldRetain becomeFirstResponder];
  1171. [self showLog:[NSString stringWithFormat:@"Refuses to Resign first responder: %@",[textFieldView _IQDescription]]];
  1172. }
  1173. return isResignFirstResponder;
  1174. }
  1175. else
  1176. {
  1177. return NO;
  1178. }
  1179. }
  1180. /** Returns YES if can navigate to previous responder textField/textView, otherwise NO. */
  1181. -(BOOL)canGoPrevious
  1182. {
  1183. //Getting all responder view's.
  1184. NSArray<UIView*> *textFields = [self responderViews];
  1185. //Getting index of current textField.
  1186. NSUInteger index = [textFields indexOfObject:_textFieldView];
  1187. //If it is not first textField. then it's previous object can becomeFirstResponder.
  1188. if (index != NSNotFound &&
  1189. index > 0)
  1190. {
  1191. return YES;
  1192. }
  1193. else
  1194. {
  1195. return NO;
  1196. }
  1197. }
  1198. /** Returns YES if can navigate to next responder textField/textView, otherwise NO. */
  1199. -(BOOL)canGoNext
  1200. {
  1201. //Getting all responder view's.
  1202. NSArray<UIView*> *textFields = [self responderViews];
  1203. //Getting index of current textField.
  1204. NSUInteger index = [textFields indexOfObject:_textFieldView];
  1205. //If it is not last textField. then it's next object becomeFirstResponder.
  1206. if (index != NSNotFound &&
  1207. index < textFields.count-1)
  1208. {
  1209. return YES;
  1210. }
  1211. else
  1212. {
  1213. return NO;
  1214. }
  1215. }
  1216. /** Navigate to previous responder textField/textView. */
  1217. -(BOOL)goPrevious
  1218. {
  1219. //Getting all responder view's.
  1220. NSArray<__kindof UIView*> *textFields = [self responderViews];
  1221. //Getting index of current textField.
  1222. NSUInteger index = [textFields indexOfObject:_textFieldView];
  1223. //If it is not first textField. then it's previous object becomeFirstResponder.
  1224. if (index != NSNotFound &&
  1225. index > 0)
  1226. {
  1227. UITextField *nextTextField = textFields[index-1];
  1228. // Retaining textFieldView
  1229. UIView *textFieldRetain = _textFieldView;
  1230. BOOL isAcceptAsFirstResponder = [nextTextField becomeFirstResponder];
  1231. // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
  1232. if (isAcceptAsFirstResponder == NO)
  1233. {
  1234. //If next field refuses to become first responder then restoring old textField as first responder.
  1235. [textFieldRetain becomeFirstResponder];
  1236. [self showLog:[NSString stringWithFormat:@"Refuses to become first responder: %@",[nextTextField _IQDescription]]];
  1237. }
  1238. return isAcceptAsFirstResponder;
  1239. }
  1240. else
  1241. {
  1242. return NO;
  1243. }
  1244. }
  1245. /** Navigate to next responder textField/textView. */
  1246. -(BOOL)goNext
  1247. {
  1248. //Getting all responder view's.
  1249. NSArray<__kindof UIView*> *textFields = [self responderViews];
  1250. //Getting index of current textField.
  1251. NSUInteger index = [textFields indexOfObject:_textFieldView];
  1252. //If it is not last textField. then it's next object becomeFirstResponder.
  1253. if (index != NSNotFound &&
  1254. index < textFields.count-1)
  1255. {
  1256. UITextField *nextTextField = textFields[index+1];
  1257. // Retaining textFieldView
  1258. UIView *textFieldRetain = _textFieldView;
  1259. BOOL isAcceptAsFirstResponder = [nextTextField becomeFirstResponder];
  1260. // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
  1261. if (isAcceptAsFirstResponder == NO)
  1262. {
  1263. //If next field refuses to become first responder then restoring old textField as first responder.
  1264. [textFieldRetain becomeFirstResponder];
  1265. [self showLog:[NSString stringWithFormat:@"Refuses to become first responder: %@",[nextTextField _IQDescription]]];
  1266. }
  1267. return isAcceptAsFirstResponder;
  1268. }
  1269. else
  1270. {
  1271. return NO;
  1272. }
  1273. }
  1274. #pragma mark AutoToolbar methods
  1275. /** Get all UITextField/UITextView siblings of textFieldView. */
  1276. -(NSArray<__kindof UIView*>*)responderViews
  1277. {
  1278. UIView *superConsideredView;
  1279. UIView *textFieldView = _textFieldView;
  1280. //If find any consider responderView in it's upper hierarchy then will get deepResponderView.
  1281. for (Class consideredClass in _toolbarPreviousNextAllowedClasses)
  1282. {
  1283. superConsideredView = [textFieldView superviewOfClassType:consideredClass];
  1284. if (superConsideredView)
  1285. break;
  1286. }
  1287. //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)
  1288. if (superConsideredView)
  1289. {
  1290. return [superConsideredView deepResponderViews];
  1291. }
  1292. //Otherwise fetching all the siblings
  1293. else
  1294. {
  1295. NSArray<UIView*> *textFields = [textFieldView responderSiblings];
  1296. //Sorting textFields according to behaviour
  1297. switch (_toolbarManageBehaviour)
  1298. {
  1299. //If autoToolbar behaviour is bySubviews, then returning it.
  1300. case IQAutoToolbarBySubviews:
  1301. return textFields;
  1302. break;
  1303. //If autoToolbar behaviour is by tag, then sorting it according to tag property.
  1304. case IQAutoToolbarByTag:
  1305. return [textFields sortedArrayByTag];
  1306. break;
  1307. //If autoToolbar behaviour is by tag, then sorting it according to tag property.
  1308. case IQAutoToolbarByPosition:
  1309. return [textFields sortedArrayByPosition];
  1310. break;
  1311. default:
  1312. return nil;
  1313. break;
  1314. }
  1315. }
  1316. }
  1317. /** Add toolbar if it is required to add on textFields and it's siblings. */
  1318. -(void)addToolbarIfRequired
  1319. {
  1320. CFTimeInterval startTime = CACurrentMediaTime();
  1321. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  1322. // Getting all the sibling textFields.
  1323. NSArray<UIView*> *siblings = [self responderViews];
  1324. [self showLog:[NSString stringWithFormat:@"Found %lu responder sibling(s)",(unsigned long)siblings.count]];
  1325. UIView *textFieldView = _textFieldView;
  1326. //Either there is no inputAccessoryView or if accessoryView is not appropriate for current situation(There is Previous/Next/Done toolbar).
  1327. //setInputAccessoryView: check (Bug ID: #307)
  1328. if ([textFieldView respondsToSelector:@selector(setInputAccessoryView:)])
  1329. {
  1330. if ([textFieldView inputAccessoryView] == nil ||
  1331. [[textFieldView inputAccessoryView] tag] == kIQPreviousNextButtonToolbarTag ||
  1332. [[textFieldView inputAccessoryView] tag] == kIQDoneButtonToolbarTag)
  1333. {
  1334. UITextField *textField = (UITextField*)textFieldView;
  1335. IQBarButtonItemConfiguration *rightConfiguration = nil;
  1336. //Supporting Custom Done button image (Enhancement ID: #366)
  1337. if (_toolbarDoneBarButtonItemImage)
  1338. {
  1339. rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:_toolbarDoneBarButtonItemImage action:@selector(doneAction:)];
  1340. }
  1341. //Supporting Custom Done button text (Enhancement ID: #209, #411, Bug ID: #376)
  1342. else if (_toolbarDoneBarButtonItemText)
  1343. {
  1344. rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithTitle:_toolbarDoneBarButtonItemText action:@selector(doneAction:)];
  1345. }
  1346. else
  1347. {
  1348. rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone action:@selector(doneAction:)];
  1349. }
  1350. // If only one object is found, then adding only Done button.
  1351. if ((siblings.count==1 && self.previousNextDisplayMode == IQPreviousNextDisplayModeDefault) || self.previousNextDisplayMode == IQPreviousNextDisplayModeAlwaysHide)
  1352. {
  1353. [textField addKeyboardToolbarWithTarget:self titleText:(_shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder : nil) rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:nil nextBarButtonConfiguration:nil];
  1354. textField.inputAccessoryView.tag = kIQDoneButtonToolbarTag; // (Bug ID: #78)
  1355. }
  1356. //If there is multiple siblings of textField
  1357. else if ((siblings.count && self.previousNextDisplayMode == IQPreviousNextDisplayModeDefault) || self.previousNextDisplayMode == IQPreviousNextDisplayModeAlwaysShow)
  1358. {
  1359. IQBarButtonItemConfiguration *prevConfiguration = nil;
  1360. //Supporting Custom Done button image (Enhancement ID: #366)
  1361. if (_toolbarPreviousBarButtonItemImage)
  1362. {
  1363. prevConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:_toolbarPreviousBarButtonItemImage action:@selector(previousAction:)];
  1364. }
  1365. //Supporting Custom Done button text (Enhancement ID: #209, #411, Bug ID: #376)
  1366. else if (_toolbarPreviousBarButtonItemText)
  1367. {
  1368. prevConfiguration = [[IQBarButtonItemConfiguration alloc] initWithTitle:_toolbarPreviousBarButtonItemText action:@selector(previousAction:)];
  1369. }
  1370. else
  1371. {
  1372. prevConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardPreviousImage] action:@selector(previousAction:)];
  1373. }
  1374. IQBarButtonItemConfiguration *nextConfiguration = nil;
  1375. //Supporting Custom Done button image (Enhancement ID: #366)
  1376. if (_toolbarNextBarButtonItemImage)
  1377. {
  1378. nextConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:_toolbarNextBarButtonItemImage action:@selector(nextAction:)];
  1379. }
  1380. //Supporting Custom Done button text (Enhancement ID: #209, #411, Bug ID: #376)
  1381. else if (_toolbarNextBarButtonItemText)
  1382. {
  1383. nextConfiguration = [[IQBarButtonItemConfiguration alloc] initWithTitle:_toolbarNextBarButtonItemText action:@selector(nextAction:)];
  1384. }
  1385. else
  1386. {
  1387. nextConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardNextImage] action:@selector(nextAction:)];
  1388. }
  1389. [textField addKeyboardToolbarWithTarget:self titleText:(_shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder : nil) rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:prevConfiguration nextBarButtonConfiguration:nextConfiguration];
  1390. textField.inputAccessoryView.tag = kIQPreviousNextButtonToolbarTag; // (Bug ID: #78)
  1391. }
  1392. IQToolbar *toolbar = textField.keyboardToolbar;
  1393. //Bar style according to keyboard appearance
  1394. if ([textField respondsToSelector:@selector(keyboardAppearance)])
  1395. {
  1396. switch ([textField keyboardAppearance])
  1397. {
  1398. case UIKeyboardAppearanceDark:
  1399. {
  1400. toolbar.barStyle = UIBarStyleBlack;
  1401. [toolbar setTintColor:[UIColor whiteColor]];
  1402. [toolbar setBarTintColor:nil];
  1403. }
  1404. break;
  1405. default:
  1406. {
  1407. toolbar.barStyle = UIBarStyleDefault;
  1408. toolbar.barTintColor = _toolbarBarTintColor;
  1409. //Setting toolbar tintColor // (Enhancement ID: #30)
  1410. if (_shouldToolbarUsesTextFieldTintColor)
  1411. {
  1412. toolbar.tintColor = [textField tintColor];
  1413. }
  1414. else if (_toolbarTintColor)
  1415. {
  1416. toolbar.tintColor = _toolbarTintColor;
  1417. }
  1418. else
  1419. {
  1420. toolbar.tintColor = [UIColor blackColor];
  1421. }
  1422. }
  1423. break;
  1424. }
  1425. //If need to show placeholder
  1426. if (_shouldShowToolbarPlaceholder &&
  1427. textField.shouldHideToolbarPlaceholder == NO)
  1428. {
  1429. //Updating placeholder //(Bug ID: #148, #272)
  1430. if (toolbar.titleBarButton.title == nil ||
  1431. [toolbar.titleBarButton.title isEqualToString:textField.drawingToolbarPlaceholder] == NO)
  1432. {
  1433. [toolbar.titleBarButton setTitle:textField.drawingToolbarPlaceholder];
  1434. }
  1435. //Setting toolbar title font. // (Enhancement ID: #30)
  1436. if (_placeholderFont &&
  1437. [_placeholderFont isKindOfClass:[UIFont class]])
  1438. {
  1439. [toolbar.titleBarButton setTitleFont:_placeholderFont];
  1440. }
  1441. //Setting toolbar title color. // (Enhancement ID: #880)
  1442. if (_placeholderColor &&
  1443. [_placeholderColor isKindOfClass:[UIColor class]])
  1444. {
  1445. [toolbar.titleBarButton setTitleColor:_placeholderColor];
  1446. }
  1447. //Setting toolbar button title color. // (Enhancement ID: #880)
  1448. if (_placeholderButtonColor &&
  1449. [_placeholderButtonColor isKindOfClass:[UIColor class]])
  1450. {
  1451. [toolbar.titleBarButton setSelectableTitleColor:_placeholderButtonColor];
  1452. }
  1453. }
  1454. else
  1455. {
  1456. //Updating placeholder //(Bug ID: #272)
  1457. toolbar.titleBarButton.title = nil;
  1458. }
  1459. }
  1460. //In case of UITableView (Special), the next/previous buttons has to be refreshed everytime. (Bug ID: #56)
  1461. // If firstTextField, then previous should not be enabled.
  1462. if (siblings.firstObject == textField)
  1463. {
  1464. if (siblings.count == 1)
  1465. {
  1466. textField.keyboardToolbar.previousBarButton.enabled = NO;
  1467. textField.keyboardToolbar.nextBarButton.enabled = NO;
  1468. }
  1469. else
  1470. {
  1471. textField.keyboardToolbar.previousBarButton.enabled = NO;
  1472. textField.keyboardToolbar.nextBarButton.enabled = YES;
  1473. }
  1474. }
  1475. // If lastTextField then next should not be enaled.
  1476. else if ([siblings lastObject] == textField)
  1477. {
  1478. textField.keyboardToolbar.previousBarButton.enabled = YES;
  1479. textField.keyboardToolbar.nextBarButton.enabled = NO;
  1480. }
  1481. else
  1482. {
  1483. textField.keyboardToolbar.previousBarButton.enabled = YES;
  1484. textField.keyboardToolbar.nextBarButton.enabled = YES;
  1485. }
  1486. }
  1487. }
  1488. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  1489. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  1490. }
  1491. /** Remove any toolbar if it is IQToolbar. */
  1492. -(void)removeToolbarIfRequired // (Bug ID: #18)
  1493. {
  1494. CFTimeInterval startTime = CACurrentMediaTime();
  1495. [self showLog:[NSString stringWithFormat:@"****** %@ started ******",NSStringFromSelector(_cmd)]];
  1496. // Getting all the sibling textFields.
  1497. NSArray<UIView*> *siblings = [self responderViews];
  1498. [self showLog:[NSString stringWithFormat:@"Found %lu responder sibling(s)",(unsigned long)siblings.count]];
  1499. for (UITextField *textField in siblings)
  1500. {
  1501. UIView *toolbar = [textField inputAccessoryView];
  1502. // (Bug ID: #78)
  1503. //setInputAccessoryView: check (Bug ID: #307)
  1504. if ([textField respondsToSelector:@selector(setInputAccessoryView:)] &&
  1505. ([toolbar isKindOfClass:[IQToolbar class]] && (toolbar.tag == kIQDoneButtonToolbarTag || toolbar.tag == kIQPreviousNextButtonToolbarTag)))
  1506. {
  1507. textField.inputAccessoryView = nil;
  1508. [textField reloadInputViews];
  1509. }
  1510. }
  1511. CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;
  1512. [self showLog:[NSString stringWithFormat:@"****** %@ ended: %g seconds ******\n",NSStringFromSelector(_cmd),elapsedTime]];
  1513. }
  1514. /** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
  1515. - (void)reloadInputViews
  1516. {
  1517. //If enabled then adding toolbar.
  1518. if ([self privateIsEnableAutoToolbar] == YES)
  1519. {
  1520. [self addToolbarIfRequired];
  1521. }
  1522. //Else removing toolbar.
  1523. else
  1524. {
  1525. [self removeToolbarIfRequired];
  1526. }
  1527. }
  1528. #pragma mark previous/next/done functionality
  1529. /** previousAction. */
  1530. -(void)previousAction:(IQBarButtonItem*)barButton
  1531. {
  1532. //If user wants to play input Click sound. Then Play Input Click Sound.
  1533. if (_shouldPlayInputClicks)
  1534. {
  1535. [[UIDevice currentDevice] playInputClick];
  1536. }
  1537. if ([self canGoPrevious])
  1538. {
  1539. UIView *currentTextFieldView = _textFieldView;
  1540. BOOL isAcceptAsFirstResponder = [self goPrevious];
  1541. NSInvocation *invocation = barButton.invocation;
  1542. //Handling search bar special case
  1543. {
  1544. UISearchBar *searchBar = currentTextFieldView.searchBar;
  1545. if (searchBar)
  1546. {
  1547. invocation = searchBar.keyboardToolbar.previousBarButton.invocation;
  1548. }
  1549. }
  1550. if (isAcceptAsFirstResponder == YES && barButton.invocation)
  1551. {
  1552. if (barButton.invocation.methodSignature.numberOfArguments > 2)
  1553. {
  1554. [barButton.invocation setArgument:&currentTextFieldView atIndex:2];
  1555. }
  1556. [barButton.invocation invoke];
  1557. }
  1558. }
  1559. }
  1560. /** nextAction. */
  1561. -(void)nextAction:(IQBarButtonItem*)barButton
  1562. {
  1563. //If user wants to play input Click sound. Then Play Input Click Sound.
  1564. if (_shouldPlayInputClicks)
  1565. {
  1566. [[UIDevice currentDevice] playInputClick];
  1567. }
  1568. if ([self canGoNext])
  1569. {
  1570. UIView *currentTextFieldView = _textFieldView;
  1571. BOOL isAcceptAsFirstResponder = [self goNext];
  1572. NSInvocation *invocation = barButton.invocation;
  1573. //Handling search bar special case
  1574. {
  1575. UISearchBar *searchBar = currentTextFieldView.searchBar;
  1576. if (searchBar)
  1577. {
  1578. invocation = searchBar.keyboardToolbar.nextBarButton.invocation;
  1579. }
  1580. }
  1581. if (isAcceptAsFirstResponder == YES && barButton.invocation)
  1582. {
  1583. if (barButton.invocation.methodSignature.numberOfArguments > 2)
  1584. {
  1585. [barButton.invocation setArgument:&currentTextFieldView atIndex:2];
  1586. }
  1587. [barButton.invocation invoke];
  1588. }
  1589. }
  1590. }
  1591. /** doneAction. Resigning current textField. */
  1592. -(void)doneAction:(IQBarButtonItem*)barButton
  1593. {
  1594. //If user wants to play input Click sound. Then Play Input Click Sound.
  1595. if (_shouldPlayInputClicks)
  1596. {
  1597. [[UIDevice currentDevice] playInputClick];
  1598. }
  1599. UIView *currentTextFieldView = _textFieldView;
  1600. BOOL isResignedFirstResponder = [self resignFirstResponder];
  1601. NSInvocation *invocation = barButton.invocation;
  1602. //Handling search bar special case
  1603. {
  1604. UISearchBar *searchBar = currentTextFieldView.searchBar;
  1605. if (searchBar)
  1606. {
  1607. invocation = searchBar.keyboardToolbar.doneBarButton.invocation;
  1608. }
  1609. }
  1610. if (isResignedFirstResponder == YES && barButton.invocation)
  1611. {
  1612. if (barButton.invocation.methodSignature.numberOfArguments > 2)
  1613. {
  1614. [barButton.invocation setArgument:&currentTextFieldView atIndex:2];
  1615. }
  1616. [barButton.invocation invoke];
  1617. }
  1618. }
  1619. #pragma mark - Customised textField/textView support.
  1620. /**
  1621. Add customised Notification for third party customised TextField/TextView.
  1622. */
  1623. -(void)registerTextFieldViewClass:(nonnull Class)aClass
  1624. didBeginEditingNotificationName:(nonnull NSString *)didBeginEditingNotificationName
  1625. didEndEditingNotificationName:(nonnull NSString *)didEndEditingNotificationName
  1626. {
  1627. [_registeredClasses addObject:aClass];
  1628. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidBeginEditing:) name:didBeginEditingNotificationName object:nil];
  1629. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidEndEditing:) name:didEndEditingNotificationName object:nil];
  1630. }
  1631. /**
  1632. Remove customised Notification for third party customised TextField/TextView.
  1633. */
  1634. -(void)unregisterTextFieldViewClass:(nonnull Class)aClass
  1635. didBeginEditingNotificationName:(nonnull NSString *)didBeginEditingNotificationName
  1636. didEndEditingNotificationName:(nonnull NSString *)didEndEditingNotificationName
  1637. {
  1638. [_registeredClasses removeObject:aClass];
  1639. [[NSNotificationCenter defaultCenter] removeObserver:self name:didBeginEditingNotificationName object:nil];
  1640. [[NSNotificationCenter defaultCenter] removeObserver:self name:didEndEditingNotificationName object:nil];
  1641. }
  1642. -(void)registerAllNotifications
  1643. {
  1644. // Registering for keyboard notification.
  1645. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
  1646. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
  1647. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
  1648. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
  1649. // Registering for UITextField notification.
  1650. [self registerTextFieldViewClass:[UITextField class]
  1651. didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification
  1652. didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];
  1653. // Registering for UITextView notification.
  1654. [self registerTextFieldViewClass:[UITextView class]
  1655. didBeginEditingNotificationName:UITextViewTextDidBeginEditingNotification
  1656. didEndEditingNotificationName:UITextViewTextDidEndEditingNotification];
  1657. // Registering for orientation changes notification
  1658. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willChangeStatusBarOrientation:) name:UIApplicationWillChangeStatusBarOrientationNotification object:[UIApplication sharedApplication]];
  1659. }
  1660. -(void)unregisterAllNotifications
  1661. {
  1662. // Unregistering for keyboard notification.
  1663. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
  1664. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
  1665. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
  1666. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
  1667. // Unregistering for UITextField notification.
  1668. [self unregisterTextFieldViewClass:[UITextField class]
  1669. didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification
  1670. didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];
  1671. // Unregistering for UITextView notification.
  1672. [self unregisterTextFieldViewClass:[UITextView class]
  1673. didBeginEditingNotificationName:UITextViewTextDidBeginEditingNotification
  1674. didEndEditingNotificationName:UITextViewTextDidEndEditingNotification];
  1675. // Unregistering for orientation changes notification
  1676. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillChangeStatusBarOrientationNotification object:[UIApplication sharedApplication]];
  1677. }
  1678. -(void)showLog:(NSString*)logString
  1679. {
  1680. if (_enableDebugging)
  1681. {
  1682. NSLog(@"IQKeyboardManager: %@",logString);
  1683. }
  1684. }
  1685. @end