IQKeyboardManager.m 84 KB

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