IQUIView+Hierarchy.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. //
  2. // IQUIView+Hierarchy.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 UIKit
  24. /**
  25. UIView hierarchy category.
  26. */
  27. @objc public extension UIView {
  28. ///----------------------
  29. // MARK: viewControllers
  30. ///----------------------
  31. /**
  32. Returns the UIViewController object that manages the receiver.
  33. */
  34. func viewContainingController() -> UIViewController? {
  35. var nextResponder: UIResponder? = self
  36. repeat {
  37. nextResponder = nextResponder?.next
  38. if let viewController = nextResponder as? UIViewController {
  39. return viewController
  40. }
  41. } while nextResponder != nil
  42. return nil
  43. }
  44. /**
  45. Returns the topMost UIViewController object in hierarchy.
  46. */
  47. func topMostController() -> UIViewController? {
  48. var controllersHierarchy = [UIViewController]()
  49. if var topController = window?.rootViewController {
  50. controllersHierarchy.append(topController)
  51. while let presented = topController.presentedViewController {
  52. topController = presented
  53. controllersHierarchy.append(presented)
  54. }
  55. var matchController: UIResponder? = viewContainingController()
  56. while let mController = matchController as? UIViewController, controllersHierarchy.contains(mController) == false {
  57. repeat {
  58. matchController = matchController?.next
  59. } while matchController != nil && matchController is UIViewController == false
  60. }
  61. return matchController as? UIViewController
  62. } else {
  63. return viewContainingController()
  64. }
  65. }
  66. /**
  67. Returns the UIViewController object that is actually the parent of this object. Most of the time it's the viewController object which actually contains it, but result may be different if it's viewController is added as childViewController of another viewController.
  68. */
  69. func parentContainerViewController() -> UIViewController? {
  70. var matchController = viewContainingController()
  71. var parentContainerViewController: UIViewController?
  72. if var navController = matchController?.navigationController {
  73. while let parentNav = navController.navigationController {
  74. navController = parentNav
  75. }
  76. var parentController: UIViewController = navController
  77. while let parent = parentController.parent,
  78. (parent.isKind(of: UINavigationController.self) == false &&
  79. parent.isKind(of: UITabBarController.self) == false &&
  80. parent.isKind(of: UISplitViewController.self) == false) {
  81. parentController = parent
  82. }
  83. if navController == parentController {
  84. parentContainerViewController = navController.topViewController
  85. } else {
  86. parentContainerViewController = parentController
  87. }
  88. } else if let tabController = matchController?.tabBarController {
  89. if let navController = tabController.selectedViewController as? UINavigationController {
  90. parentContainerViewController = navController.topViewController
  91. } else {
  92. parentContainerViewController = tabController.selectedViewController
  93. }
  94. } else {
  95. while let parentController = matchController?.parent,
  96. (parentController.isKind(of: UINavigationController.self) == false &&
  97. parentController.isKind(of: UITabBarController.self) == false &&
  98. parentController.isKind(of: UISplitViewController.self) == false) {
  99. matchController = parentController
  100. }
  101. parentContainerViewController = matchController
  102. }
  103. let finalController = parentContainerViewController?.parentIQContainerViewController() ?? parentContainerViewController
  104. return finalController
  105. }
  106. ///-----------------------------------
  107. // MARK: Superviews/Subviews/Siglings
  108. ///-----------------------------------
  109. /**
  110. Returns the superView of provided class type.
  111. @param classType class type of the object which is to be search in above hierarchy and return
  112. @param belowView view object in upper hierarchy where method should stop searching and return nil
  113. */
  114. func superviewOfClassType(_ classType: UIView.Type, belowView: UIView? = nil) -> UIView? {
  115. var superView = superview
  116. while let unwrappedSuperView = superView {
  117. if unwrappedSuperView.isKind(of: classType) {
  118. //If it's UIScrollView, then validating for special cases
  119. if unwrappedSuperView.isKind(of: UIScrollView.self) {
  120. let classNameString = NSStringFromClass(type(of: unwrappedSuperView.self))
  121. // If it's not UITableViewWrapperView class, this is internal class which is actually manage in UITableview. The speciality of this class is that it's superview is UITableView.
  122. // If it's not UITableViewCellScrollView class, this is internal class which is actually manage in UITableviewCell. The speciality of this class is that it's superview is UITableViewCell.
  123. //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes
  124. if unwrappedSuperView.superview?.isKind(of: UITableView.self) == false &&
  125. unwrappedSuperView.superview?.isKind(of: UITableViewCell.self) == false &&
  126. classNameString.hasPrefix("_") == false {
  127. return superView
  128. }
  129. } else {
  130. return superView
  131. }
  132. } else if unwrappedSuperView == belowView {
  133. return nil
  134. }
  135. superView = unwrappedSuperView.superview
  136. }
  137. return nil
  138. }
  139. /**
  140. Returns all siblings of the receiver which canBecomeFirstResponder.
  141. */
  142. internal func responderSiblings() -> [UIView] {
  143. //Array of (UITextField/UITextView's).
  144. var tempTextFields = [UIView]()
  145. // Getting all siblings
  146. if let siblings = superview?.subviews {
  147. for textField in siblings {
  148. if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true {
  149. tempTextFields.append(textField)
  150. }
  151. }
  152. }
  153. return tempTextFields
  154. }
  155. /**
  156. Returns all deep subViews of the receiver which canBecomeFirstResponder.
  157. */
  158. internal func deepResponderViews() -> [UIView] {
  159. //Array of (UITextField/UITextView's).
  160. var textfields = [UIView]()
  161. for textField in subviews {
  162. if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true {
  163. textfields.append(textField)
  164. }
  165. //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458)
  166. //Uncommented else (Bug ID: #625)
  167. if textField.subviews.count != 0 && isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 {
  168. for deepView in textField.deepResponderViews() {
  169. textfields.append(deepView)
  170. }
  171. }
  172. }
  173. //subviews are returning in opposite order. Sorting according the frames 'y'.
  174. return textfields.sorted(by: { (view1: UIView, view2: UIView) -> Bool in
  175. let frame1 = view1.convert(view1.bounds, to: self)
  176. let frame2 = view2.convert(view2.bounds, to: self)
  177. if frame1.minY != frame2.minY {
  178. return frame1.minY < frame2.minY
  179. } else {
  180. return frame1.minX < frame2.minX
  181. }
  182. })
  183. }
  184. private func IQcanBecomeFirstResponder() -> Bool {
  185. var IQcanBecomeFirstResponder = false
  186. // Setting toolbar to keyboard.
  187. if let textField = self as? UITextField {
  188. IQcanBecomeFirstResponder = textField.isEnabled
  189. } else if let textView = self as? UITextView {
  190. IQcanBecomeFirstResponder = textView.isEditable
  191. }
  192. if IQcanBecomeFirstResponder == true {
  193. IQcanBecomeFirstResponder = isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 && isAlertViewTextField() == false && textFieldSearchBar() == nil
  194. }
  195. return IQcanBecomeFirstResponder
  196. }
  197. ///-------------------------
  198. // MARK: Special TextFields
  199. ///-------------------------
  200. /**
  201. Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil.
  202. */
  203. internal func textFieldSearchBar() -> UISearchBar? {
  204. var responder: UIResponder? = self.next
  205. while let bar = responder {
  206. if let searchBar = bar as? UISearchBar {
  207. return searchBar
  208. } else if bar is UIViewController {
  209. break
  210. }
  211. responder = bar.next
  212. }
  213. return nil
  214. }
  215. /**
  216. Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO.
  217. */
  218. internal func isAlertViewTextField() -> Bool {
  219. var alertViewController: UIResponder? = viewContainingController()
  220. var isAlertViewTextField = false
  221. while let controller = alertViewController, isAlertViewTextField == false {
  222. if controller.isKind(of: UIAlertController.self) {
  223. isAlertViewTextField = true
  224. break
  225. }
  226. alertViewController = controller.next
  227. }
  228. return isAlertViewTextField
  229. }
  230. private func depth() -> Int {
  231. var depth: Int = 0
  232. if let superView = superview {
  233. depth = superView.depth()+1
  234. }
  235. return depth
  236. }
  237. }
  238. @objc public extension UIViewController {
  239. func parentIQContainerViewController() -> UIViewController? {
  240. return self
  241. }
  242. }
  243. extension NSObject {
  244. internal func _IQDescription() -> String {
  245. return "<\(self) \(Unmanaged.passUnretained(self).toOpaque())>"
  246. }
  247. }