悟空记账

IQKeyboardManager.m 95KB

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