IQUIView+Hierarchy.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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 "IQUIViewController+Additions.h"
  26. #import <UIKit/UICollectionView.h>
  27. #import <UIKit/UIAlertController.h>
  28. #import <UIKit/UITableView.h>
  29. #import <UIKit/UITextView.h>
  30. #import <UIKit/UITextField.h>
  31. #import <UIKit/UISearchBar.h>
  32. #import <UIKit/UINavigationController.h>
  33. #import <UIKit/UITabBarController.h>
  34. #import <UIKit/UISplitViewController.h>
  35. #import <UIKit/UIWindow.h>
  36. #import <objc/runtime.h>
  37. #import "IQNSArray+Sort.h"
  38. @implementation UIView (IQ_UIView_Hierarchy)
  39. -(UIViewController*)viewContainingController
  40. {
  41. UIResponder *nextResponder = self;
  42. do
  43. {
  44. nextResponder = [nextResponder nextResponder];
  45. if ([nextResponder isKindOfClass:[UIViewController class]])
  46. return (UIViewController*)nextResponder;
  47. } while (nextResponder);
  48. return nil;
  49. }
  50. -(UIViewController *)topMostController
  51. {
  52. NSMutableArray<UIViewController*> *controllersHierarchy = [[NSMutableArray alloc] init];
  53. UIViewController *topController = self.window.rootViewController;
  54. if (topController)
  55. {
  56. [controllersHierarchy addObject:topController];
  57. }
  58. while ([topController presentedViewController]) {
  59. topController = [topController presentedViewController];
  60. [controllersHierarchy addObject:topController];
  61. }
  62. UIViewController *matchController = [self viewContainingController];
  63. while (matchController && [controllersHierarchy containsObject:matchController] == NO)
  64. {
  65. do
  66. {
  67. matchController = (UIViewController*)[matchController nextResponder];
  68. } while (matchController && [matchController isKindOfClass:[UIViewController class]] == NO);
  69. }
  70. return matchController;
  71. }
  72. -(UIViewController *)parentContainerViewController
  73. {
  74. UIViewController *matchController = [self viewContainingController];
  75. UIViewController *parentContainerViewController = nil;
  76. if (matchController.navigationController)
  77. {
  78. UINavigationController *navController = matchController.navigationController;
  79. while (navController.navigationController) {
  80. navController = navController.navigationController;
  81. }
  82. UIViewController *parentController = navController;
  83. UIViewController *parentParentController = parentController.parentViewController;
  84. while (parentParentController &&
  85. ([parentParentController isKindOfClass:[UINavigationController class]] == NO &&
  86. [parentParentController isKindOfClass:[UITabBarController class]] == NO &&
  87. [parentParentController isKindOfClass:[UISplitViewController class]] == NO))
  88. {
  89. parentController = parentParentController;
  90. parentParentController = parentController.parentViewController;
  91. }
  92. if (navController == parentController)
  93. {
  94. parentContainerViewController = navController.topViewController;
  95. }
  96. else
  97. {
  98. parentContainerViewController = parentController;
  99. }
  100. }
  101. else if (matchController.tabBarController)
  102. {
  103. if ([matchController.tabBarController.selectedViewController isKindOfClass:[UINavigationController class]])
  104. {
  105. parentContainerViewController = [(UINavigationController*)matchController.tabBarController.selectedViewController topViewController];
  106. }
  107. else
  108. {
  109. parentContainerViewController = matchController.tabBarController.selectedViewController;
  110. }
  111. }
  112. else
  113. {
  114. UIViewController *matchParentController = matchController.parentViewController;
  115. while (matchParentController &&
  116. ([matchParentController isKindOfClass:[UINavigationController class]] == NO &&
  117. [matchParentController isKindOfClass:[UITabBarController class]] == NO &&
  118. [matchParentController isKindOfClass:[UISplitViewController class]] == NO))
  119. {
  120. matchController = matchParentController;
  121. matchParentController = matchController.parentViewController;
  122. }
  123. parentContainerViewController = matchController;
  124. }
  125. UIViewController *finalController = [parentContainerViewController parentIQContainerViewController] ?: parentContainerViewController;
  126. return finalController;
  127. }
  128. -(UIView*)superviewOfClassType:(nonnull Class)classType
  129. {
  130. return [self superviewOfClassType:classType belowView:nil];
  131. }
  132. -(nullable __kindof UIView*)superviewOfClassType:(nonnull Class)classType belowView:(nullable UIView*)belowView
  133. {
  134. UIView *superview = self.superview;
  135. while (superview)
  136. {
  137. if ([superview isKindOfClass:classType])
  138. {
  139. //If it's UIScrollView, then validating for special cases
  140. if ([superview isKindOfClass:[UIScrollView class]])
  141. {
  142. NSString *classNameString = NSStringFromClass([superview class]);
  143. // 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.
  144. // 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.
  145. //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes
  146. if ([superview.superview isKindOfClass:[UITableView class]] == NO &&
  147. [superview.superview isKindOfClass:[UITableViewCell class]] == NO &&
  148. [classNameString hasPrefix:@"_"] == NO)
  149. {
  150. return superview;
  151. }
  152. }
  153. else
  154. {
  155. return superview;
  156. }
  157. }
  158. else if (belowView == superview)
  159. {
  160. return nil;
  161. }
  162. superview = superview.superview;
  163. }
  164. return nil;
  165. }
  166. -(BOOL)_IQcanBecomeFirstResponder
  167. {
  168. BOOL _IQcanBecomeFirstResponder = NO;
  169. if ([self conformsToProtocol:@protocol(UITextInput)]) {
  170. if ([self respondsToSelector:@selector(isEditable)] && [self isKindOfClass:[UIScrollView class]])
  171. {
  172. _IQcanBecomeFirstResponder = [(UITextView*)self isEditable];
  173. }
  174. else if ([self respondsToSelector:@selector(isEnabled)])
  175. {
  176. _IQcanBecomeFirstResponder = [(UITextField*)self isEnabled];
  177. }
  178. }
  179. if (_IQcanBecomeFirstResponder == YES)
  180. {
  181. _IQcanBecomeFirstResponder = ([self isUserInteractionEnabled] && ![self isHidden] && [self alpha]!=0.0 && ![self isAlertViewTextField] && !self.textFieldSearchBar);
  182. }
  183. return _IQcanBecomeFirstResponder;
  184. }
  185. - (NSArray<UIView*>*)responderSiblings
  186. {
  187. // Getting all siblings
  188. NSArray<UIView*> *siblings = self.superview.subviews;
  189. //Array of (UITextField/UITextView's).
  190. NSMutableArray<UIView*> *tempTextFields = [[NSMutableArray alloc] init];
  191. for (UIView *textField in siblings)
  192. if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder])
  193. [tempTextFields addObject:textField];
  194. return tempTextFields;
  195. }
  196. - (NSArray<UIView*>*)deepResponderViews
  197. {
  198. NSMutableArray<UIView*> *textFields = [[NSMutableArray alloc] init];
  199. for (UIView *textField in self.subviews)
  200. {
  201. if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder])
  202. {
  203. [textFields addObject:textField];
  204. }
  205. //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458)
  206. //Uncommented else (Bug ID: #625)
  207. else if (textField.subviews.count && [textField isUserInteractionEnabled] && ![textField isHidden] && [textField alpha]!=0.0)
  208. {
  209. [textFields addObjectsFromArray:[textField deepResponderViews]];
  210. }
  211. }
  212. //subviews are returning in incorrect order. Sorting according the frames 'y'.
  213. return [textFields sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {
  214. CGRect frame1 = [view1 convertRect:view1.bounds toView:self];
  215. CGRect frame2 = [view2 convertRect:view2.bounds toView:self];
  216. CGFloat x1 = CGRectGetMinX(frame1);
  217. CGFloat y1 = CGRectGetMinY(frame1);
  218. CGFloat x2 = CGRectGetMinX(frame2);
  219. CGFloat y2 = CGRectGetMinY(frame2);
  220. if (y1 < y2) return NSOrderedAscending;
  221. else if (y1 > y2) return NSOrderedDescending;
  222. //Else both y are same so checking for x positions
  223. else if (x1 < x2) return NSOrderedAscending;
  224. else if (x1 > x2) return NSOrderedDescending;
  225. else return NSOrderedSame;
  226. }];
  227. return textFields;
  228. }
  229. -(CGAffineTransform)convertTransformToView:(UIView*)toView
  230. {
  231. if (toView == nil)
  232. {
  233. toView = self.window;
  234. }
  235. CGAffineTransform myTransform = CGAffineTransformIdentity;
  236. //My Transform
  237. {
  238. UIView *superView = [self superview];
  239. if (superView) myTransform = CGAffineTransformConcat(self.transform, [superView convertTransformToView:nil]);
  240. else myTransform = self.transform;
  241. }
  242. CGAffineTransform viewTransform = CGAffineTransformIdentity;
  243. //view Transform
  244. {
  245. UIView *superView = [toView superview];
  246. if (superView) viewTransform = CGAffineTransformConcat(toView.transform, [superView convertTransformToView:nil]);
  247. else if (toView) viewTransform = toView.transform;
  248. }
  249. return CGAffineTransformConcat(myTransform, CGAffineTransformInvert(viewTransform));
  250. }
  251. - (NSInteger)depth
  252. {
  253. NSInteger depth = 0;
  254. if ([self superview])
  255. {
  256. depth = [[self superview] depth] + 1;
  257. }
  258. return depth;
  259. }
  260. - (NSString *)subHierarchy
  261. {
  262. NSMutableString *debugInfo = [[NSMutableString alloc] initWithString:@"\n"];
  263. NSInteger depth = [self depth];
  264. for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "];
  265. [debugInfo appendString:[self debugHierarchy]];
  266. for (UIView *subview in self.subviews)
  267. {
  268. [debugInfo appendString:[subview subHierarchy]];
  269. }
  270. return debugInfo;
  271. }
  272. - (NSString *)superHierarchy
  273. {
  274. NSMutableString *debugInfo = [[NSMutableString alloc] init];
  275. if (self.superview)
  276. {
  277. [debugInfo appendString:[self.superview superHierarchy]];
  278. }
  279. else
  280. {
  281. [debugInfo appendString:@"\n"];
  282. }
  283. NSInteger depth = [self depth];
  284. for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "];
  285. [debugInfo appendString:[self debugHierarchy]];
  286. [debugInfo appendString:@"\n"];
  287. return debugInfo;
  288. }
  289. -(NSString *)debugHierarchy
  290. {
  291. NSMutableString *debugInfo = [[NSMutableString alloc] init];
  292. [debugInfo appendFormat:@"%@: ( %.0f, %.0f, %.0f, %.0f )",NSStringFromClass([self class]), CGRectGetMinX(self.frame), CGRectGetMinY(self.frame), CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)];
  293. if ([self isKindOfClass:[UIScrollView class]])
  294. {
  295. UIScrollView *scrollView = (UIScrollView*)self;
  296. [debugInfo appendFormat:@"%@: ( %.0f, %.0f )",NSStringFromSelector(@selector(contentSize)),scrollView.contentSize.width,scrollView.contentSize.height];
  297. }
  298. if (CGAffineTransformEqualToTransform(self.transform, CGAffineTransformIdentity) == false)
  299. {
  300. [debugInfo appendFormat:@"%@: %@",NSStringFromSelector(@selector(transform)),NSStringFromCGAffineTransform(self.transform)];
  301. }
  302. return debugInfo;
  303. }
  304. -(UISearchBar *)textFieldSearchBar
  305. {
  306. UIResponder *searchBar = [self nextResponder];
  307. while (searchBar)
  308. {
  309. if ([searchBar isKindOfClass:[UISearchBar class]])
  310. {
  311. return (UISearchBar*)searchBar;
  312. }
  313. else if ([searchBar isKindOfClass:[UIViewController class]]) //If found viewcontroller but still not found UISearchBar then it's not the search bar textfield
  314. {
  315. break;
  316. }
  317. searchBar = [searchBar nextResponder];
  318. }
  319. return nil;
  320. }
  321. -(BOOL)isAlertViewTextField
  322. {
  323. UIResponder *alertViewController = [self viewContainingController];
  324. BOOL isAlertViewTextField = NO;
  325. while (alertViewController && isAlertViewTextField == NO)
  326. {
  327. if ([alertViewController isKindOfClass:[UIAlertController class]])
  328. {
  329. isAlertViewTextField = YES;
  330. break;
  331. }
  332. alertViewController = [alertViewController nextResponder];
  333. }
  334. return isAlertViewTextField;
  335. }
  336. @end
  337. @implementation NSObject (IQ_Logging)
  338. -(NSString *)_IQDescription
  339. {
  340. return [NSString stringWithFormat:@"<%@ %p>",NSStringFromClass([self class]),self];
  341. }
  342. @end