悟空记账

IQUIView+Hierarchy.m 11KB

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