IQUIView+Hierarchy.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. //
  2. // IQUIView+Hierarchy.m
  3. // https://github.com/hackiftekhar/IQKeyboardManager
  4. // Copyright (c) 2013-16 Iftekhar Qurashi.
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. #import "IQUIView+Hierarchy.h"
  24. #import "IQUITextFieldView+Additions.h"
  25. #import <UIKit/UICollectionView.h>
  26. #import <UIKit/UIAlertController.h>
  27. #import <UIKit/UITableView.h>
  28. #import <UIKit/UITextView.h>
  29. #import <UIKit/UITextField.h>
  30. #import <UIKit/UISearchBar.h>
  31. #import <UIKit/UINavigationController.h>
  32. #import <UIKit/UITabBarController.h>
  33. #import <UIKit/UISplitViewController.h>
  34. #import <UIKit/UIWindow.h>
  35. #import <objc/runtime.h>
  36. #import "IQNSArray+Sort.h"
  37. @implementation UIView (IQ_UIView_Hierarchy)
  38. -(UIViewController*)viewContainingController
  39. {
  40. UIResponder *nextResponder = self;
  41. do
  42. {
  43. nextResponder = [nextResponder nextResponder];
  44. if ([nextResponder isKindOfClass:[UIViewController class]])
  45. return (UIViewController*)nextResponder;
  46. } while (nextResponder);
  47. return nil;
  48. }
  49. -(UIViewController *)topMostController
  50. {
  51. NSMutableArray<UIViewController*> *controllersHierarchy = [[NSMutableArray alloc] init];
  52. UIViewController *topController = self.window.rootViewController;
  53. if (topController)
  54. {
  55. [controllersHierarchy addObject:topController];
  56. }
  57. while ([topController presentedViewController]) {
  58. topController = [topController presentedViewController];
  59. [controllersHierarchy addObject:topController];
  60. }
  61. UIViewController *matchController = [self viewContainingController];
  62. while (matchController && [controllersHierarchy containsObject:matchController] == NO)
  63. {
  64. do
  65. {
  66. matchController = (UIViewController*)[matchController nextResponder];
  67. } while (matchController && [matchController isKindOfClass:[UIViewController class]] == NO);
  68. }
  69. return matchController;
  70. }
  71. -(UIViewController *)parentContainerViewController
  72. {
  73. UIViewController *matchController = [self viewContainingController];
  74. UIViewController *parentContainerViewController = nil;
  75. if (matchController.navigationController)
  76. {
  77. UINavigationController *navController = matchController.navigationController;
  78. while (navController.navigationController) {
  79. navController = navController.navigationController;
  80. }
  81. UIViewController *parentController = navController;
  82. UIViewController *parentParentController = parentController.parentViewController;
  83. while (parentParentController &&
  84. ([parentParentController isKindOfClass:[UINavigationController class]] == NO &&
  85. [parentParentController isKindOfClass:[UITabBarController class]] == NO &&
  86. [parentParentController isKindOfClass:[UISplitViewController class]] == NO))
  87. {
  88. parentController = parentParentController;
  89. parentParentController = parentController.parentViewController;
  90. }
  91. if (navController == parentController)
  92. {
  93. parentContainerViewController = navController.topViewController;
  94. }
  95. else
  96. {
  97. parentContainerViewController = parentController;
  98. }
  99. }
  100. else if (matchController.tabBarController)
  101. {
  102. if ([matchController.tabBarController.selectedViewController isKindOfClass:[UINavigationController class]])
  103. {
  104. parentContainerViewController = [(UINavigationController*)matchController.tabBarController.selectedViewController topViewController];
  105. }
  106. else
  107. {
  108. parentContainerViewController = matchController.tabBarController.selectedViewController;
  109. }
  110. }
  111. else
  112. {
  113. UIViewController *matchParentController = matchController.parentViewController;
  114. while (matchParentController &&
  115. ([matchParentController isKindOfClass:[UINavigationController class]] == NO &&
  116. [matchParentController isKindOfClass:[UITabBarController class]] == NO &&
  117. [matchParentController isKindOfClass:[UISplitViewController class]] == NO))
  118. {
  119. matchController = matchParentController;
  120. matchParentController = matchController.parentViewController;
  121. }
  122. parentContainerViewController = matchController;
  123. }
  124. UIViewController *finalController = [parentContainerViewController parentIQContainerViewController] ?: parentContainerViewController;
  125. return finalController;
  126. }
  127. -(UIView*)superviewOfClassType:(Class)classType
  128. {
  129. UIView *superview = self.superview;
  130. while (superview)
  131. {
  132. if ([superview isKindOfClass:classType])
  133. {
  134. //If it's UIScrollView, then validating for special cases
  135. if ([superview isKindOfClass:[UIScrollView class]])
  136. {
  137. NSString *classNameString = NSStringFromClass([superview class]);
  138. // 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.
  139. // 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.
  140. //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes
  141. if ([superview.superview isKindOfClass:[UITableView class]] == NO &&
  142. [superview.superview isKindOfClass:[UITableViewCell class]] == NO &&
  143. [classNameString hasPrefix:@"_"] == NO)
  144. {
  145. return superview;
  146. }
  147. }
  148. else
  149. {
  150. return superview;
  151. }
  152. }
  153. superview = superview.superview;
  154. }
  155. return nil;
  156. }
  157. -(BOOL)_IQcanBecomeFirstResponder
  158. {
  159. BOOL _IQcanBecomeFirstResponder = NO;
  160. if ([self isKindOfClass:[UITextField class]])
  161. {
  162. _IQcanBecomeFirstResponder = [(UITextField*)self isEnabled];
  163. }
  164. else if ([self isKindOfClass:[UITextView class]])
  165. {
  166. _IQcanBecomeFirstResponder = [(UITextView*)self isEditable];
  167. }
  168. if (_IQcanBecomeFirstResponder == YES)
  169. {
  170. _IQcanBecomeFirstResponder = ([self isUserInteractionEnabled] && ![self isHidden] && [self alpha]!=0.0 && ![self isAlertViewTextField] && !self.textFieldSearchBar);
  171. }
  172. return _IQcanBecomeFirstResponder;
  173. }
  174. - (NSArray<UIView*>*)responderSiblings
  175. {
  176. // Getting all siblings
  177. NSArray<UIView*> *siblings = self.superview.subviews;
  178. //Array of (UITextField/UITextView's).
  179. NSMutableArray<UIView*> *tempTextFields = [[NSMutableArray alloc] init];
  180. for (UIView *textField in siblings)
  181. if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder])
  182. [tempTextFields addObject:textField];
  183. return tempTextFields;
  184. }
  185. - (NSArray<UIView*>*)deepResponderViews
  186. {
  187. NSMutableArray<UIView*> *textFields = [[NSMutableArray alloc] init];
  188. for (UIView *textField in self.subviews)
  189. {
  190. if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder])
  191. {
  192. [textFields addObject:textField];
  193. }
  194. //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458)
  195. //Uncommented else (Bug ID: #625)
  196. if (textField.subviews.count && [textField isUserInteractionEnabled] && ![textField isHidden] && [textField alpha]!=0.0)
  197. {
  198. [textFields addObjectsFromArray:[textField deepResponderViews]];
  199. }
  200. }
  201. //subviews are returning in incorrect order. Sorting according the frames 'y'.
  202. return [textFields sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {
  203. CGRect frame1 = [view1 convertRect:view1.bounds toView:self];
  204. CGRect frame2 = [view2 convertRect:view2.bounds toView:self];
  205. CGFloat x1 = CGRectGetMinX(frame1);
  206. CGFloat y1 = CGRectGetMinY(frame1);
  207. CGFloat x2 = CGRectGetMinX(frame2);
  208. CGFloat y2 = CGRectGetMinY(frame2);
  209. if (y1 < y2) return NSOrderedAscending;
  210. else if (y1 > y2) return NSOrderedDescending;
  211. //Else both y are same so checking for x positions
  212. else if (x1 < x2) return NSOrderedAscending;
  213. else if (x1 > x2) return NSOrderedDescending;
  214. else return NSOrderedSame;
  215. }];
  216. return textFields;
  217. }
  218. -(CGAffineTransform)convertTransformToView:(UIView*)toView
  219. {
  220. if (toView == nil)
  221. {
  222. toView = self.window;
  223. }
  224. CGAffineTransform myTransform = CGAffineTransformIdentity;
  225. //My Transform
  226. {
  227. UIView *superView = [self superview];
  228. if (superView) myTransform = CGAffineTransformConcat(self.transform, [superView convertTransformToView:nil]);
  229. else myTransform = self.transform;
  230. }
  231. CGAffineTransform viewTransform = CGAffineTransformIdentity;
  232. //view Transform
  233. {
  234. UIView *superView = [toView superview];
  235. if (superView) viewTransform = CGAffineTransformConcat(toView.transform, [superView convertTransformToView:nil]);
  236. else if (toView) viewTransform = toView.transform;
  237. }
  238. return CGAffineTransformConcat(myTransform, CGAffineTransformInvert(viewTransform));
  239. }
  240. - (NSInteger)depth
  241. {
  242. NSInteger depth = 0;
  243. if ([self superview])
  244. {
  245. depth = [[self superview] depth] + 1;
  246. }
  247. return depth;
  248. }
  249. - (NSString *)subHierarchy
  250. {
  251. NSMutableString *debugInfo = [[NSMutableString alloc] initWithString:@"\n"];
  252. NSInteger depth = [self depth];
  253. for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "];
  254. [debugInfo appendString:[self debugHierarchy]];
  255. for (UIView *subview in self.subviews)
  256. {
  257. [debugInfo appendString:[subview subHierarchy]];
  258. }
  259. return debugInfo;
  260. }
  261. - (NSString *)superHierarchy
  262. {
  263. NSMutableString *debugInfo = [[NSMutableString alloc] init];
  264. if (self.superview)
  265. {
  266. [debugInfo appendString:[self.superview superHierarchy]];
  267. }
  268. else
  269. {
  270. [debugInfo appendString:@"\n"];
  271. }
  272. NSInteger depth = [self depth];
  273. for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "];
  274. [debugInfo appendString:[self debugHierarchy]];
  275. [debugInfo appendString:@"\n"];
  276. return debugInfo;
  277. }
  278. -(NSString *)debugHierarchy
  279. {
  280. NSMutableString *debugInfo = [[NSMutableString alloc] init];
  281. [debugInfo appendFormat:@"%@: ( %.0f, %.0f, %.0f, %.0f )",NSStringFromClass([self class]), CGRectGetMinX(self.frame), CGRectGetMinY(self.frame), CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)];
  282. if ([self isKindOfClass:[UIScrollView class]])
  283. {
  284. UIScrollView *scrollView = (UIScrollView*)self;
  285. [debugInfo appendFormat:@"%@: ( %.0f, %.0f )",NSStringFromSelector(@selector(contentSize)),scrollView.contentSize.width,scrollView.contentSize.height];
  286. }
  287. if (CGAffineTransformEqualToTransform(self.transform, CGAffineTransformIdentity) == false)
  288. {
  289. [debugInfo appendFormat:@"%@: %@",NSStringFromSelector(@selector(transform)),NSStringFromCGAffineTransform(self.transform)];
  290. }
  291. return debugInfo;
  292. }
  293. -(UISearchBar *)textFieldSearchBar
  294. {
  295. UIResponder *searchBar = [self nextResponder];
  296. while (searchBar)
  297. {
  298. if ([searchBar isKindOfClass:[UISearchBar class]])
  299. {
  300. return (UISearchBar*)searchBar;
  301. }
  302. else if ([searchBar isKindOfClass:[UIViewController class]]) //If found viewcontroller but still not found UISearchBar then it's not the search bar textfield
  303. {
  304. break;
  305. }
  306. searchBar = [searchBar nextResponder];
  307. }
  308. return nil;
  309. }
  310. -(BOOL)isAlertViewTextField
  311. {
  312. UIResponder *alertViewController = [self viewContainingController];
  313. BOOL isAlertViewTextField = NO;
  314. while (alertViewController && isAlertViewTextField == NO)
  315. {
  316. if ([alertViewController isKindOfClass:[UIAlertController class]])
  317. {
  318. isAlertViewTextField = YES;
  319. break;
  320. }
  321. alertViewController = [alertViewController nextResponder];
  322. }
  323. return isAlertViewTextField;
  324. }
  325. @end
  326. @implementation UIViewController (IQ_UIView_Hierarchy)
  327. -(nullable UIViewController*)parentIQContainerViewController
  328. {
  329. return self;
  330. }
  331. @end
  332. @implementation NSObject (IQ_Logging)
  333. -(NSString *)_IQDescription
  334. {
  335. return [NSString stringWithFormat:@"<%@ %p>",NSStringFromClass([self class]),self];
  336. }
  337. @end