IQKeyboardReturnKeyHandler.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. //
  2. // IQKeyboardReturnKeyHandler.swift
  3. // https://github.com/hackiftekhar/IQKeyboardManager
  4. // Copyright (c) 2013-20 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 Foundation - UIKit contains Foundation
  24. import UIKit
  25. @available(iOSApplicationExtension, unavailable)
  26. private final class IQTextFieldViewInfoModal: NSObject {
  27. fileprivate weak var textFieldDelegate: UITextFieldDelegate?
  28. fileprivate weak var textViewDelegate: UITextViewDelegate?
  29. fileprivate weak var textFieldView: UIView?
  30. fileprivate var originalReturnKeyType = UIReturnKeyType.default
  31. init(textFieldView: UIView?, textFieldDelegate: UITextFieldDelegate?, textViewDelegate: UITextViewDelegate?, originalReturnKeyType: UIReturnKeyType = .default) {
  32. self.textFieldView = textFieldView
  33. self.textFieldDelegate = textFieldDelegate
  34. self.textViewDelegate = textViewDelegate
  35. self.originalReturnKeyType = originalReturnKeyType
  36. }
  37. }
  38. /**
  39. Manages the return key to work like next/done in a view hierarchy.
  40. */
  41. @available(iOSApplicationExtension, unavailable)
  42. @objc public final class IQKeyboardReturnKeyHandler: NSObject {
  43. // MARK: Settings
  44. /**
  45. Delegate of textField/textView.
  46. */
  47. @objc public weak var delegate: (UITextFieldDelegate & UITextViewDelegate)?
  48. /**
  49. Set the last textfield return key type. Default is UIReturnKeyDefault.
  50. */
  51. @objc public var lastTextFieldReturnKeyType: UIReturnKeyType = UIReturnKeyType.default {
  52. didSet {
  53. for modal in textFieldInfoCache {
  54. if let view = modal.textFieldView {
  55. updateReturnKeyTypeOnTextField(view)
  56. }
  57. }
  58. }
  59. }
  60. // MARK: Initialization/Deinitialization
  61. @objc public override init() {
  62. super.init()
  63. }
  64. /**
  65. Add all the textFields available in UIViewController's view.
  66. */
  67. @objc public init(controller: UIViewController) {
  68. super.init()
  69. addResponderFromView(controller.view)
  70. }
  71. deinit {
  72. for modal in textFieldInfoCache {
  73. if let textField = modal.textFieldView as? UITextField {
  74. textField.returnKeyType = modal.originalReturnKeyType
  75. textField.delegate = modal.textFieldDelegate
  76. } else if let textView = modal.textFieldView as? UITextView {
  77. textView.returnKeyType = modal.originalReturnKeyType
  78. textView.delegate = modal.textViewDelegate
  79. }
  80. }
  81. textFieldInfoCache.removeAll()
  82. }
  83. // MARK: Private variables
  84. private var textFieldInfoCache = [IQTextFieldViewInfoModal]()
  85. // MARK: Private Functions
  86. private func textFieldViewCachedInfo(_ textField: UIView) -> IQTextFieldViewInfoModal? {
  87. for modal in textFieldInfoCache {
  88. if let view = modal.textFieldView {
  89. if view == textField {
  90. return modal
  91. }
  92. }
  93. }
  94. return nil
  95. }
  96. private func updateReturnKeyTypeOnTextField(_ view: UIView) {
  97. var superConsideredView: UIView?
  98. //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
  99. for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
  100. superConsideredView = view.superviewOfClassType(disabledClass)
  101. if superConsideredView != nil {
  102. break
  103. }
  104. }
  105. var textFields = [UIView]()
  106. //If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
  107. if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22)
  108. textFields = unwrappedTableView.deepResponderViews()
  109. } else { //Otherwise fetching all the siblings
  110. textFields = view.responderSiblings()
  111. //Sorting textFields according to behaviour
  112. switch IQKeyboardManager.shared.toolbarManageBehaviour {
  113. //If needs to sort it by tag
  114. case .byTag: textFields = textFields.sortedArrayByTag()
  115. //If needs to sort it by Position
  116. case .byPosition: textFields = textFields.sortedArrayByPosition()
  117. default: break
  118. }
  119. }
  120. if let lastView = textFields.last {
  121. if let textField = view as? UITextField {
  122. //If it's the last textField in responder view, else next
  123. textField.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
  124. } else if let textView = view as? UITextView {
  125. //If it's the last textField in responder view, else next
  126. textView.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
  127. }
  128. }
  129. }
  130. // MARK: Registering/Unregistering textFieldView
  131. /**
  132. Should pass UITextField/UITextView intance. Assign textFieldView delegate to self, change it's returnKeyType.
  133. @param view UITextField/UITextView object to register.
  134. */
  135. @objc public func addTextFieldView(_ view: UIView) {
  136. let modal = IQTextFieldViewInfoModal(textFieldView: view, textFieldDelegate: nil, textViewDelegate: nil)
  137. if let textField = view as? UITextField {
  138. modal.originalReturnKeyType = textField.returnKeyType
  139. modal.textFieldDelegate = textField.delegate
  140. textField.delegate = self
  141. } else if let textView = view as? UITextView {
  142. modal.originalReturnKeyType = textView.returnKeyType
  143. modal.textViewDelegate = textView.delegate
  144. textView.delegate = self
  145. }
  146. textFieldInfoCache.append(modal)
  147. }
  148. /**
  149. Should pass UITextField/UITextView intance. Restore it's textFieldView delegate and it's returnKeyType.
  150. @param view UITextField/UITextView object to unregister.
  151. */
  152. @objc public func removeTextFieldView(_ view: UIView) {
  153. if let modal = textFieldViewCachedInfo(view) {
  154. if let textField = view as? UITextField {
  155. textField.returnKeyType = modal.originalReturnKeyType
  156. textField.delegate = modal.textFieldDelegate
  157. } else if let textView = view as? UITextView {
  158. textView.returnKeyType = modal.originalReturnKeyType
  159. textView.delegate = modal.textViewDelegate
  160. }
  161. if let index = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) {
  162. textFieldInfoCache.remove(at: index)
  163. }
  164. }
  165. }
  166. /**
  167. Add all the UITextField/UITextView responderView's.
  168. @param view UIView object to register all it's responder subviews.
  169. */
  170. @objc public func addResponderFromView(_ view: UIView) {
  171. let textFields = view.deepResponderViews()
  172. for textField in textFields {
  173. addTextFieldView(textField)
  174. }
  175. }
  176. /**
  177. Remove all the UITextField/UITextView responderView's.
  178. @param view UIView object to unregister all it's responder subviews.
  179. */
  180. @objc public func removeResponderFromView(_ view: UIView) {
  181. let textFields = view.deepResponderViews()
  182. for textField in textFields {
  183. removeTextFieldView(textField)
  184. }
  185. }
  186. @discardableResult private func goToNextResponderOrResign(_ view: UIView) -> Bool {
  187. var superConsideredView: UIView?
  188. //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
  189. for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
  190. superConsideredView = view.superviewOfClassType(disabledClass)
  191. if superConsideredView != nil {
  192. break
  193. }
  194. }
  195. var textFields = [UIView]()
  196. //If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
  197. if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22)
  198. textFields = unwrappedTableView.deepResponderViews()
  199. } else { //Otherwise fetching all the siblings
  200. textFields = view.responderSiblings()
  201. //Sorting textFields according to behaviour
  202. switch IQKeyboardManager.shared.toolbarManageBehaviour {
  203. //If needs to sort it by tag
  204. case .byTag: textFields = textFields.sortedArrayByTag()
  205. //If needs to sort it by Position
  206. case .byPosition: textFields = textFields.sortedArrayByPosition()
  207. default:
  208. break
  209. }
  210. }
  211. //Getting index of current textField.
  212. if let index = textFields.firstIndex(of: view) {
  213. //If it is not last textField. then it's next object becomeFirstResponder.
  214. if index < (textFields.count - 1) {
  215. let nextTextField = textFields[index+1]
  216. nextTextField.becomeFirstResponder()
  217. return false
  218. } else {
  219. view.resignFirstResponder()
  220. return true
  221. }
  222. } else {
  223. return true
  224. }
  225. }
  226. }
  227. // MARK: UITextFieldDelegate
  228. @available(iOSApplicationExtension, unavailable)
  229. extension IQKeyboardReturnKeyHandler: UITextFieldDelegate {
  230. @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
  231. if delegate == nil {
  232. if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
  233. if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) {
  234. return unwrapDelegate.textFieldShouldBeginEditing?(textField) ?? false
  235. }
  236. }
  237. }
  238. return true
  239. }
  240. @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
  241. if delegate == nil {
  242. if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
  243. if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) {
  244. return unwrapDelegate.textFieldShouldEndEditing?(textField) ?? false
  245. }
  246. }
  247. }
  248. return true
  249. }
  250. @objc public func textFieldDidBeginEditing(_ textField: UITextField) {
  251. updateReturnKeyTypeOnTextField(textField)
  252. var aDelegate: UITextFieldDelegate? = delegate
  253. if aDelegate == nil {
  254. if let modal = textFieldViewCachedInfo(textField) {
  255. aDelegate = modal.textFieldDelegate
  256. }
  257. }
  258. aDelegate?.textFieldDidBeginEditing?(textField)
  259. }
  260. @objc public func textFieldDidEndEditing(_ textField: UITextField) {
  261. var aDelegate: UITextFieldDelegate? = delegate
  262. if aDelegate == nil {
  263. if let modal = textFieldViewCachedInfo(textField) {
  264. aDelegate = modal.textFieldDelegate
  265. }
  266. }
  267. aDelegate?.textFieldDidEndEditing?(textField)
  268. }
  269. @available(iOS 10.0, *)
  270. @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
  271. var aDelegate: UITextFieldDelegate? = delegate
  272. if aDelegate == nil {
  273. if let modal = textFieldViewCachedInfo(textField) {
  274. aDelegate = modal.textFieldDelegate
  275. }
  276. }
  277. aDelegate?.textFieldDidEndEditing?(textField, reason: reason)
  278. }
  279. @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  280. if delegate == nil {
  281. if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
  282. if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:))) {
  283. return unwrapDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? false
  284. }
  285. }
  286. }
  287. return true
  288. }
  289. @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
  290. if delegate == nil {
  291. if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
  292. if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) {
  293. return unwrapDelegate.textFieldShouldClear?(textField) ?? false
  294. }
  295. }
  296. }
  297. return true
  298. }
  299. @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  300. var shouldReturn = true
  301. if delegate == nil {
  302. if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
  303. if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) {
  304. shouldReturn = unwrapDelegate.textFieldShouldReturn?(textField) ?? false
  305. }
  306. }
  307. }
  308. if shouldReturn {
  309. goToNextResponderOrResign(textField)
  310. return true
  311. } else {
  312. return goToNextResponderOrResign(textField)
  313. }
  314. }
  315. }
  316. // MARK: UITextViewDelegate
  317. @available(iOSApplicationExtension, unavailable)
  318. extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
  319. @objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
  320. if delegate == nil {
  321. if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
  322. if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldBeginEditing(_:))) {
  323. return unwrapDelegate.textViewShouldBeginEditing?(textView) ?? false
  324. }
  325. }
  326. }
  327. return true
  328. }
  329. @objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
  330. if delegate == nil {
  331. if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
  332. if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldEndEditing(_:))) {
  333. return unwrapDelegate.textViewShouldEndEditing?(textView) ?? false
  334. }
  335. }
  336. }
  337. return true
  338. }
  339. @objc public func textViewDidBeginEditing(_ textView: UITextView) {
  340. updateReturnKeyTypeOnTextField(textView)
  341. var aDelegate: UITextViewDelegate? = delegate
  342. if aDelegate == nil {
  343. if let modal = textFieldViewCachedInfo(textView) {
  344. aDelegate = modal.textViewDelegate
  345. }
  346. }
  347. aDelegate?.textViewDidBeginEditing?(textView)
  348. }
  349. @objc public func textViewDidEndEditing(_ textView: UITextView) {
  350. var aDelegate: UITextViewDelegate? = delegate
  351. if aDelegate == nil {
  352. if let modal = textFieldViewCachedInfo(textView) {
  353. aDelegate = modal.textViewDelegate
  354. }
  355. }
  356. aDelegate?.textViewDidEndEditing?(textView)
  357. }
  358. @objc public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  359. var shouldReturn = true
  360. if delegate == nil {
  361. if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
  362. if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textView(_:shouldChangeTextIn:replacementText:))) {
  363. shouldReturn = (unwrapDelegate.textView?(textView, shouldChangeTextIn: range, replacementText: text)) ?? false
  364. }
  365. }
  366. }
  367. if shouldReturn, text == "\n" {
  368. shouldReturn = goToNextResponderOrResign(textView)
  369. }
  370. return shouldReturn
  371. }
  372. @objc public func textViewDidChange(_ textView: UITextView) {
  373. var aDelegate: UITextViewDelegate? = delegate
  374. if aDelegate == nil {
  375. if let modal = textFieldViewCachedInfo(textView) {
  376. aDelegate = modal.textViewDelegate
  377. }
  378. }
  379. aDelegate?.textViewDidChange?(textView)
  380. }
  381. @objc public func textViewDidChangeSelection(_ textView: UITextView) {
  382. var aDelegate: UITextViewDelegate? = delegate
  383. if aDelegate == nil {
  384. if let modal = textFieldViewCachedInfo(textView) {
  385. aDelegate = modal.textViewDelegate
  386. }
  387. }
  388. aDelegate?.textViewDidChangeSelection?(textView)
  389. }
  390. @available(iOS 10.0, *)
  391. @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
  392. if delegate == nil {
  393. if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
  394. if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange, UITextItemInteraction) -> Bool)) {
  395. return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? false
  396. }
  397. }
  398. }
  399. return true
  400. }
  401. @available(iOS 10.0, *)
  402. @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
  403. if delegate == nil {
  404. if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
  405. if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange, UITextItemInteraction) -> Bool)) {
  406. return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) ?? false
  407. }
  408. }
  409. }
  410. return true
  411. }
  412. @available(iOS, deprecated: 10.0)
  413. @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
  414. if delegate == nil {
  415. if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
  416. if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) {
  417. return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange) ?? false
  418. }
  419. }
  420. }
  421. return true
  422. }
  423. @available(iOS, deprecated: 10.0)
  424. @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
  425. if delegate == nil {
  426. if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
  427. if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) {
  428. return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange) ?? false
  429. }
  430. }
  431. }
  432. return true
  433. }
  434. #if swift(>=5.7)
  435. @available(iOS 16.0, *)
  436. public func textView(_ aTextView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {
  437. if delegate == nil {
  438. if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
  439. if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSRange, [UIMenuElement]) -> UIMenu?)) {
  440. return unwrapDelegate.textView?(aTextView, editMenuForTextIn: range, suggestedActions: suggestedActions)
  441. }
  442. }
  443. }
  444. return nil
  445. }
  446. @available(iOS 16.0, *)
  447. public func textView(_ aTextView: UITextView, willPresentEditMenuWith animator: UIEditMenuInteractionAnimating) {
  448. var aDelegate: UITextViewDelegate? = delegate
  449. if aDelegate == nil {
  450. if let modal = textFieldViewCachedInfo(aTextView) {
  451. aDelegate = modal.textViewDelegate
  452. }
  453. }
  454. aDelegate?.textView?(aTextView, willPresentEditMenuWith: animator)
  455. }
  456. @available(iOS 16.0, *)
  457. public func textView(_ aTextView: UITextView, willDismissEditMenuWith animator: UIEditMenuInteractionAnimating) {
  458. var aDelegate: UITextViewDelegate? = delegate
  459. if aDelegate == nil {
  460. if let modal = textFieldViewCachedInfo(aTextView) {
  461. aDelegate = modal.textViewDelegate
  462. }
  463. }
  464. aDelegate?.textView?(aTextView, willDismissEditMenuWith: animator)
  465. }
  466. #endif
  467. }