IQKeyboardReturnKeyHandler.swift 22 KB

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