猎豆优选

IQKeyboardManager.m 94KB

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