IQKeyboardManager.swift 110 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302
  1. //
  2. // IQKeyboardManager.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 CoreGraphics
  25. import UIKit
  26. import QuartzCore
  27. ///---------------------
  28. /// MARK: IQToolbar tags
  29. ///---------------------
  30. /**
  31. Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more. A generic version of KeyboardManagement. https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
  32. */
  33. @objc public class IQKeyboardManager: NSObject, UIGestureRecognizerDelegate {
  34. /**
  35. Default tag for toolbar with Done button -1002.
  36. */
  37. private static let kIQDoneButtonToolbarTag = -1002
  38. /**
  39. Default tag for toolbar with Previous/Next buttons -1005.
  40. */
  41. private static let kIQPreviousNextButtonToolbarTag = -1005
  42. /**
  43. Invalid point value.
  44. */
  45. private static let kIQCGPointInvalid = CGPoint.init(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude)
  46. ///---------------------------
  47. /// MARK: UIKeyboard handling
  48. ///---------------------------
  49. /**
  50. Enable/disable managing distance between keyboard and textField. Default is YES(Enabled when class loads in `+(void)load` method).
  51. */
  52. @objc public var enable = false {
  53. didSet {
  54. //If not enable, enable it.
  55. if enable == true &&
  56. oldValue == false {
  57. //If keyboard is currently showing. Sending a fake notification for keyboardWillHide to retain view's original position.
  58. if let notification = _kbShowNotification {
  59. keyboardWillShow(notification)
  60. }
  61. showLog("Enabled")
  62. } else if enable == false &&
  63. oldValue == true { //If not disable, desable it.
  64. keyboardWillHide(nil)
  65. showLog("Disabled")
  66. }
  67. }
  68. }
  69. private func privateIsEnabled() -> Bool {
  70. var isEnabled = enable
  71. let enableMode = _textFieldView?.enableMode
  72. if enableMode == .enabled {
  73. isEnabled = true
  74. } else if enableMode == .disabled {
  75. isEnabled = false
  76. } else {
  77. if var textFieldViewController = _textFieldView?.viewContainingController() {
  78. //If it is searchBar textField embedded in Navigation Bar
  79. if _textFieldView?.textFieldSearchBar() != nil, let navController = textFieldViewController as? UINavigationController, let topController = navController.topViewController {
  80. textFieldViewController = topController
  81. }
  82. if isEnabled == false {
  83. //If viewController is kind of enable viewController class, then assuming it's enabled.
  84. for enabledClass in enabledDistanceHandlingClasses {
  85. if textFieldViewController.isKind(of: enabledClass) {
  86. isEnabled = true
  87. break
  88. }
  89. }
  90. }
  91. if isEnabled == true {
  92. //If viewController is kind of disabled viewController class, then assuming it's disabled.
  93. for disabledClass in disabledDistanceHandlingClasses {
  94. if textFieldViewController.isKind(of: disabledClass) {
  95. isEnabled = false
  96. break
  97. }
  98. }
  99. //Special Controllers
  100. if isEnabled == true {
  101. let classNameString = NSStringFromClass(type(of: textFieldViewController.self))
  102. //_UIAlertControllerTextFieldViewController
  103. if classNameString.contains("UIAlertController") && classNameString.hasSuffix("TextFieldViewController") {
  104. isEnabled = false
  105. }
  106. }
  107. }
  108. }
  109. }
  110. return isEnabled
  111. }
  112. /**
  113. To set keyboard distance from textField. can't be less than zero. Default is 10.0.
  114. */
  115. @objc public var keyboardDistanceFromTextField: CGFloat {
  116. set {
  117. _privateKeyboardDistanceFromTextField = max(0, newValue)
  118. showLog("keyboardDistanceFromTextField: \(_privateKeyboardDistanceFromTextField)")
  119. }
  120. get {
  121. return _privateKeyboardDistanceFromTextField
  122. }
  123. }
  124. /**
  125. Boolean to know if keyboard is showing.
  126. */
  127. @objc public var keyboardShowing: Bool {
  128. return _privateIsKeyboardShowing
  129. }
  130. /**
  131. moved distance to the top used to maintain distance between keyboard and textField. Most of the time this will be a positive value.
  132. */
  133. @objc public var movedDistance: CGFloat {
  134. return _privateMovedDistance
  135. }
  136. /**
  137. Will be called then movedDistance will be changed
  138. */
  139. @objc public var movedDistanceChanged: ((CGFloat) -> Void)?
  140. /**
  141. Returns the default singleton instance.
  142. */
  143. @objc public static let shared = IQKeyboardManager()
  144. ///-------------------------
  145. /// MARK: IQToolbar handling
  146. ///-------------------------
  147. /**
  148. Automatic add the IQToolbar functionality. Default is YES.
  149. */
  150. @objc public var enableAutoToolbar = true {
  151. didSet {
  152. privateIsEnableAutoToolbar() ? addToolbarIfRequired() : removeToolbarIfRequired()
  153. let enableToolbar = enableAutoToolbar ? "Yes" : "NO"
  154. showLog("enableAutoToolbar: \(enableToolbar)")
  155. }
  156. }
  157. private func privateIsEnableAutoToolbar() -> Bool {
  158. var enableToolbar = enableAutoToolbar
  159. if var textFieldViewController = _textFieldView?.viewContainingController() {
  160. //If it is searchBar textField embedded in Navigation Bar
  161. if _textFieldView?.textFieldSearchBar() != nil, let navController = textFieldViewController as? UINavigationController, let topController = navController.topViewController {
  162. textFieldViewController = topController
  163. }
  164. if enableToolbar == false {
  165. //If found any toolbar enabled classes then return.
  166. for enabledClass in enabledToolbarClasses {
  167. if textFieldViewController.isKind(of: enabledClass) {
  168. enableToolbar = true
  169. break
  170. }
  171. }
  172. }
  173. if enableToolbar == true {
  174. //If found any toolbar disabled classes then return.
  175. for disabledClass in disabledToolbarClasses {
  176. if textFieldViewController.isKind(of: disabledClass) {
  177. enableToolbar = false
  178. break
  179. }
  180. }
  181. //Special Controllers
  182. if enableToolbar == true {
  183. let classNameString = NSStringFromClass(type(of: textFieldViewController.self))
  184. //_UIAlertControllerTextFieldViewController
  185. if classNameString.contains("UIAlertController") && classNameString.hasSuffix("TextFieldViewController") {
  186. enableToolbar = false
  187. }
  188. }
  189. }
  190. }
  191. return enableToolbar
  192. }
  193. /**
  194. /**
  195. IQAutoToolbarBySubviews: Creates Toolbar according to subview's hirarchy of Textfield's in view.
  196. IQAutoToolbarByTag: Creates Toolbar according to tag property of TextField's.
  197. IQAutoToolbarByPosition: Creates Toolbar according to the y,x position of textField in it's superview coordinate.
  198. Default is IQAutoToolbarBySubviews.
  199. */
  200. AutoToolbar managing behaviour. Default is IQAutoToolbarBySubviews.
  201. */
  202. @objc public var toolbarManageBehaviour = IQAutoToolbarManageBehaviour.bySubviews
  203. /**
  204. If YES, then uses textField's tintColor property for IQToolbar, otherwise tint color is default. Default is NO.
  205. */
  206. @objc public var shouldToolbarUsesTextFieldTintColor = false
  207. /**
  208. This is used for toolbar.tintColor when textfield.keyboardAppearance is UIKeyboardAppearanceDefault. If shouldToolbarUsesTextFieldTintColor is YES then this property is ignored. Default is nil and uses black color.
  209. */
  210. @objc public var toolbarTintColor: UIColor?
  211. /**
  212. This is used for toolbar.barTintColor. Default is nil.
  213. */
  214. @objc public var toolbarBarTintColor: UIColor?
  215. /**
  216. IQPreviousNextDisplayModeDefault: Show NextPrevious when there are more than 1 textField otherwise hide.
  217. IQPreviousNextDisplayModeAlwaysHide: Do not show NextPrevious buttons in any case.
  218. IQPreviousNextDisplayModeAlwaysShow: Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled.
  219. */
  220. @objc public var previousNextDisplayMode = IQPreviousNextDisplayMode.default
  221. /**
  222. Toolbar previous/next/done button icon, If nothing is provided then check toolbarDoneBarButtonItemText to draw done button.
  223. */
  224. @objc public var toolbarPreviousBarButtonItemImage: UIImage?
  225. @objc public var toolbarNextBarButtonItemImage: UIImage?
  226. @objc public var toolbarDoneBarButtonItemImage: UIImage?
  227. /**
  228. Toolbar previous/next/done button text, If nothing is provided then system default 'UIBarButtonSystemItemDone' will be used.
  229. */
  230. @objc public var toolbarPreviousBarButtonItemText: String?
  231. @objc public var toolbarPreviousBarButtonItemAccessibilityLabel: String?
  232. @objc public var toolbarNextBarButtonItemText: String?
  233. @objc public var toolbarNextBarButtonItemAccessibilityLabel: String?
  234. @objc public var toolbarDoneBarButtonItemText: String?
  235. @objc public var toolbarDoneBarButtonItemAccessibilityLabel: String?
  236. /**
  237. If YES, then it add the textField's placeholder text on IQToolbar. Default is YES.
  238. */
  239. @objc public var shouldShowToolbarPlaceholder = true
  240. /**
  241. Placeholder Font. Default is nil.
  242. */
  243. @objc public var placeholderFont: UIFont?
  244. /**
  245. Placeholder Color. Default is nil. Which means lightGray
  246. */
  247. @objc public var placeholderColor: UIColor?
  248. /**
  249. Placeholder Button Color when it's treated as button. Default is nil.
  250. */
  251. @objc public var placeholderButtonColor: UIColor?
  252. ///--------------------------
  253. /// MARK: UITextView handling
  254. ///--------------------------
  255. /** used to adjust contentInset of UITextView. */
  256. private var startingTextViewContentInsets = UIEdgeInsets()
  257. /** used to adjust scrollIndicatorInsets of UITextView. */
  258. private var startingTextViewScrollIndicatorInsets = UIEdgeInsets()
  259. /** used with textView to detect a textFieldView contentInset is changed or not. (Bug ID: #92)*/
  260. private var isTextViewContentInsetChanged = false
  261. ///---------------------------------------
  262. /// MARK: UIKeyboard appearance overriding
  263. ///---------------------------------------
  264. /**
  265. Override the keyboardAppearance for all textField/textView. Default is NO.
  266. */
  267. @objc public var overrideKeyboardAppearance = false
  268. /**
  269. If overrideKeyboardAppearance is YES, then all the textField keyboardAppearance is set using this property.
  270. */
  271. @objc public var keyboardAppearance = UIKeyboardAppearance.default
  272. ///-----------------------------------------------------------
  273. /// MARK: UITextField/UITextView Next/Previous/Resign handling
  274. ///-----------------------------------------------------------
  275. /**
  276. Resigns Keyboard on touching outside of UITextField/View. Default is NO.
  277. */
  278. @objc public var shouldResignOnTouchOutside = false {
  279. didSet {
  280. resignFirstResponderGesture.isEnabled = privateShouldResignOnTouchOutside()
  281. let shouldResign = shouldResignOnTouchOutside ? "Yes" : "NO"
  282. showLog("shouldResignOnTouchOutside: \(shouldResign)")
  283. }
  284. }
  285. /** 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 */
  286. @objc lazy public var resignFirstResponderGesture: UITapGestureRecognizer = {
  287. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapRecognized(_:)))
  288. tapGesture.cancelsTouchesInView = false
  289. tapGesture.delegate = self
  290. return tapGesture
  291. }()
  292. /*******************************************/
  293. private func privateShouldResignOnTouchOutside() -> Bool {
  294. var shouldResign = shouldResignOnTouchOutside
  295. let enableMode = _textFieldView?.shouldResignOnTouchOutsideMode
  296. if enableMode == .enabled {
  297. shouldResign = true
  298. } else if enableMode == .disabled {
  299. shouldResign = false
  300. } else {
  301. if var textFieldViewController = _textFieldView?.viewContainingController() {
  302. //If it is searchBar textField embedded in Navigation Bar
  303. if _textFieldView?.textFieldSearchBar() != nil, let navController = textFieldViewController as? UINavigationController, let topController = navController.topViewController {
  304. textFieldViewController = topController
  305. }
  306. if shouldResign == false {
  307. //If viewController is kind of enable viewController class, then assuming shouldResignOnTouchOutside is enabled.
  308. for enabledClass in enabledTouchResignedClasses {
  309. if textFieldViewController.isKind(of: enabledClass) {
  310. shouldResign = true
  311. break
  312. }
  313. }
  314. }
  315. if shouldResign == true {
  316. //If viewController is kind of disable viewController class, then assuming shouldResignOnTouchOutside is disable.
  317. for disabledClass in disabledTouchResignedClasses {
  318. if textFieldViewController.isKind(of: disabledClass) {
  319. shouldResign = false
  320. break
  321. }
  322. }
  323. //Special Controllers
  324. if shouldResign == true {
  325. let classNameString = NSStringFromClass(type(of: textFieldViewController.self))
  326. //_UIAlertControllerTextFieldViewController
  327. if classNameString.contains("UIAlertController") && classNameString.hasSuffix("TextFieldViewController") {
  328. shouldResign = false
  329. }
  330. }
  331. }
  332. }
  333. }
  334. return shouldResign
  335. }
  336. /**
  337. Resigns currently first responder field.
  338. */
  339. @objc @discardableResult public func resignFirstResponder() -> Bool {
  340. if let textFieldRetain = _textFieldView {
  341. //Resigning first responder
  342. let isResignFirstResponder = textFieldRetain.resignFirstResponder()
  343. // If it refuses then becoming it as first responder again. (Bug ID: #96)
  344. if isResignFirstResponder == false {
  345. //If it refuses to resign then becoming it first responder again for getting notifications callback.
  346. textFieldRetain.becomeFirstResponder()
  347. showLog("Refuses to resign first responder: \(textFieldRetain)")
  348. }
  349. return isResignFirstResponder
  350. }
  351. return false
  352. }
  353. /**
  354. Returns YES if can navigate to previous responder textField/textView, otherwise NO.
  355. */
  356. @objc public var canGoPrevious: Bool {
  357. //Getting all responder view's.
  358. if let textFields = responderViews() {
  359. if let textFieldRetain = _textFieldView {
  360. //Getting index of current textField.
  361. if let index = textFields.firstIndex(of: textFieldRetain) {
  362. //If it is not first textField. then it's previous object canBecomeFirstResponder.
  363. if index > 0 {
  364. return true
  365. }
  366. }
  367. }
  368. }
  369. return false
  370. }
  371. /**
  372. Returns YES if can navigate to next responder textField/textView, otherwise NO.
  373. */
  374. @objc public var canGoNext: Bool {
  375. //Getting all responder view's.
  376. if let textFields = responderViews() {
  377. if let textFieldRetain = _textFieldView {
  378. //Getting index of current textField.
  379. if let index = textFields.firstIndex(of: textFieldRetain) {
  380. //If it is not first textField. then it's previous object canBecomeFirstResponder.
  381. if index < textFields.count-1 {
  382. return true
  383. }
  384. }
  385. }
  386. }
  387. return false
  388. }
  389. /**
  390. Navigate to previous responder textField/textView.
  391. */
  392. @objc @discardableResult public func goPrevious() -> Bool {
  393. //Getting all responder view's.
  394. if let textFieldRetain = _textFieldView {
  395. if let textFields = responderViews() {
  396. //Getting index of current textField.
  397. if let index = textFields.firstIndex(of: textFieldRetain) {
  398. //If it is not first textField. then it's previous object becomeFirstResponder.
  399. if index > 0 {
  400. let nextTextField = textFields[index-1]
  401. let isAcceptAsFirstResponder = nextTextField.becomeFirstResponder()
  402. // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
  403. if isAcceptAsFirstResponder == false {
  404. //If next field refuses to become first responder then restoring old textField as first responder.
  405. textFieldRetain.becomeFirstResponder()
  406. showLog("Refuses to become first responder: \(nextTextField)")
  407. }
  408. return isAcceptAsFirstResponder
  409. }
  410. }
  411. }
  412. }
  413. return false
  414. }
  415. /**
  416. Navigate to next responder textField/textView.
  417. */
  418. @objc @discardableResult public func goNext() -> Bool {
  419. //Getting all responder view's.
  420. if let textFieldRetain = _textFieldView {
  421. if let textFields = responderViews() {
  422. //Getting index of current textField.
  423. if let index = textFields.firstIndex(of: textFieldRetain) {
  424. //If it is not last textField. then it's next object becomeFirstResponder.
  425. if index < textFields.count-1 {
  426. let nextTextField = textFields[index+1]
  427. let isAcceptAsFirstResponder = nextTextField.becomeFirstResponder()
  428. // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
  429. if isAcceptAsFirstResponder == false {
  430. //If next field refuses to become first responder then restoring old textField as first responder.
  431. textFieldRetain.becomeFirstResponder()
  432. showLog("Refuses to become first responder: \(nextTextField)")
  433. }
  434. return isAcceptAsFirstResponder
  435. }
  436. }
  437. }
  438. }
  439. return false
  440. }
  441. /** previousAction. */
  442. @objc internal func previousAction (_ barButton: IQBarButtonItem) {
  443. //If user wants to play input Click sound.
  444. if shouldPlayInputClicks == true {
  445. //Play Input Click Sound.
  446. UIDevice.current.playInputClick()
  447. }
  448. if canGoPrevious == true {
  449. if let textFieldRetain = _textFieldView {
  450. let isAcceptAsFirstResponder = goPrevious()
  451. var invocation = barButton.invocation
  452. var sender = textFieldRetain
  453. //Handling search bar special case
  454. do {
  455. if let searchBar = textFieldRetain.textFieldSearchBar() {
  456. invocation = searchBar.keyboardToolbar.previousBarButton.invocation
  457. sender = searchBar
  458. }
  459. }
  460. if isAcceptAsFirstResponder {
  461. invocation?.invoke(from: sender)
  462. }
  463. }
  464. }
  465. }
  466. /** nextAction. */
  467. @objc internal func nextAction (_ barButton: IQBarButtonItem) {
  468. //If user wants to play input Click sound.
  469. if shouldPlayInputClicks == true {
  470. //Play Input Click Sound.
  471. UIDevice.current.playInputClick()
  472. }
  473. if canGoNext == true {
  474. if let textFieldRetain = _textFieldView {
  475. let isAcceptAsFirstResponder = goNext()
  476. var invocation = barButton.invocation
  477. var sender = textFieldRetain
  478. //Handling search bar special case
  479. do {
  480. if let searchBar = textFieldRetain.textFieldSearchBar() {
  481. invocation = searchBar.keyboardToolbar.nextBarButton.invocation
  482. sender = searchBar
  483. }
  484. }
  485. if isAcceptAsFirstResponder {
  486. invocation?.invoke(from: sender)
  487. }
  488. }
  489. }
  490. }
  491. /** doneAction. Resigning current textField. */
  492. @objc internal func doneAction (_ barButton: IQBarButtonItem) {
  493. //If user wants to play input Click sound.
  494. if shouldPlayInputClicks == true {
  495. //Play Input Click Sound.
  496. UIDevice.current.playInputClick()
  497. }
  498. if let textFieldRetain = _textFieldView {
  499. //Resign textFieldView.
  500. let isResignedFirstResponder = resignFirstResponder()
  501. var invocation = barButton.invocation
  502. var sender = textFieldRetain
  503. //Handling search bar special case
  504. do {
  505. if let searchBar = textFieldRetain.textFieldSearchBar() {
  506. invocation = searchBar.keyboardToolbar.doneBarButton.invocation
  507. sender = searchBar
  508. }
  509. }
  510. if isResignedFirstResponder {
  511. invocation?.invoke(from: sender)
  512. }
  513. }
  514. }
  515. /** Resigning on tap gesture. (Enhancement ID: #14)*/
  516. @objc internal func tapRecognized(_ gesture: UITapGestureRecognizer) {
  517. if gesture.state == .ended {
  518. //Resigning currently responder textField.
  519. resignFirstResponder()
  520. }
  521. }
  522. /** 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. */
  523. @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  524. return false
  525. }
  526. /** To not detect touch events in a subclass of UIControl, these may have added their own selector for specific work */
  527. @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
  528. // Should not recognize gesture if the clicked view is either UIControl or UINavigationBar(<Back button etc...) (Bug ID: #145)
  529. for ignoreClass in touchResignedGestureIgnoreClasses {
  530. if touch.view?.isKind(of: ignoreClass) == true {
  531. return false
  532. }
  533. }
  534. return true
  535. }
  536. ///-----------------------
  537. /// MARK: UISound handling
  538. ///-----------------------
  539. /**
  540. If YES, then it plays inputClick sound on next/previous/done click.
  541. */
  542. @objc public var shouldPlayInputClicks = true
  543. ///---------------------------
  544. /// MARK: UIAnimation handling
  545. ///---------------------------
  546. /**
  547. If YES, then calls 'setNeedsLayout' and 'layoutIfNeeded' on any frame update of to viewController's view.
  548. */
  549. @objc public var layoutIfNeededOnUpdate = false
  550. ///------------------------------------
  551. /// MARK: Class Level disabling methods
  552. ///------------------------------------
  553. /**
  554. Disable distance handling within the scope of disabled distance handling viewControllers classes. Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController.
  555. */
  556. @objc public var disabledDistanceHandlingClasses = [UIViewController.Type]()
  557. /**
  558. Enable distance handling within the scope of enabled distance handling viewControllers classes. Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController. If same Class is added in disabledDistanceHandlingClasses list, then enabledDistanceHandlingClasses will be ignored.
  559. */
  560. @objc public var enabledDistanceHandlingClasses = [UIViewController.Type]()
  561. /**
  562. Disable automatic toolbar creation within the scope of disabled toolbar viewControllers classes. Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController.
  563. */
  564. @objc public var disabledToolbarClasses = [UIViewController.Type]()
  565. /**
  566. Enable automatic toolbar creation within the scope of enabled toolbar viewControllers classes. Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController. If same Class is added in disabledToolbarClasses list, then enabledToolbarClasses will be ignore.
  567. */
  568. @objc public var enabledToolbarClasses = [UIViewController.Type]()
  569. /**
  570. Allowed subclasses of UIView to add all inner textField, this will allow to navigate between textField contains in different superview. Class should be kind of UIView.
  571. */
  572. @objc public var toolbarPreviousNextAllowedClasses = [UIView.Type]()
  573. /**
  574. Disabled classes to ignore 'shouldResignOnTouchOutside' property, Class should be kind of UIViewController.
  575. */
  576. @objc public var disabledTouchResignedClasses = [UIViewController.Type]()
  577. /**
  578. Enabled classes to forcefully enable 'shouldResignOnTouchOutsite' property. Class should be kind of UIViewController. If same Class is added in disabledTouchResignedClasses list, then enabledTouchResignedClasses will be ignored.
  579. */
  580. @objc public var enabledTouchResignedClasses = [UIViewController.Type]()
  581. /**
  582. if shouldResignOnTouchOutside is enabled then you can customise the behaviour to not recognise gesture touches on some specific view subclasses. Class should be kind of UIView. Default is [UIControl, UINavigationBar]
  583. */
  584. @objc public var touchResignedGestureIgnoreClasses = [UIView.Type]()
  585. ///----------------------------------
  586. /// MARK: Third Party Library support
  587. /// Add TextField/TextView Notifications customised Notifications. For example while using YYTextView https://github.com/ibireme/YYText
  588. ///----------------------------------
  589. /**
  590. Add/Remove customised Notification for third party customised TextField/TextView. Please be aware that the Notification object must be idential to UITextField/UITextView Notification objects and customised TextField/TextView support must be idential to UITextField/UITextView.
  591. @param didBeginEditingNotificationName This should be identical to UITextViewTextDidBeginEditingNotification
  592. @param didEndEditingNotificationName This should be identical to UITextViewTextDidEndEditingNotification
  593. */
  594. @objc public func registerTextFieldViewClass(_ aClass: UIView.Type, didBeginEditingNotificationName: String, didEndEditingNotificationName: String) {
  595. NotificationCenter.default.addObserver(self, selector: #selector(self.textFieldViewDidBeginEditing(_:)), name: Notification.Name(rawValue: didBeginEditingNotificationName), object: nil)
  596. NotificationCenter.default.addObserver(self, selector: #selector(self.textFieldViewDidEndEditing(_:)), name: Notification.Name(rawValue: didEndEditingNotificationName), object: nil)
  597. }
  598. @objc public func unregisterTextFieldViewClass(_ aClass: UIView.Type, didBeginEditingNotificationName: String, didEndEditingNotificationName: String) {
  599. NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: didBeginEditingNotificationName), object: nil)
  600. NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: didEndEditingNotificationName), object: nil)
  601. }
  602. /**************************************************************************************/
  603. ///------------------------
  604. /// MARK: Private variables
  605. ///------------------------
  606. /*******************************************/
  607. /** To save UITextField/UITextView object voa textField/textView notifications. */
  608. private weak var _textFieldView: UIView?
  609. /** To save rootViewController.view.frame.origin. */
  610. private var _topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid
  611. /** To overcome with popGestureRecognizer issue Bug ID: #1361 */
  612. private weak var _rootViewControllerWhilePopGestureRecognizerActive: UIViewController?
  613. private var _topViewBeginOriginWhilePopGestureRecognizerActive = IQKeyboardManager.kIQCGPointInvalid
  614. /** To save rootViewController */
  615. private weak var _rootViewController: UIViewController?
  616. /*******************************************/
  617. /** Variable to save lastScrollView that was scrolled. */
  618. private weak var _lastScrollView: UIScrollView?
  619. /** LastScrollView's initial contentOffset. */
  620. private var _startingContentOffset = CGPoint.zero
  621. /** LastScrollView's initial scrollIndicatorInsets. */
  622. private var _startingScrollIndicatorInsets = UIEdgeInsets()
  623. /** LastScrollView's initial contentInsets. */
  624. private var _startingContentInsets = UIEdgeInsets()
  625. /*******************************************/
  626. /** To save keyboardWillShowNotification. Needed for enable keyboard functionality. */
  627. private var _kbShowNotification: Notification?
  628. /** To save keyboard rame. */
  629. private var _kbFrame = CGRect.zero
  630. /** To save keyboard animation duration. */
  631. private var _animationDuration: TimeInterval = 0.25
  632. /** To mimic the keyboard animation */
  633. #if swift(>=4.2)
  634. private var _animationCurve: UIView.AnimationOptions = .curveEaseOut
  635. #else
  636. private var _animationCurve: UIViewAnimationOptions = .curveEaseOut
  637. #endif
  638. /*******************************************/
  639. /** Boolean to maintain keyboard is showing or it is hide. To solve rootViewController.view.frame calculations. */
  640. private var _privateIsKeyboardShowing = false
  641. private var _privateMovedDistance: CGFloat = 0.0 {
  642. didSet {
  643. movedDistanceChanged?(_privateMovedDistance)
  644. }
  645. }
  646. /** To use with keyboardDistanceFromTextField. */
  647. private var _privateKeyboardDistanceFromTextField: CGFloat = 10.0
  648. /** To know if we have any pending request to adjust view position. */
  649. private var _privateHasPendingAdjustRequest = false
  650. /**************************************************************************************/
  651. ///--------------------------------------
  652. /// MARK: Initialization/Deinitialization
  653. ///--------------------------------------
  654. /* Singleton Object Initialization. */
  655. override init() {
  656. super.init()
  657. self.registerAllNotifications()
  658. //Creating gesture for @shouldResignOnTouchOutside. (Enhancement ID: #14)
  659. resignFirstResponderGesture.isEnabled = shouldResignOnTouchOutside
  660. //Loading IQToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay (Bug ID: #550)
  661. //If you experience exception breakpoint issue at below line then try these solutions https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator
  662. let textField = UITextField()
  663. textField.addDoneOnKeyboardWithTarget(nil, action: #selector(self.doneAction(_:)))
  664. textField.addPreviousNextDoneOnKeyboardWithTarget(nil, previousAction: #selector(self.previousAction(_:)), nextAction: #selector(self.nextAction(_:)), doneAction: #selector(self.doneAction(_:)))
  665. disabledDistanceHandlingClasses.append(UITableViewController.self)
  666. disabledDistanceHandlingClasses.append(UIAlertController.self)
  667. disabledToolbarClasses.append(UIAlertController.self)
  668. disabledTouchResignedClasses.append(UIAlertController.self)
  669. toolbarPreviousNextAllowedClasses.append(UITableView.self)
  670. toolbarPreviousNextAllowedClasses.append(UICollectionView.self)
  671. toolbarPreviousNextAllowedClasses.append(IQPreviousNextView.self)
  672. touchResignedGestureIgnoreClasses.append(UIControl.self)
  673. touchResignedGestureIgnoreClasses.append(UINavigationBar.self)
  674. }
  675. /** Override +load method to enable KeyboardManager when class loader load IQKeyboardManager. Enabling when app starts (No need to write any code) */
  676. /** It doesn't work from Swift 1.2 */
  677. // override public class func load() {
  678. // super.load()
  679. //
  680. // //Enabling IQKeyboardManager.
  681. // IQKeyboardManager.shared.enable = true
  682. // }
  683. deinit {
  684. // Disable the keyboard manager.
  685. enable = false
  686. //Removing notification observers on dealloc.
  687. NotificationCenter.default.removeObserver(self)
  688. }
  689. /** Getting keyWindow. */
  690. private func keyWindow() -> UIWindow? {
  691. if let keyWindow = _textFieldView?.window {
  692. return keyWindow
  693. } else {
  694. struct Static {
  695. /** @abstract Save keyWindow object for reuse.
  696. @discussion Sometimes [[UIApplication sharedApplication] keyWindow] is returning nil between the app. */
  697. static weak var keyWindow: UIWindow?
  698. }
  699. var originalKeyWindow : UIWindow? = nil
  700. #if swift(>=5.1)
  701. if #available(iOS 13, *) {
  702. originalKeyWindow = UIApplication.shared.connectedScenes
  703. .compactMap { $0 as? UIWindowScene }
  704. .flatMap { $0.windows }
  705. .first(where: { $0.isKeyWindow })
  706. } else {
  707. originalKeyWindow = UIApplication.shared.keyWindow
  708. }
  709. #else
  710. originalKeyWindow = UIApplication.shared.keyWindow
  711. #endif
  712. //If original key window is not nil and the cached keywindow is also not original keywindow then changing keywindow.
  713. if let originalKeyWindow = originalKeyWindow {
  714. Static.keyWindow = originalKeyWindow
  715. }
  716. //Return KeyWindow
  717. return Static.keyWindow
  718. }
  719. }
  720. ///-----------------------
  721. /// MARK: Helper Functions
  722. ///-----------------------
  723. private func optimizedAdjustPosition() {
  724. if _privateHasPendingAdjustRequest == false {
  725. _privateHasPendingAdjustRequest = true
  726. OperationQueue.main.addOperation {
  727. self.adjustPosition()
  728. self._privateHasPendingAdjustRequest = false
  729. }
  730. }
  731. }
  732. /* Adjusting RootViewController's frame according to interface orientation. */
  733. private func adjustPosition() {
  734. // We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11)
  735. if _privateHasPendingAdjustRequest == true,
  736. let textFieldView = _textFieldView,
  737. let rootController = textFieldView.parentContainerViewController(),
  738. let window = keyWindow(),
  739. let textFieldViewRectInWindow = textFieldView.superview?.convert(textFieldView.frame, to: window),
  740. let textFieldViewRectInRootSuperview = textFieldView.superview?.convert(textFieldView.frame, to: rootController.view?.superview) {
  741. let startTime = CACurrentMediaTime()
  742. showLog("****** \(#function) started ******", indentation: 1)
  743. // Getting RootViewOrigin.
  744. var rootViewOrigin = rootController.view.frame.origin
  745. //Maintain keyboardDistanceFromTextField
  746. var specialKeyboardDistanceFromTextField = textFieldView.keyboardDistanceFromTextField
  747. if let searchBar = textFieldView.textFieldSearchBar() {
  748. specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField
  749. }
  750. let newKeyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance) ? keyboardDistanceFromTextField : specialKeyboardDistanceFromTextField
  751. var kbSize = _kbFrame.size
  752. do {
  753. var kbFrame = _kbFrame
  754. kbFrame.origin.y -= newKeyboardDistanceFromTextField
  755. kbFrame.size.height += newKeyboardDistanceFromTextField
  756. //Calculating actual keyboard covered size respect to window, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
  757. let intersectRect = kbFrame.intersection(window.frame)
  758. if intersectRect.isNull {
  759. kbSize = CGSize(width: kbFrame.size.width, height: 0)
  760. } else {
  761. kbSize = intersectRect.size
  762. }
  763. }
  764. let statusBarHeight : CGFloat
  765. #if swift(>=5.1)
  766. if #available(iOS 13, *) {
  767. statusBarHeight = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
  768. } else {
  769. statusBarHeight = UIApplication.shared.statusBarFrame.height
  770. }
  771. #else
  772. statusBarHeight = UIApplication.shared.statusBarFrame.height
  773. #endif
  774. let navigationBarAreaHeight: CGFloat = statusBarHeight + ( rootController.navigationController?.navigationBar.frame.height ?? 0)
  775. let layoutAreaHeight: CGFloat = rootController.view.layoutMargins.bottom
  776. let topLayoutGuide: CGFloat = max(navigationBarAreaHeight, layoutAreaHeight) + 5
  777. let bottomLayoutGuide: CGFloat = (textFieldView is UIScrollView && textFieldView.responds(to: #selector(getter: UITextView.isEditable))) ? 0 : rootController.view.layoutMargins.bottom //Validation of textView for case where there is a tab bar at the bottom or running on iPhone X and textView is at the bottom.
  778. // Move positive = textField is hidden.
  779. // Move negative = textField is showing.
  780. // Calculating move position.
  781. var move: CGFloat = min(textFieldViewRectInRootSuperview.minY-(topLayoutGuide), textFieldViewRectInWindow.maxY-(window.frame.height-kbSize.height)+bottomLayoutGuide)
  782. showLog("Need to move: \(move)")
  783. var superScrollView: UIScrollView?
  784. var superView = textFieldView.superviewOfClassType(UIScrollView.self) as? UIScrollView
  785. //Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
  786. while let view = superView {
  787. if view.isScrollEnabled && view.shouldIgnoreScrollingAdjustment == false {
  788. superScrollView = view
  789. break
  790. } else {
  791. // Getting it's superScrollView. // (Enhancement ID: #21, #24)
  792. superView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
  793. }
  794. }
  795. //If there was a lastScrollView. // (Bug ID: #34)
  796. if let lastScrollView = _lastScrollView {
  797. //If we can't find current superScrollView, then setting lastScrollView to it's original form.
  798. if superScrollView == nil {
  799. if lastScrollView.contentInset != self._startingContentInsets {
  800. showLog("Restoring contentInset to: \(_startingContentInsets)")
  801. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  802. lastScrollView.contentInset = self._startingContentInsets
  803. lastScrollView.scrollIndicatorInsets = self._startingScrollIndicatorInsets
  804. })
  805. }
  806. if lastScrollView.shouldRestoreScrollViewContentOffset == true && lastScrollView.contentOffset.equalTo(_startingContentOffset) == false {
  807. showLog("Restoring contentOffset to: \(_startingContentOffset)")
  808. var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
  809. if #available(iOS 9, *) {
  810. animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil
  811. }
  812. if animatedContentOffset {
  813. lastScrollView.setContentOffset(_startingContentOffset, animated: UIView.areAnimationsEnabled)
  814. } else {
  815. lastScrollView.contentOffset = _startingContentOffset
  816. }
  817. }
  818. _startingContentInsets = UIEdgeInsets()
  819. _startingScrollIndicatorInsets = UIEdgeInsets()
  820. _startingContentOffset = CGPoint.zero
  821. _lastScrollView = nil
  822. } else if superScrollView != lastScrollView { //If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
  823. if lastScrollView.contentInset != self._startingContentInsets {
  824. showLog("Restoring contentInset to: \(_startingContentInsets)")
  825. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  826. lastScrollView.contentInset = self._startingContentInsets
  827. lastScrollView.scrollIndicatorInsets = self._startingScrollIndicatorInsets
  828. })
  829. }
  830. if lastScrollView.shouldRestoreScrollViewContentOffset == true && lastScrollView.contentOffset.equalTo(_startingContentOffset) == false {
  831. showLog("Restoring contentOffset to: \(_startingContentOffset)")
  832. var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
  833. if #available(iOS 9, *) {
  834. animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil
  835. }
  836. if animatedContentOffset {
  837. lastScrollView.setContentOffset(_startingContentOffset, animated: UIView.areAnimationsEnabled)
  838. } else {
  839. lastScrollView.contentOffset = _startingContentOffset
  840. }
  841. }
  842. _lastScrollView = superScrollView
  843. if let scrollView = superScrollView {
  844. _startingContentInsets = scrollView.contentInset
  845. _startingContentOffset = scrollView.contentOffset
  846. #if swift(>=5.1)
  847. if #available(iOS 11.1, *) {
  848. _startingScrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets
  849. } else {
  850. _startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
  851. }
  852. #else
  853. _startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
  854. #endif
  855. }
  856. showLog("Saving ScrollView New contentInset: \(_startingContentInsets) and contentOffset: \(_startingContentOffset)")
  857. }
  858. //Else the case where superScrollView == lastScrollView means we are on same scrollView after switching to different textField. So doing nothing, going ahead
  859. } else if let unwrappedSuperScrollView = superScrollView { //If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
  860. _lastScrollView = unwrappedSuperScrollView
  861. _startingContentInsets = unwrappedSuperScrollView.contentInset
  862. _startingContentOffset = unwrappedSuperScrollView.contentOffset
  863. #if swift(>=5.1)
  864. if #available(iOS 11.1, *) {
  865. _startingScrollIndicatorInsets = unwrappedSuperScrollView.verticalScrollIndicatorInsets
  866. } else {
  867. _startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
  868. }
  869. #else
  870. _startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
  871. #endif
  872. showLog("Saving ScrollView contentInset: \(_startingContentInsets) and contentOffset: \(_startingContentOffset)")
  873. }
  874. // Special case for ScrollView.
  875. // If we found lastScrollView then setting it's contentOffset to show textField.
  876. if let lastScrollView = _lastScrollView {
  877. //Saving
  878. var lastView = textFieldView
  879. var superScrollView = _lastScrollView
  880. while let scrollView = superScrollView {
  881. var shouldContinue = false
  882. if move > 0 {
  883. shouldContinue = move > (-scrollView.contentOffset.y - scrollView.contentInset.top)
  884. } else if let tableView = scrollView.superviewOfClassType(UITableView.self) as? UITableView {
  885. shouldContinue = scrollView.contentOffset.y > 0
  886. if shouldContinue == true, let tableCell = textFieldView.superviewOfClassType(UITableViewCell.self) as? UITableViewCell, let indexPath = tableView.indexPath(for: tableCell), let previousIndexPath = tableView.previousIndexPath(of: indexPath) {
  887. let previousCellRect = tableView.rectForRow(at: previousIndexPath)
  888. if previousCellRect.isEmpty == false {
  889. let previousCellRectInRootSuperview = tableView.convert(previousCellRect, to: rootController.view.superview)
  890. move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
  891. }
  892. }
  893. } else if let collectionView = scrollView.superviewOfClassType(UICollectionView.self) as? UICollectionView {
  894. shouldContinue = scrollView.contentOffset.y > 0
  895. if shouldContinue == true, let collectionCell = textFieldView.superviewOfClassType(UICollectionViewCell.self) as? UICollectionViewCell, let indexPath = collectionView.indexPath(for: collectionCell), let previousIndexPath = collectionView.previousIndexPath(of: indexPath), let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) {
  896. let previousCellRect = attributes.frame
  897. if previousCellRect.isEmpty == false {
  898. let previousCellRectInRootSuperview = collectionView.convert(previousCellRect, to: rootController.view.superview)
  899. move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
  900. }
  901. }
  902. } else {
  903. shouldContinue = textFieldViewRectInRootSuperview.origin.y < topLayoutGuide
  904. if shouldContinue {
  905. move = min(0, textFieldViewRectInRootSuperview.origin.y - topLayoutGuide)
  906. }
  907. }
  908. //Looping in upper hierarchy until we don't found any scrollView in it's upper hirarchy till UIWindow object.
  909. if shouldContinue {
  910. var tempScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView
  911. var nextScrollView: UIScrollView?
  912. while let view = tempScrollView {
  913. if view.isScrollEnabled && view.shouldIgnoreScrollingAdjustment == false {
  914. nextScrollView = view
  915. break
  916. } else {
  917. tempScrollView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
  918. }
  919. }
  920. //Getting lastViewRect.
  921. if let lastViewRect = lastView.superview?.convert(lastView.frame, to: scrollView) {
  922. //Calculating the expected Y offset from move and scrollView's contentOffset.
  923. var shouldOffsetY = scrollView.contentOffset.y - min(scrollView.contentOffset.y, -move)
  924. //Rearranging the expected Y offset according to the view.
  925. shouldOffsetY = min(shouldOffsetY, lastViewRect.origin.y)
  926. //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
  927. //nextScrollView == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.)
  928. //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
  929. //shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
  930. if (textFieldView is UIScrollView && textFieldView.responds(to: #selector(getter: UITextView.isEditable))) &&
  931. nextScrollView == nil &&
  932. shouldOffsetY >= 0 {
  933. // Converting Rectangle according to window bounds.
  934. if let currentTextFieldViewRect = textFieldView.superview?.convert(textFieldView.frame, to: window) {
  935. //Calculating expected fix distance which needs to be managed from navigation bar
  936. let expectedFixDistance = currentTextFieldViewRect.minY - topLayoutGuide
  937. //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)
  938. shouldOffsetY = min(shouldOffsetY, scrollView.contentOffset.y + expectedFixDistance)
  939. //Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic.
  940. move = 0
  941. } else {
  942. //Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
  943. move -= (shouldOffsetY-scrollView.contentOffset.y)
  944. }
  945. } else {
  946. //Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
  947. move -= (shouldOffsetY-scrollView.contentOffset.y)
  948. }
  949. let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: shouldOffsetY)
  950. if scrollView.contentOffset.equalTo(newContentOffset) == false {
  951. showLog("old contentOffset: \(scrollView.contentOffset) new contentOffset: \(newContentOffset)")
  952. self.showLog("Remaining Move: \(move)")
  953. //Getting problem while using `setContentOffset:animated:`, So I used animation API.
  954. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  955. var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
  956. if #available(iOS 9, *) {
  957. animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil
  958. }
  959. if animatedContentOffset {
  960. scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
  961. } else {
  962. scrollView.contentOffset = newContentOffset
  963. }
  964. }) { _ in
  965. if scrollView is UITableView || scrollView is UICollectionView {
  966. //This will update the next/previous states
  967. self.addToolbarIfRequired()
  968. }
  969. }
  970. }
  971. }
  972. // Getting next lastView & superScrollView.
  973. lastView = scrollView
  974. superScrollView = nextScrollView
  975. } else {
  976. move = 0
  977. break
  978. }
  979. }
  980. //Updating contentInset
  981. if let lastScrollViewRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window),
  982. lastScrollView.shouldIgnoreContentInsetAdjustment == false {
  983. var bottomInset: CGFloat = (kbSize.height)-(window.frame.height-lastScrollViewRect.maxY)
  984. var bottomScrollIndicatorInset = bottomInset - newKeyboardDistanceFromTextField
  985. // Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view.
  986. bottomInset = max(_startingContentInsets.bottom, bottomInset)
  987. bottomScrollIndicatorInset = max(_startingScrollIndicatorInsets.bottom, bottomScrollIndicatorInset)
  988. #if swift(>=4.0)
  989. if #available(iOS 11, *) {
  990. bottomInset -= lastScrollView.safeAreaInsets.bottom
  991. bottomScrollIndicatorInset -= lastScrollView.safeAreaInsets.bottom
  992. }
  993. #endif
  994. var movedInsets = lastScrollView.contentInset
  995. movedInsets.bottom = bottomInset
  996. if lastScrollView.contentInset != movedInsets {
  997. showLog("old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)")
  998. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  999. lastScrollView.contentInset = movedInsets
  1000. var newScrollIndicatorInset : UIEdgeInsets
  1001. #if swift(>=5.1)
  1002. if #available(iOS 11.1, *) {
  1003. newScrollIndicatorInset = lastScrollView.verticalScrollIndicatorInsets
  1004. } else {
  1005. newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
  1006. }
  1007. #else
  1008. newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
  1009. #endif
  1010. newScrollIndicatorInset.bottom = bottomScrollIndicatorInset
  1011. lastScrollView.scrollIndicatorInsets = newScrollIndicatorInset
  1012. })
  1013. }
  1014. }
  1015. }
  1016. //Going ahead. No else if.
  1017. //Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen)
  1018. //_lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
  1019. //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
  1020. if let textView = textFieldView as? UIScrollView, textView.isScrollEnabled, textFieldView.responds(to: #selector(getter: UITextView.isEditable)) {
  1021. // CGRect rootSuperViewFrameInWindow = [_rootViewController.view.superview convertRect:_rootViewController.view.superview.bounds toView:keyWindow];
  1022. //
  1023. // CGFloat keyboardOverlapping = CGRectGetMaxY(rootSuperViewFrameInWindow) - keyboardYPosition;
  1024. //
  1025. // CGFloat textViewHeight = MIN(CGRectGetHeight(_textFieldView.frame), (CGRectGetHeight(rootSuperViewFrameInWindow)-topLayoutGuide-keyboardOverlapping));
  1026. let keyboardYPosition = window.frame.height - (kbSize.height-newKeyboardDistanceFromTextField)
  1027. var rootSuperViewFrameInWindow = window.frame
  1028. if let rootSuperview = rootController.view.superview {
  1029. rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window)
  1030. }
  1031. let keyboardOverlapping = rootSuperViewFrameInWindow.maxY - keyboardYPosition
  1032. let textViewHeight = min(textView.frame.height, rootSuperViewFrameInWindow.height-topLayoutGuide-keyboardOverlapping)
  1033. if textView.frame.size.height-textView.contentInset.bottom>textViewHeight {
  1034. //_isTextViewContentInsetChanged, If frame is not change by library in past, then saving user textView properties (Bug ID: #92)
  1035. if self.isTextViewContentInsetChanged == false {
  1036. self.startingTextViewContentInsets = textView.contentInset
  1037. #if swift(>=5.1)
  1038. if #available(iOS 11.1, *) {
  1039. self.startingTextViewScrollIndicatorInsets = textView.verticalScrollIndicatorInsets
  1040. } else {
  1041. self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
  1042. }
  1043. #else
  1044. self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
  1045. #endif
  1046. }
  1047. self.isTextViewContentInsetChanged = true
  1048. var newContentInset = textView.contentInset
  1049. newContentInset.bottom = textView.frame.size.height-textViewHeight
  1050. #if swift(>=4.0)
  1051. if #available(iOS 11, *) {
  1052. newContentInset.bottom -= textView.safeAreaInsets.bottom
  1053. }
  1054. #endif
  1055. if textView.contentInset != newContentInset {
  1056. self.showLog("\(textFieldView) Old UITextView.contentInset: \(textView.contentInset) New UITextView.contentInset: \(newContentInset)")
  1057. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1058. textView.contentInset = newContentInset
  1059. textView.scrollIndicatorInsets = newContentInset
  1060. }, completion: { (_) -> Void in })
  1061. }
  1062. }
  1063. }
  1064. // +Positive or zero.
  1065. if move >= 0 {
  1066. rootViewOrigin.y = max(rootViewOrigin.y - move, min(0, -(kbSize.height-newKeyboardDistanceFromTextField)))
  1067. if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
  1068. showLog("Moving Upward")
  1069. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1070. var rect = rootController.view.frame
  1071. rect.origin = rootViewOrigin
  1072. rootController.view.frame = rect
  1073. //Animating content if needed (Bug ID: #204)
  1074. if self.layoutIfNeededOnUpdate == true {
  1075. //Animating content (Bug ID: #160)
  1076. rootController.view.setNeedsLayout()
  1077. rootController.view.layoutIfNeeded()
  1078. }
  1079. self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
  1080. })
  1081. }
  1082. _privateMovedDistance = (_topViewBeginOrigin.y-rootViewOrigin.y)
  1083. } else { // -Negative
  1084. let disturbDistance: CGFloat = rootViewOrigin.y-_topViewBeginOrigin.y
  1085. // disturbDistance Negative = frame disturbed.
  1086. // disturbDistance positive = frame not disturbed.
  1087. if disturbDistance <= 0 {
  1088. rootViewOrigin.y -= max(move, disturbDistance)
  1089. if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
  1090. showLog("Moving Downward")
  1091. // Setting adjusted rootViewRect
  1092. // Setting adjusted rootViewRect
  1093. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1094. var rect = rootController.view.frame
  1095. rect.origin = rootViewOrigin
  1096. rootController.view.frame = rect
  1097. //Animating content if needed (Bug ID: #204)
  1098. if self.layoutIfNeededOnUpdate == true {
  1099. //Animating content (Bug ID: #160)
  1100. rootController.view.setNeedsLayout()
  1101. rootController.view.layoutIfNeeded()
  1102. }
  1103. self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
  1104. })
  1105. }
  1106. _privateMovedDistance = (_topViewBeginOrigin.y-rootViewOrigin.y)
  1107. }
  1108. }
  1109. let elapsedTime = CACurrentMediaTime() - startTime
  1110. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1111. }
  1112. }
  1113. private func restorePosition() {
  1114. _privateHasPendingAdjustRequest = false
  1115. // Setting rootViewController frame to it's original position. // (Bug ID: #18)
  1116. if _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false {
  1117. if let rootViewController = _rootViewController {
  1118. if rootViewController.view.frame.origin.equalTo(self._topViewBeginOrigin) == false {
  1119. //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
  1120. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1121. self.showLog("Restoring \(rootViewController) origin to: \(self._topViewBeginOrigin)")
  1122. // Setting it's new frame
  1123. var rect = rootViewController.view.frame
  1124. rect.origin = self._topViewBeginOrigin
  1125. rootViewController.view.frame = rect
  1126. //Animating content if needed (Bug ID: #204)
  1127. if self.layoutIfNeededOnUpdate == true {
  1128. //Animating content (Bug ID: #160)
  1129. rootViewController.view.setNeedsLayout()
  1130. rootViewController.view.layoutIfNeeded()
  1131. }
  1132. })
  1133. }
  1134. self._privateMovedDistance = 0
  1135. if rootViewController.navigationController?.interactivePopGestureRecognizer?.state == .began {
  1136. self._rootViewControllerWhilePopGestureRecognizerActive = rootViewController
  1137. self._topViewBeginOriginWhilePopGestureRecognizerActive = self._topViewBeginOrigin
  1138. }
  1139. _rootViewController = nil
  1140. }
  1141. }
  1142. }
  1143. ///---------------------
  1144. /// MARK: Public Methods
  1145. ///---------------------
  1146. /* Refreshes textField/textView position if any external changes is explicitly made by user. */
  1147. @objc public func reloadLayoutIfNeeded() {
  1148. if privateIsEnabled() == true {
  1149. if _privateIsKeyboardShowing == true,
  1150. _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false,
  1151. let textFieldView = _textFieldView,
  1152. textFieldView.isAlertViewTextField() == false {
  1153. optimizedAdjustPosition()
  1154. }
  1155. }
  1156. }
  1157. ///-------------------------------
  1158. /// MARK: UIKeyboard Notifications
  1159. ///-------------------------------
  1160. /* UIKeyboardWillShowNotification. */
  1161. @objc internal func keyboardWillShow(_ notification: Notification?) {
  1162. _kbShowNotification = notification
  1163. // Boolean to know keyboard is showing/hiding
  1164. _privateIsKeyboardShowing = true
  1165. let oldKBFrame = _kbFrame
  1166. if let info = notification?.userInfo {
  1167. #if swift(>=4.2)
  1168. let curveUserInfoKey = UIResponder.keyboardAnimationCurveUserInfoKey
  1169. let durationUserInfoKey = UIResponder.keyboardAnimationDurationUserInfoKey
  1170. let frameEndUserInfoKey = UIResponder.keyboardFrameEndUserInfoKey
  1171. #else
  1172. let curveUserInfoKey = UIKeyboardAnimationCurveUserInfoKey
  1173. let durationUserInfoKey = UIKeyboardAnimationDurationUserInfoKey
  1174. let frameEndUserInfoKey = UIKeyboardFrameEndUserInfoKey
  1175. #endif
  1176. // Getting keyboard animation.
  1177. if let curve = info[curveUserInfoKey] as? UInt {
  1178. _animationCurve = .init(rawValue: curve)
  1179. } else {
  1180. _animationCurve = .curveEaseOut
  1181. }
  1182. // Getting keyboard animation duration
  1183. if let duration = info[durationUserInfoKey] as? TimeInterval {
  1184. //Saving animation duration
  1185. if duration != 0.0 {
  1186. _animationDuration = duration
  1187. }
  1188. } else {
  1189. _animationDuration = 0.25
  1190. }
  1191. // Getting UIKeyboardSize.
  1192. if let kbFrame = info[frameEndUserInfoKey] as? CGRect {
  1193. _kbFrame = kbFrame
  1194. showLog("UIKeyboard Frame: \(_kbFrame)")
  1195. }
  1196. }
  1197. if privateIsEnabled() == false {
  1198. restorePosition()
  1199. _topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid
  1200. return
  1201. }
  1202. let startTime = CACurrentMediaTime()
  1203. showLog("****** \(#function) started ******", indentation: 1)
  1204. // (Bug ID: #5)
  1205. if let textFieldView = _textFieldView, _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == true {
  1206. // keyboard is not showing(At the beginning only). We should save rootViewRect.
  1207. _rootViewController = textFieldView.parentContainerViewController()
  1208. if let controller = _rootViewController {
  1209. if _rootViewControllerWhilePopGestureRecognizerActive == controller {
  1210. _topViewBeginOrigin = _topViewBeginOriginWhilePopGestureRecognizerActive
  1211. } else {
  1212. _topViewBeginOrigin = controller.view.frame.origin
  1213. }
  1214. _rootViewControllerWhilePopGestureRecognizerActive = nil
  1215. _topViewBeginOriginWhilePopGestureRecognizerActive = IQKeyboardManager.kIQCGPointInvalid
  1216. self.showLog("Saving \(controller) beginning origin: \(self._topViewBeginOrigin)")
  1217. }
  1218. }
  1219. //If last restored keyboard size is different(any orientation accure), then refresh. otherwise not.
  1220. if _kbFrame.equalTo(oldKBFrame) == false {
  1221. //If _textFieldView is inside UITableViewController then let UITableViewController to handle it (Bug ID: #37) (Bug ID: #76) See note:- 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).
  1222. if _privateIsKeyboardShowing == true,
  1223. let textFieldView = _textFieldView,
  1224. textFieldView.isAlertViewTextField() == false {
  1225. // keyboard is already showing. adjust position.
  1226. optimizedAdjustPosition()
  1227. }
  1228. }
  1229. let elapsedTime = CACurrentMediaTime() - startTime
  1230. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1231. }
  1232. /* UIKeyboardDidShowNotification. */
  1233. @objc internal func keyboardDidShow(_ notification: Notification?) {
  1234. if privateIsEnabled() == false {
  1235. return
  1236. }
  1237. let startTime = CACurrentMediaTime()
  1238. showLog("****** \(#function) started ******", indentation: 1)
  1239. if let textFieldView = _textFieldView,
  1240. let parentController = textFieldView.parentContainerViewController(), (parentController.modalPresentationStyle == UIModalPresentationStyle.formSheet || parentController.modalPresentationStyle == UIModalPresentationStyle.pageSheet),
  1241. textFieldView.isAlertViewTextField() == false {
  1242. self.optimizedAdjustPosition()
  1243. }
  1244. let elapsedTime = CACurrentMediaTime() - startTime
  1245. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1246. }
  1247. /* UIKeyboardWillHideNotification. So setting rootViewController to it's default frame. */
  1248. @objc internal func keyboardWillHide(_ notification: Notification?) {
  1249. //If it's not a fake notification generated by [self setEnable:NO].
  1250. if notification != nil {
  1251. _kbShowNotification = nil
  1252. }
  1253. // Boolean to know keyboard is showing/hiding
  1254. _privateIsKeyboardShowing = false
  1255. if let info = notification?.userInfo {
  1256. #if swift(>=4.2)
  1257. let durationUserInfoKey = UIResponder.keyboardAnimationDurationUserInfoKey
  1258. #else
  1259. let durationUserInfoKey = UIKeyboardAnimationDurationUserInfoKey
  1260. #endif
  1261. // Getting keyboard animation duration
  1262. if let duration = info[durationUserInfoKey] as? TimeInterval {
  1263. if duration != 0 {
  1264. // Setitng keyboard animation duration
  1265. _animationDuration = duration
  1266. }
  1267. }
  1268. }
  1269. //If not enabled then do nothing.
  1270. if privateIsEnabled() == false {
  1271. return
  1272. }
  1273. let startTime = CACurrentMediaTime()
  1274. showLog("****** \(#function) started ******", indentation: 1)
  1275. //Commented due to #56. Added all the conditions below to handle WKWebView's textFields. (Bug ID: #56)
  1276. // We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11)
  1277. // if (_textFieldView == nil) return
  1278. //Restoring the contentOffset of the lastScrollView
  1279. if let lastScrollView = _lastScrollView {
  1280. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1281. if lastScrollView.contentInset != self._startingContentInsets {
  1282. self.showLog("Restoring contentInset to: \(self._startingContentInsets)")
  1283. lastScrollView.contentInset = self._startingContentInsets
  1284. lastScrollView.scrollIndicatorInsets = self._startingScrollIndicatorInsets
  1285. }
  1286. if lastScrollView.shouldRestoreScrollViewContentOffset == true && lastScrollView.contentOffset.equalTo(self._startingContentOffset) == false {
  1287. self.showLog("Restoring contentOffset to: \(self._startingContentOffset)")
  1288. var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
  1289. if #available(iOS 9, *) {
  1290. animatedContentOffset = self._textFieldView?.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil
  1291. }
  1292. if animatedContentOffset {
  1293. lastScrollView.setContentOffset(self._startingContentOffset, animated: UIView.areAnimationsEnabled)
  1294. } else {
  1295. lastScrollView.contentOffset = self._startingContentOffset
  1296. }
  1297. }
  1298. // TODO: restore scrollView state
  1299. // This is temporary solution. Have to implement the save and restore scrollView state
  1300. var superScrollView: UIScrollView? = lastScrollView
  1301. while let scrollView = superScrollView {
  1302. let contentSize = CGSize(width: max(scrollView.contentSize.width, scrollView.frame.width), height: max(scrollView.contentSize.height, scrollView.frame.height))
  1303. let minimumY = contentSize.height - scrollView.frame.height
  1304. if minimumY < scrollView.contentOffset.y {
  1305. let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: minimumY)
  1306. if scrollView.contentOffset.equalTo(newContentOffset) == false {
  1307. var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
  1308. if #available(iOS 9, *) {
  1309. animatedContentOffset = self._textFieldView?.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil
  1310. }
  1311. if animatedContentOffset {
  1312. scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
  1313. } else {
  1314. scrollView.contentOffset = newContentOffset
  1315. }
  1316. self.showLog("Restoring contentOffset to: \(self._startingContentOffset)")
  1317. }
  1318. }
  1319. superScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView
  1320. }
  1321. })
  1322. }
  1323. restorePosition()
  1324. //Reset all values
  1325. _lastScrollView = nil
  1326. _kbFrame = CGRect.zero
  1327. _startingContentInsets = UIEdgeInsets()
  1328. _startingScrollIndicatorInsets = UIEdgeInsets()
  1329. _startingContentOffset = CGPoint.zero
  1330. // topViewBeginRect = CGRectZero //Commented due to #82
  1331. let elapsedTime = CACurrentMediaTime() - startTime
  1332. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1333. }
  1334. @objc internal func keyboardDidHide(_ notification: Notification) {
  1335. let startTime = CACurrentMediaTime()
  1336. showLog("****** \(#function) started ******", indentation: 1)
  1337. _topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid
  1338. _kbFrame = CGRect.zero
  1339. let elapsedTime = CACurrentMediaTime() - startTime
  1340. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1341. }
  1342. ///-------------------------------------------
  1343. /// MARK: UITextField/UITextView Notifications
  1344. ///-------------------------------------------
  1345. /** UITextFieldTextDidBeginEditingNotification, UITextViewTextDidBeginEditingNotification. Fetching UITextFieldView object. */
  1346. @objc internal func textFieldViewDidBeginEditing(_ notification: Notification) {
  1347. let startTime = CACurrentMediaTime()
  1348. showLog("****** \(#function) started ******", indentation: 1)
  1349. // Getting object
  1350. _textFieldView = notification.object as? UIView
  1351. if overrideKeyboardAppearance == true {
  1352. if let textInput = _textFieldView as? UITextInput {
  1353. if textInput.keyboardAppearance != keyboardAppearance {
  1354. //Setting textField keyboard appearance and reloading inputViews.
  1355. if let textFieldView = _textFieldView as? UITextField {
  1356. textFieldView.keyboardAppearance = keyboardAppearance
  1357. } else if let textFieldView = _textFieldView as? UITextView {
  1358. textFieldView.keyboardAppearance = keyboardAppearance
  1359. }
  1360. _textFieldView?.reloadInputViews()
  1361. }
  1362. }
  1363. }
  1364. //If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required.
  1365. if privateIsEnableAutoToolbar() == true {
  1366. //UITextView special case. Keyboard Notification is firing before textView notification so we need to resign it first and then again set it as first responder to add toolbar on it.
  1367. if let textView = _textFieldView as? UIScrollView, textView.responds(to: #selector(getter: UITextView.isEditable)),
  1368. textView.inputAccessoryView == nil {
  1369. UIView.animate(withDuration: 0.00001, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1370. self.addToolbarIfRequired()
  1371. }, completion: { (_) -> Void in
  1372. //On textView toolbar didn't appear on first time, so forcing textView to reload it's inputViews.
  1373. textView.reloadInputViews()
  1374. })
  1375. } else {
  1376. //Adding toolbar
  1377. addToolbarIfRequired()
  1378. }
  1379. } else {
  1380. removeToolbarIfRequired()
  1381. }
  1382. resignFirstResponderGesture.isEnabled = privateShouldResignOnTouchOutside()
  1383. _textFieldView?.window?.addGestureRecognizer(resignFirstResponderGesture) // (Enhancement ID: #14)
  1384. if privateIsEnabled() == false {
  1385. restorePosition()
  1386. _topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid
  1387. } else {
  1388. if _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == true { // (Bug ID: #5)
  1389. _rootViewController = _textFieldView?.parentContainerViewController()
  1390. if let controller = _rootViewController {
  1391. if _rootViewControllerWhilePopGestureRecognizerActive == controller {
  1392. _topViewBeginOrigin = _topViewBeginOriginWhilePopGestureRecognizerActive
  1393. } else {
  1394. _topViewBeginOrigin = controller.view.frame.origin
  1395. }
  1396. _rootViewControllerWhilePopGestureRecognizerActive = nil
  1397. _topViewBeginOriginWhilePopGestureRecognizerActive = IQKeyboardManager.kIQCGPointInvalid
  1398. self.showLog("Saving \(controller) beginning origin: \(self._topViewBeginOrigin)")
  1399. }
  1400. }
  1401. //If _textFieldView is inside ignored responder then do nothing. (Bug ID: #37, #74, #76)
  1402. //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).
  1403. if _privateIsKeyboardShowing == true,
  1404. let textFieldView = _textFieldView,
  1405. textFieldView.isAlertViewTextField() == false {
  1406. // keyboard is already showing. adjust position.
  1407. optimizedAdjustPosition()
  1408. }
  1409. }
  1410. let elapsedTime = CACurrentMediaTime() - startTime
  1411. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1412. }
  1413. /** UITextFieldTextDidEndEditingNotification, UITextViewTextDidEndEditingNotification. Removing fetched object. */
  1414. @objc internal func textFieldViewDidEndEditing(_ notification: Notification) {
  1415. let startTime = CACurrentMediaTime()
  1416. showLog("****** \(#function) started ******", indentation: 1)
  1417. //Removing gesture recognizer (Enhancement ID: #14)
  1418. _textFieldView?.window?.removeGestureRecognizer(resignFirstResponderGesture)
  1419. // We check if there's a change in original frame or not.
  1420. if let textView = _textFieldView as? UIScrollView, textView.responds(to: #selector(getter: UITextView.isEditable)) {
  1421. if isTextViewContentInsetChanged == true {
  1422. self.isTextViewContentInsetChanged = false
  1423. if textView.contentInset != self.startingTextViewContentInsets {
  1424. self.showLog("Restoring textView.contentInset to: \(self.startingTextViewContentInsets)")
  1425. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1426. //Setting textField to it's initial contentInset
  1427. textView.contentInset = self.startingTextViewContentInsets
  1428. textView.scrollIndicatorInsets = self.startingTextViewScrollIndicatorInsets
  1429. }, completion: { (_) -> Void in })
  1430. }
  1431. }
  1432. }
  1433. //Setting object to nil
  1434. _textFieldView = nil
  1435. let elapsedTime = CACurrentMediaTime() - startTime
  1436. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1437. }
  1438. ///---------------------------------------
  1439. /// MARK: UIStatusBar Notification methods
  1440. ///---------------------------------------
  1441. /** UIApplicationWillChangeStatusBarOrientationNotification. Need to set the textView to it's original position. If any frame changes made. (Bug ID: #92)*/
  1442. @objc internal func willChangeStatusBarOrientation(_ notification: Notification) {
  1443. let currentStatusBarOrientation : UIInterfaceOrientation
  1444. #if swift(>=5.1)
  1445. if #available(iOS 13, *) {
  1446. currentStatusBarOrientation = keyWindow()?.windowScene?.interfaceOrientation ?? UIInterfaceOrientation.unknown
  1447. } else {
  1448. currentStatusBarOrientation = UIApplication.shared.statusBarOrientation
  1449. }
  1450. #else
  1451. currentStatusBarOrientation = UIApplication.shared.statusBarOrientation
  1452. #endif
  1453. #if swift(>=4.2)
  1454. let statusBarUserInfoKey = UIApplication.statusBarOrientationUserInfoKey
  1455. #else
  1456. let statusBarUserInfoKey = UIApplicationStatusBarOrientationUserInfoKey
  1457. #endif
  1458. guard let statusBarOrientation = notification.userInfo?[statusBarUserInfoKey] as? Int, currentStatusBarOrientation.rawValue != statusBarOrientation else {
  1459. return
  1460. }
  1461. let startTime = CACurrentMediaTime()
  1462. showLog("****** \(#function) started ******", indentation: 1)
  1463. //If textViewContentInsetChanged is saved then restore it.
  1464. if let textView = _textFieldView as? UITextView, textView.responds(to: #selector(getter: UITextView.isEditable)) {
  1465. if isTextViewContentInsetChanged == true {
  1466. self.isTextViewContentInsetChanged = false
  1467. if textView.contentInset != self.startingTextViewContentInsets {
  1468. UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
  1469. self.showLog("Restoring textView.contentInset to: \(self.startingTextViewContentInsets)")
  1470. //Setting textField to it's initial contentInset
  1471. textView.contentInset = self.startingTextViewContentInsets
  1472. textView.scrollIndicatorInsets = self.startingTextViewScrollIndicatorInsets
  1473. }, completion: { (_) -> Void in })
  1474. }
  1475. }
  1476. }
  1477. restorePosition()
  1478. let elapsedTime = CACurrentMediaTime() - startTime
  1479. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1480. }
  1481. ///------------------
  1482. /// MARK: AutoToolbar
  1483. ///------------------
  1484. /** Get all UITextField/UITextView siblings of textFieldView. */
  1485. private func responderViews() -> [UIView]? {
  1486. var superConsideredView: UIView?
  1487. //If find any consider responderView in it's upper hierarchy then will get deepResponderView.
  1488. for disabledClass in toolbarPreviousNextAllowedClasses {
  1489. superConsideredView = _textFieldView?.superviewOfClassType(disabledClass)
  1490. if superConsideredView != nil {
  1491. break
  1492. }
  1493. }
  1494. //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)
  1495. if let view = superConsideredView {
  1496. return view.deepResponderViews()
  1497. } else { //Otherwise fetching all the siblings
  1498. if let textFields = _textFieldView?.responderSiblings() {
  1499. //Sorting textFields according to behaviour
  1500. switch toolbarManageBehaviour {
  1501. //If autoToolbar behaviour is bySubviews, then returning it.
  1502. case IQAutoToolbarManageBehaviour.bySubviews: return textFields
  1503. //If autoToolbar behaviour is by tag, then sorting it according to tag property.
  1504. case IQAutoToolbarManageBehaviour.byTag: return textFields.sortedArrayByTag()
  1505. //If autoToolbar behaviour is by tag, then sorting it according to tag property.
  1506. case IQAutoToolbarManageBehaviour.byPosition: return textFields.sortedArrayByPosition()
  1507. }
  1508. } else {
  1509. return nil
  1510. }
  1511. }
  1512. }
  1513. /** Add toolbar if it is required to add on textFields and it's siblings. */
  1514. private func addToolbarIfRequired() {
  1515. let startTime = CACurrentMediaTime()
  1516. showLog("****** \(#function) started ******", indentation: 1)
  1517. // Getting all the sibling textFields.
  1518. if let siblings = responderViews(), !siblings.isEmpty {
  1519. showLog("Found \(siblings.count) responder sibling(s)")
  1520. if let textField = _textFieldView {
  1521. //Either there is no inputAccessoryView or if accessoryView is not appropriate for current situation(There is Previous/Next/Done toolbar).
  1522. //setInputAccessoryView: check (Bug ID: #307)
  1523. if textField.responds(to: #selector(setter: UITextField.inputAccessoryView)) {
  1524. if textField.inputAccessoryView == nil ||
  1525. textField.inputAccessoryView?.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag ||
  1526. textField.inputAccessoryView?.tag == IQKeyboardManager.kIQDoneButtonToolbarTag {
  1527. let rightConfiguration: IQBarButtonItemConfiguration
  1528. if let doneBarButtonItemImage = toolbarDoneBarButtonItemImage {
  1529. rightConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.doneAction(_:)))
  1530. } else if let doneBarButtonItemText = toolbarDoneBarButtonItemText {
  1531. rightConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.doneAction(_:)))
  1532. } else {
  1533. rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: #selector(self.doneAction(_:)))
  1534. }
  1535. rightConfiguration.accessibilityLabel = toolbarDoneBarButtonItemAccessibilityLabel ?? "Done"
  1536. // If only one object is found, then adding only Done button.
  1537. if (siblings.count <= 1 && previousNextDisplayMode == .default) || previousNextDisplayMode == .alwaysHide {
  1538. textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: nil, nextBarButtonConfiguration: nil)
  1539. textField.inputAccessoryView?.tag = IQKeyboardManager.kIQDoneButtonToolbarTag // (Bug ID: #78)
  1540. } else if previousNextDisplayMode == .default || previousNextDisplayMode == .alwaysShow {
  1541. let prevConfiguration: IQBarButtonItemConfiguration
  1542. if let doneBarButtonItemImage = toolbarPreviousBarButtonItemImage {
  1543. prevConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.previousAction(_:)))
  1544. } else if let doneBarButtonItemText = toolbarPreviousBarButtonItemText {
  1545. prevConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.previousAction(_:)))
  1546. } else {
  1547. prevConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardPreviousImage() ?? UIImage()), action: #selector(self.previousAction(_:)))
  1548. }
  1549. prevConfiguration.accessibilityLabel = toolbarPreviousBarButtonItemAccessibilityLabel ?? "Previous"
  1550. let nextConfiguration: IQBarButtonItemConfiguration
  1551. if let doneBarButtonItemImage = toolbarNextBarButtonItemImage {
  1552. nextConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.nextAction(_:)))
  1553. } else if let doneBarButtonItemText = toolbarNextBarButtonItemText {
  1554. nextConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.nextAction(_:)))
  1555. } else {
  1556. nextConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardNextImage() ?? UIImage()), action: #selector(self.nextAction(_:)))
  1557. }
  1558. nextConfiguration.accessibilityLabel = toolbarNextBarButtonItemAccessibilityLabel ?? "Next"
  1559. textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration)
  1560. textField.inputAccessoryView?.tag = IQKeyboardManager.kIQPreviousNextButtonToolbarTag // (Bug ID: #78)
  1561. }
  1562. let toolbar = textField.keyboardToolbar
  1563. //Setting toolbar tintColor // (Enhancement ID: #30)
  1564. if shouldToolbarUsesTextFieldTintColor {
  1565. toolbar.tintColor = textField.tintColor
  1566. } else if let tintColor = toolbarTintColor {
  1567. toolbar.tintColor = tintColor
  1568. } else {
  1569. toolbar.tintColor = nil
  1570. }
  1571. // Setting toolbar to keyboard.
  1572. if let textFieldView = textField as? UITextInput {
  1573. //Bar style according to keyboard appearance
  1574. switch textFieldView.keyboardAppearance {
  1575. case .dark?:
  1576. toolbar.barStyle = .black
  1577. toolbar.barTintColor = nil
  1578. default:
  1579. toolbar.barStyle = .default
  1580. toolbar.barTintColor = toolbarBarTintColor
  1581. }
  1582. }
  1583. //Setting toolbar title font. // (Enhancement ID: #30)
  1584. if shouldShowToolbarPlaceholder == true &&
  1585. textField.shouldHideToolbarPlaceholder == false {
  1586. //Updating placeholder font to toolbar. //(Bug ID: #148, #272)
  1587. if toolbar.titleBarButton.title == nil ||
  1588. toolbar.titleBarButton.title != textField.drawingToolbarPlaceholder {
  1589. toolbar.titleBarButton.title = textField.drawingToolbarPlaceholder
  1590. }
  1591. //Setting toolbar title font. // (Enhancement ID: #30)
  1592. if let font = placeholderFont {
  1593. toolbar.titleBarButton.titleFont = font
  1594. }
  1595. //Setting toolbar title color. // (Enhancement ID: #880)
  1596. if let color = placeholderColor {
  1597. toolbar.titleBarButton.titleColor = color
  1598. }
  1599. //Setting toolbar button title color. // (Enhancement ID: #880)
  1600. if let color = placeholderButtonColor {
  1601. toolbar.titleBarButton.selectableTitleColor = color
  1602. }
  1603. } else {
  1604. toolbar.titleBarButton.title = nil
  1605. }
  1606. //In case of UITableView (Special), the next/previous buttons has to be refreshed everytime. (Bug ID: #56)
  1607. // If firstTextField, then previous should not be enabled.
  1608. if siblings.first == textField {
  1609. if siblings.count == 1 {
  1610. textField.keyboardToolbar.previousBarButton.isEnabled = false
  1611. textField.keyboardToolbar.nextBarButton.isEnabled = false
  1612. } else {
  1613. textField.keyboardToolbar.previousBarButton.isEnabled = false
  1614. textField.keyboardToolbar.nextBarButton.isEnabled = true
  1615. }
  1616. } else if siblings.last == textField { // If lastTextField then next should not be enaled.
  1617. textField.keyboardToolbar.previousBarButton.isEnabled = true
  1618. textField.keyboardToolbar.nextBarButton.isEnabled = false
  1619. } else {
  1620. textField.keyboardToolbar.previousBarButton.isEnabled = true
  1621. textField.keyboardToolbar.nextBarButton.isEnabled = true
  1622. }
  1623. }
  1624. }
  1625. }
  1626. }
  1627. let elapsedTime = CACurrentMediaTime() - startTime
  1628. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1629. }
  1630. /** Remove any toolbar if it is IQToolbar. */
  1631. private func removeToolbarIfRequired() { // (Bug ID: #18)
  1632. let startTime = CACurrentMediaTime()
  1633. showLog("****** \(#function) started ******", indentation: 1)
  1634. // Getting all the sibling textFields.
  1635. if let siblings = responderViews() {
  1636. showLog("Found \(siblings.count) responder sibling(s)")
  1637. for view in siblings {
  1638. if let toolbar = view.inputAccessoryView as? IQToolbar {
  1639. //setInputAccessoryView: check (Bug ID: #307)
  1640. if view.responds(to: #selector(setter: UITextField.inputAccessoryView)) &&
  1641. (toolbar.tag == IQKeyboardManager.kIQDoneButtonToolbarTag || toolbar.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag) {
  1642. if let textField = view as? UITextField {
  1643. textField.inputAccessoryView = nil
  1644. } else if let textView = view as? UITextView {
  1645. textView.inputAccessoryView = nil
  1646. }
  1647. view.reloadInputViews()
  1648. }
  1649. }
  1650. }
  1651. }
  1652. let elapsedTime = CACurrentMediaTime() - startTime
  1653. showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
  1654. }
  1655. /** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
  1656. @objc public func reloadInputViews() {
  1657. //If enabled then adding toolbar.
  1658. if privateIsEnableAutoToolbar() == true {
  1659. self.addToolbarIfRequired()
  1660. } else {
  1661. self.removeToolbarIfRequired()
  1662. }
  1663. }
  1664. ///------------------------------------
  1665. /// MARK: Debugging & Developer options
  1666. ///------------------------------------
  1667. @objc public var enableDebugging = false
  1668. /**
  1669. @warning Use below methods to completely enable/disable notifications registered by library internally. Please keep in mind that library is totally dependent on NSNotification of UITextField, UITextField, Keyboard etc. If you do unregisterAllNotifications then library will not work at all. You should only use below methods if you want to completedly disable all library functions. You should use below methods at your own risk.
  1670. */
  1671. @objc public func registerAllNotifications() {
  1672. #if swift(>=4.2)
  1673. let UIKeyboardWillShow = UIResponder.keyboardWillShowNotification
  1674. let UIKeyboardDidShow = UIResponder.keyboardDidShowNotification
  1675. let UIKeyboardWillHide = UIResponder.keyboardWillHideNotification
  1676. let UIKeyboardDidHide = UIResponder.keyboardDidHideNotification
  1677. let UITextFieldTextDidBeginEditing = UITextField.textDidBeginEditingNotification
  1678. let UITextFieldTextDidEndEditing = UITextField.textDidEndEditingNotification
  1679. let UITextViewTextDidBeginEditing = UITextView.textDidBeginEditingNotification
  1680. let UITextViewTextDidEndEditing = UITextView.textDidEndEditingNotification
  1681. let UIApplicationWillChangeStatusBarOrientation = UIApplication.willChangeStatusBarOrientationNotification
  1682. #else
  1683. let UIKeyboardWillShow = Notification.Name.UIKeyboardWillShow
  1684. let UIKeyboardDidShow = Notification.Name.UIKeyboardDidShow
  1685. let UIKeyboardWillHide = Notification.Name.UIKeyboardWillHide
  1686. let UIKeyboardDidHide = Notification.Name.UIKeyboardDidHide
  1687. let UITextFieldTextDidBeginEditing = Notification.Name.UITextFieldTextDidBeginEditing
  1688. let UITextFieldTextDidEndEditing = Notification.Name.UITextFieldTextDidEndEditing
  1689. let UITextViewTextDidBeginEditing = Notification.Name.UITextViewTextDidBeginEditing
  1690. let UITextViewTextDidEndEditing = Notification.Name.UITextViewTextDidEndEditing
  1691. let UIApplicationWillChangeStatusBarOrientation = Notification.Name.UIApplicationWillChangeStatusBarOrientation
  1692. #endif
  1693. // Registering for keyboard notification.
  1694. NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIKeyboardWillShow, object: nil)
  1695. NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: UIKeyboardDidShow, object: nil)
  1696. NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIKeyboardWillHide, object: nil)
  1697. NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: UIKeyboardDidHide, object: nil)
  1698. // Registering for UITextField notification.
  1699. registerTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextFieldTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextFieldTextDidEndEditing.rawValue)
  1700. // Registering for UITextView notification.
  1701. registerTextFieldViewClass(UITextView.self, didBeginEditingNotificationName: UITextViewTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextViewTextDidEndEditing.rawValue)
  1702. // Registering for orientation changes notification
  1703. NotificationCenter.default.addObserver(self, selector: #selector(self.willChangeStatusBarOrientation(_:)), name: UIApplicationWillChangeStatusBarOrientation, object: UIApplication.shared)
  1704. }
  1705. @objc public func unregisterAllNotifications() {
  1706. #if swift(>=4.2)
  1707. let UIKeyboardWillShow = UIResponder.keyboardWillShowNotification
  1708. let UIKeyboardDidShow = UIResponder.keyboardDidShowNotification
  1709. let UIKeyboardWillHide = UIResponder.keyboardWillHideNotification
  1710. let UIKeyboardDidHide = UIResponder.keyboardDidHideNotification
  1711. let UITextFieldTextDidBeginEditing = UITextField.textDidBeginEditingNotification
  1712. let UITextFieldTextDidEndEditing = UITextField.textDidEndEditingNotification
  1713. let UITextViewTextDidBeginEditing = UITextView.textDidBeginEditingNotification
  1714. let UITextViewTextDidEndEditing = UITextView.textDidEndEditingNotification
  1715. let UIApplicationWillChangeStatusBarOrientation = UIApplication.willChangeStatusBarOrientationNotification
  1716. #else
  1717. let UIKeyboardWillShow = Notification.Name.UIKeyboardWillShow
  1718. let UIKeyboardDidShow = Notification.Name.UIKeyboardDidShow
  1719. let UIKeyboardWillHide = Notification.Name.UIKeyboardWillHide
  1720. let UIKeyboardDidHide = Notification.Name.UIKeyboardDidHide
  1721. let UITextFieldTextDidBeginEditing = Notification.Name.UITextFieldTextDidBeginEditing
  1722. let UITextFieldTextDidEndEditing = Notification.Name.UITextFieldTextDidEndEditing
  1723. let UITextViewTextDidBeginEditing = Notification.Name.UITextViewTextDidBeginEditing
  1724. let UITextViewTextDidEndEditing = Notification.Name.UITextViewTextDidEndEditing
  1725. let UIApplicationWillChangeStatusBarOrientation = Notification.Name.UIApplicationWillChangeStatusBarOrientation
  1726. #endif
  1727. // Unregistering for keyboard notification.
  1728. NotificationCenter.default.removeObserver(self, name: UIKeyboardWillShow, object: nil)
  1729. NotificationCenter.default.removeObserver(self, name: UIKeyboardDidShow, object: nil)
  1730. NotificationCenter.default.removeObserver(self, name: UIKeyboardWillHide, object: nil)
  1731. NotificationCenter.default.removeObserver(self, name: UIKeyboardDidHide, object: nil)
  1732. // Unregistering for UITextField notification.
  1733. unregisterTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextFieldTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextFieldTextDidEndEditing.rawValue)
  1734. // Unregistering for UITextView notification.
  1735. unregisterTextFieldViewClass(UITextView.self, didBeginEditingNotificationName: UITextViewTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextViewTextDidEndEditing.rawValue)
  1736. // Unregistering for orientation changes notification
  1737. NotificationCenter.default.removeObserver(self, name: UIApplicationWillChangeStatusBarOrientation, object: UIApplication.shared)
  1738. }
  1739. private func showLog(_ logString: String, indentation: Int = 0) {
  1740. struct Static {
  1741. static var indentation = 0
  1742. }
  1743. if indentation < 0 {
  1744. Static.indentation = max(0, Static.indentation + indentation)
  1745. }
  1746. if enableDebugging {
  1747. var preLog = "IQKeyboardManager"
  1748. for _ in 0 ... Static.indentation {
  1749. preLog += "|\t"
  1750. }
  1751. print(preLog + logString)
  1752. }
  1753. if indentation > 0 {
  1754. Static.indentation += indentation
  1755. }
  1756. }
  1757. }