IQKeyboardReturnKeyHandler.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. //
  2. // IQKeyboardReturnKeyHandler.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 "IQKeyboardReturnKeyHandler.h"
  24. #import "IQKeyboardManager.h"
  25. #import "IQUIView+Hierarchy.h"
  26. #import "IQNSArray+Sort.h"
  27. #import <UIKit/UITextField.h>
  28. #import <UIKit/UITextView.h>
  29. #import <UIKit/UIViewController.h>
  30. @interface IQTextFieldViewInfoModal : NSObject
  31. @property(nullable, nonatomic, weak) UIView *textFieldView;
  32. @property(nullable, nonatomic, weak) id<UITextFieldDelegate> textFieldDelegate;
  33. @property(nullable, nonatomic, weak) id<UITextViewDelegate> textViewDelegate;
  34. @property(nonatomic) UIReturnKeyType originalReturnKeyType;
  35. @end
  36. @implementation IQTextFieldViewInfoModal
  37. -(instancetype)initWithTextFieldView:(UIView*)textFieldView textFieldDelegate:(id<UITextFieldDelegate>)textFieldDelegate textViewDelegate:(id<UITextViewDelegate>)textViewDelegate originalReturnKey:(UIReturnKeyType)returnKeyType
  38. {
  39. self = [super init];
  40. if (self)
  41. {
  42. _textFieldView = textFieldView;
  43. _textFieldDelegate = textFieldDelegate;
  44. _textViewDelegate = textViewDelegate;
  45. _originalReturnKeyType = returnKeyType;
  46. }
  47. return self;
  48. }
  49. @end
  50. @interface IQKeyboardReturnKeyHandler ()<UITextFieldDelegate,UITextViewDelegate>
  51. -(void)updateReturnKeyTypeOnTextField:(UIView*)textField;
  52. @end
  53. @implementation IQKeyboardReturnKeyHandler
  54. {
  55. NSMutableSet<IQTextFieldViewInfoModal*> *textFieldInfoCache;
  56. }
  57. @synthesize lastTextFieldReturnKeyType = _lastTextFieldReturnKeyType;
  58. @synthesize delegate = _delegate;
  59. - (instancetype)init
  60. {
  61. self = [self initWithViewController:nil];
  62. return self;
  63. }
  64. -(instancetype)initWithViewController:(nullable UIViewController*)controller
  65. {
  66. self = [super init];
  67. if (self)
  68. {
  69. textFieldInfoCache = [[NSMutableSet alloc] init];
  70. if (controller.view)
  71. {
  72. [self addResponderFromView:controller.view];
  73. }
  74. }
  75. return self;
  76. }
  77. -(IQTextFieldViewInfoModal*)textFieldViewCachedInfo:(UIView*)textField
  78. {
  79. for (IQTextFieldViewInfoModal *modal in textFieldInfoCache)
  80. if (modal.textFieldView == textField) return modal;
  81. return nil;
  82. }
  83. #pragma mark - Add/Remove TextFields
  84. -(void)addResponderFromView:(UIView*)view
  85. {
  86. NSArray<UIView*> *textFields = [view deepResponderViews];
  87. for (UIView *textField in textFields) [self addTextFieldView:textField];
  88. }
  89. -(void)removeResponderFromView:(UIView*)view
  90. {
  91. NSArray<UIView*> *textFields = [view deepResponderViews];
  92. for (UIView *textField in textFields) [self removeTextFieldView:textField];
  93. }
  94. -(void)removeTextFieldView:(UIView*)view
  95. {
  96. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:view];
  97. if (modal)
  98. {
  99. UITextField *textField = (UITextField*)view;
  100. if ([view respondsToSelector:@selector(setReturnKeyType:)])
  101. {
  102. textField.returnKeyType = modal.originalReturnKeyType;
  103. }
  104. if ([view respondsToSelector:@selector(setDelegate:)])
  105. {
  106. textField.delegate = modal.textFieldDelegate;
  107. }
  108. [textFieldInfoCache removeObject:modal];
  109. }
  110. }
  111. -(void)addTextFieldView:(UIView*)view
  112. {
  113. IQTextFieldViewInfoModal *modal = [[IQTextFieldViewInfoModal alloc] initWithTextFieldView:view textFieldDelegate:nil textViewDelegate:nil originalReturnKey:UIReturnKeyDefault];
  114. UITextField *textField = (UITextField*)view;
  115. if ([view respondsToSelector:@selector(setReturnKeyType:)])
  116. {
  117. modal.originalReturnKeyType = textField.returnKeyType;
  118. }
  119. if ([view respondsToSelector:@selector(setDelegate:)])
  120. {
  121. modal.textFieldDelegate = textField.delegate;
  122. [textField setDelegate:self];
  123. }
  124. [textFieldInfoCache addObject:modal];
  125. }
  126. -(void)updateReturnKeyTypeOnTextField:(UIView*)textField
  127. {
  128. UIView *superConsideredView;
  129. //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
  130. for (Class consideredClass in [[IQKeyboardManager sharedManager] toolbarPreviousNextAllowedClasses])
  131. {
  132. superConsideredView = [textField superviewOfClassType:consideredClass];
  133. if (superConsideredView)
  134. break;
  135. }
  136. NSArray<UIView*> *textFields = nil;
  137. //If there is a tableView in view's hierarchy, then fetching all it's subview that responds. No sorting for tableView, it's by subView position.
  138. if (superConsideredView) // // (Enhancement ID: #22)
  139. {
  140. textFields = [superConsideredView deepResponderViews];
  141. }
  142. //Otherwise fetching all the siblings
  143. else
  144. {
  145. textFields = [textField responderSiblings];
  146. //Sorting textFields according to behaviour
  147. switch ([[IQKeyboardManager sharedManager] toolbarManageBehaviour])
  148. {
  149. //If needs to sort it by tag
  150. case IQAutoToolbarByTag:
  151. textFields = [textFields sortedArrayByTag];
  152. break;
  153. //If needs to sort it by Position
  154. case IQAutoToolbarByPosition:
  155. textFields = [textFields sortedArrayByPosition];
  156. break;
  157. default:
  158. break;
  159. }
  160. }
  161. //If it's the last textField in responder view, else next
  162. [(UITextField*)textField setReturnKeyType:(([textFields lastObject] == textField) ? self.lastTextFieldReturnKeyType : UIReturnKeyNext)];
  163. }
  164. #pragma mark - Goto next or Resign.
  165. -(BOOL)goToNextResponderOrResign:(UIView*)textField
  166. {
  167. UIView *superConsideredView;
  168. //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
  169. for (Class consideredClass in [[IQKeyboardManager sharedManager] toolbarPreviousNextAllowedClasses])
  170. {
  171. superConsideredView = [textField superviewOfClassType:consideredClass];
  172. if (superConsideredView)
  173. break;
  174. }
  175. NSArray<UIView*> *textFields = nil;
  176. //If there is a tableView in view's hierarchy, then fetching all it's subview that responds. No sorting for tableView, it's by subView position.
  177. if (superConsideredView) // // (Enhancement ID: #22)
  178. {
  179. textFields = [superConsideredView deepResponderViews];
  180. }
  181. //Otherwise fetching all the siblings
  182. else
  183. {
  184. textFields = [textField responderSiblings];
  185. //Sorting textFields according to behaviour
  186. switch ([[IQKeyboardManager sharedManager] toolbarManageBehaviour])
  187. {
  188. //If needs to sort it by tag
  189. case IQAutoToolbarByTag:
  190. textFields = [textFields sortedArrayByTag];
  191. break;
  192. //If needs to sort it by Position
  193. case IQAutoToolbarByPosition:
  194. textFields = [textFields sortedArrayByPosition];
  195. break;
  196. default:
  197. break;
  198. }
  199. }
  200. //Getting index of current textField.
  201. NSUInteger index = [textFields indexOfObject:textField];
  202. //If it is not last textField. then it's next object becomeFirstResponder.
  203. if (index != NSNotFound && index < textFields.count-1)
  204. {
  205. [textFields[index+1] becomeFirstResponder];
  206. return NO;
  207. }
  208. else
  209. {
  210. [textField resignFirstResponder];
  211. return YES;
  212. }
  213. }
  214. #pragma mark - TextField delegate
  215. - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
  216. {
  217. id<UITextFieldDelegate> delegate = self.delegate;
  218. if (delegate == nil)
  219. {
  220. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  221. delegate = modal.textFieldDelegate;
  222. }
  223. if ([delegate respondsToSelector:@selector(textFieldShouldBeginEditing:)])
  224. return [delegate textFieldShouldBeginEditing:textField];
  225. else
  226. return YES;
  227. }
  228. - (void)textFieldDidBeginEditing:(UITextField *)textField
  229. {
  230. [self updateReturnKeyTypeOnTextField:textField];
  231. id<UITextFieldDelegate> delegate = self.delegate;
  232. if (delegate == nil)
  233. {
  234. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  235. delegate = modal.textFieldDelegate;
  236. }
  237. if ([delegate respondsToSelector:@selector(textFieldDidBeginEditing:)])
  238. [delegate textFieldDidBeginEditing:textField];
  239. }
  240. - (BOOL)textFieldShouldEndEditing:(UITextField *)textField
  241. {
  242. id<UITextFieldDelegate> delegate = self.delegate;
  243. if (delegate == nil)
  244. {
  245. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  246. delegate = modal.textFieldDelegate;
  247. }
  248. if ([delegate respondsToSelector:@selector(textFieldShouldEndEditing:)])
  249. return [delegate textFieldShouldEndEditing:textField];
  250. else
  251. return YES;
  252. }
  253. - (void)textFieldDidEndEditing:(UITextField *)textField
  254. {
  255. id<UITextFieldDelegate> delegate = self.delegate;
  256. if (delegate == nil)
  257. {
  258. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  259. delegate = modal.textFieldDelegate;
  260. }
  261. if ([delegate respondsToSelector:@selector(textFieldDidEndEditing:)])
  262. [delegate textFieldDidEndEditing:textField];
  263. }
  264. - (void)textFieldDidEndEditing:(UITextField *)textField reason:(UITextFieldDidEndEditingReason)reason NS_AVAILABLE_IOS(10_0);
  265. {
  266. id<UITextFieldDelegate> delegate = self.delegate;
  267. if (delegate == nil)
  268. {
  269. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  270. delegate = modal.textFieldDelegate;
  271. }
  272. if (@available(iOS 10.0, *)) {
  273. if ([delegate respondsToSelector:@selector(textFieldDidEndEditing:reason:)])
  274. [delegate textFieldDidEndEditing:textField reason:reason];
  275. }
  276. }
  277. - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
  278. {
  279. id<UITextFieldDelegate> delegate = self.delegate;
  280. if (delegate == nil)
  281. {
  282. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  283. delegate = modal.textFieldDelegate;
  284. }
  285. if ([delegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)])
  286. return [delegate textField:textField shouldChangeCharactersInRange:range replacementString:string];
  287. else
  288. return YES;
  289. }
  290. - (BOOL)textFieldShouldClear:(UITextField *)textField
  291. {
  292. id<UITextFieldDelegate> delegate = self.delegate;
  293. if (delegate == nil)
  294. {
  295. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  296. delegate = modal.textFieldDelegate;
  297. }
  298. if ([delegate respondsToSelector:@selector(textFieldShouldClear:)])
  299. return [delegate textFieldShouldClear:textField];
  300. else
  301. return YES;
  302. }
  303. -(BOOL)textFieldShouldReturn:(UITextField *)textField
  304. {
  305. id<UITextFieldDelegate> delegate = self.delegate;
  306. if (delegate == nil)
  307. {
  308. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
  309. delegate = modal.textFieldDelegate;
  310. }
  311. if ([delegate respondsToSelector:@selector(textFieldShouldReturn:)])
  312. {
  313. BOOL shouldReturn = [delegate textFieldShouldReturn:textField];
  314. if (shouldReturn)
  315. {
  316. shouldReturn = [self goToNextResponderOrResign:textField];
  317. }
  318. return shouldReturn;
  319. }
  320. else
  321. {
  322. return [self goToNextResponderOrResign:textField];
  323. }
  324. }
  325. #pragma mark - TextView delegate
  326. - (BOOL)textViewShouldBeginEditing:(UITextView *)textView
  327. {
  328. id<UITextViewDelegate> delegate = self.delegate;
  329. if (delegate == nil)
  330. {
  331. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  332. delegate = modal.textViewDelegate;
  333. }
  334. if ([delegate respondsToSelector:@selector(textViewShouldBeginEditing:)])
  335. return [delegate textViewShouldBeginEditing:textView];
  336. else
  337. return YES;
  338. }
  339. - (BOOL)textViewShouldEndEditing:(UITextView *)textView
  340. {
  341. id<UITextViewDelegate> delegate = self.delegate;
  342. if (delegate == nil)
  343. {
  344. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  345. delegate = modal.textViewDelegate;
  346. }
  347. if ([delegate respondsToSelector:@selector(textViewShouldEndEditing:)])
  348. return [delegate textViewShouldEndEditing:textView];
  349. else
  350. return YES;
  351. }
  352. - (void)textViewDidBeginEditing:(UITextView *)textView
  353. {
  354. [self updateReturnKeyTypeOnTextField:textView];
  355. id<UITextViewDelegate> delegate = self.delegate;
  356. if (delegate == nil)
  357. {
  358. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  359. delegate = modal.textViewDelegate;
  360. }
  361. if ([delegate respondsToSelector:@selector(textViewDidBeginEditing:)])
  362. [delegate textViewDidBeginEditing:textView];
  363. }
  364. - (void)textViewDidEndEditing:(UITextView *)textView
  365. {
  366. id<UITextViewDelegate> delegate = self.delegate;
  367. if (delegate == nil)
  368. {
  369. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  370. delegate = modal.textViewDelegate;
  371. }
  372. if ([delegate respondsToSelector:@selector(textViewDidEndEditing:)])
  373. [delegate textViewDidEndEditing:textView];
  374. }
  375. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
  376. {
  377. id<UITextViewDelegate> delegate = self.delegate;
  378. if (delegate == nil)
  379. {
  380. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  381. delegate = modal.textViewDelegate;
  382. }
  383. BOOL shouldReturn = YES;
  384. if ([delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)])
  385. shouldReturn = [delegate textView:textView shouldChangeTextInRange:range replacementText:text];
  386. if (shouldReturn && [text isEqualToString:@"\n"])
  387. {
  388. shouldReturn = [self goToNextResponderOrResign:textView];
  389. }
  390. return shouldReturn;
  391. }
  392. - (void)textViewDidChange:(UITextView *)textView
  393. {
  394. id<UITextViewDelegate> delegate = self.delegate;
  395. if (delegate == nil)
  396. {
  397. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  398. delegate = modal.textViewDelegate;
  399. }
  400. if ([delegate respondsToSelector:@selector(textViewDidChange:)])
  401. [delegate textViewDidChange:textView];
  402. }
  403. - (void)textViewDidChangeSelection:(UITextView *)textView
  404. {
  405. id<UITextViewDelegate> delegate = self.delegate;
  406. if (delegate == nil)
  407. {
  408. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  409. delegate = modal.textViewDelegate;
  410. }
  411. if ([delegate respondsToSelector:@selector(textViewDidChangeSelection:)])
  412. [delegate textViewDidChangeSelection:textView];
  413. }
  414. - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0);
  415. {
  416. id<UITextViewDelegate> delegate = self.delegate;
  417. if (delegate == nil)
  418. {
  419. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  420. delegate = modal.textViewDelegate;
  421. }
  422. if (@available(iOS 10.0, *)) {
  423. if ([delegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:interaction:)])
  424. return [delegate textView:textView shouldInteractWithURL:URL inRange:characterRange interaction:interaction];
  425. }
  426. return YES;
  427. }
  428. - (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0);
  429. {
  430. id<UITextViewDelegate> delegate = self.delegate;
  431. if (delegate == nil)
  432. {
  433. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  434. delegate = modal.textViewDelegate;
  435. }
  436. if (@available(iOS 10.0, *)) {
  437. if ([delegate respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:interaction:)])
  438. return [delegate textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange interaction:interaction];
  439. }
  440. return YES;
  441. }
  442. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100000
  443. - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
  444. {
  445. id<UITextViewDelegate> delegate = self.delegate;
  446. if (delegate == nil)
  447. {
  448. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  449. delegate = modal.textViewDelegate;
  450. }
  451. if ([delegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:)])
  452. return [delegate textView:textView shouldInteractWithURL:URL inRange:characterRange];
  453. else
  454. return YES;
  455. }
  456. - (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange
  457. {
  458. id<UITextViewDelegate> delegate = self.delegate;
  459. if (delegate == nil)
  460. {
  461. IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
  462. delegate = modal.textViewDelegate;
  463. }
  464. if ([delegate respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:)])
  465. return [delegate textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange];
  466. else
  467. return YES;
  468. }
  469. #endif
  470. -(void)dealloc
  471. {
  472. for (IQTextFieldViewInfoModal *modal in textFieldInfoCache)
  473. {
  474. UITextField *textField = (UITextField*)modal.textFieldView;
  475. if ([textField respondsToSelector:@selector(setReturnKeyType:)])
  476. {
  477. textField.returnKeyType = modal.originalReturnKeyType;
  478. }
  479. if ([textField respondsToSelector:@selector(setDelegate:)])
  480. {
  481. textField.delegate = modal.textFieldDelegate;
  482. }
  483. }
  484. [textFieldInfoCache removeAllObjects];
  485. }
  486. @end