New file |
| | |
| | | // |
| | | // IQUIView+Hierarchy.m |
| | | // https://github.com/hackiftekhar/IQKeyboardManager |
| | | // Copyright (c) 2013-16 Iftekhar Qurashi. |
| | | // |
| | | // Permission is hereby granted, free of charge, to any person obtaining a copy |
| | | // of this software and associated documentation files (the "Software"), to deal |
| | | // in the Software without restriction, including without limitation the rights |
| | | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| | | // copies of the Software, and to permit persons to whom the Software is |
| | | // furnished to do so, subject to the following conditions: |
| | | // |
| | | // The above copyright notice and this permission notice shall be included in |
| | | // all copies or substantial portions of the Software. |
| | | // |
| | | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| | | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| | | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| | | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| | | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| | | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| | | // THE SOFTWARE. |
| | | |
| | | #import "IQUIView+Hierarchy.h" |
| | | #import "IQUITextFieldView+Additions.h" |
| | | |
| | | #import <UIKit/UICollectionView.h> |
| | | #import <UIKit/UIAlertController.h> |
| | | #import <UIKit/UITableView.h> |
| | | #import <UIKit/UITextView.h> |
| | | #import <UIKit/UITextField.h> |
| | | #import <UIKit/UISearchBar.h> |
| | | #import <UIKit/UINavigationController.h> |
| | | #import <UIKit/UITabBarController.h> |
| | | #import <UIKit/UISplitViewController.h> |
| | | #import <UIKit/UIWindow.h> |
| | | |
| | | #import <objc/runtime.h> |
| | | |
| | | #import "IQNSArray+Sort.h" |
| | | |
| | | @implementation UIView (IQ_UIView_Hierarchy) |
| | | |
| | | -(UIViewController*)viewContainingController |
| | | { |
| | | UIResponder *nextResponder = self; |
| | | |
| | | do |
| | | { |
| | | nextResponder = [nextResponder nextResponder]; |
| | | |
| | | if ([nextResponder isKindOfClass:[UIViewController class]]) |
| | | return (UIViewController*)nextResponder; |
| | | |
| | | } while (nextResponder); |
| | | |
| | | return nil; |
| | | } |
| | | |
| | | -(UIViewController *)topMostController |
| | | { |
| | | NSMutableArray<UIViewController*> *controllersHierarchy = [[NSMutableArray alloc] init]; |
| | | |
| | | UIViewController *topController = self.window.rootViewController; |
| | | |
| | | if (topController) |
| | | { |
| | | [controllersHierarchy addObject:topController]; |
| | | } |
| | | |
| | | while ([topController presentedViewController]) { |
| | | |
| | | topController = [topController presentedViewController]; |
| | | [controllersHierarchy addObject:topController]; |
| | | } |
| | | |
| | | UIViewController *matchController = [self viewContainingController]; |
| | | |
| | | while (matchController && [controllersHierarchy containsObject:matchController] == NO) |
| | | { |
| | | do |
| | | { |
| | | matchController = (UIViewController*)[matchController nextResponder]; |
| | | |
| | | } while (matchController && [matchController isKindOfClass:[UIViewController class]] == NO); |
| | | } |
| | | |
| | | return matchController; |
| | | } |
| | | |
| | | -(UIViewController *)parentContainerViewController |
| | | { |
| | | UIViewController *matchController = [self viewContainingController]; |
| | | |
| | | if (matchController.navigationController) |
| | | { |
| | | UINavigationController *navController = matchController.navigationController; |
| | | |
| | | while (navController.navigationController) { |
| | | navController = navController.navigationController; |
| | | } |
| | | |
| | | UIViewController *parentController = navController; |
| | | |
| | | UIViewController *parentParentController = parentController.parentViewController; |
| | | |
| | | while (parentParentController && |
| | | ([parentParentController isKindOfClass:[UINavigationController class]] == NO && |
| | | [parentParentController isKindOfClass:[UITabBarController class]] == NO && |
| | | [parentParentController isKindOfClass:[UISplitViewController class]] == NO)) |
| | | { |
| | | parentController = parentParentController; |
| | | parentParentController = parentController.parentViewController; |
| | | } |
| | | |
| | | if (navController == parentController) |
| | | { |
| | | return navController.topViewController; |
| | | } |
| | | else |
| | | { |
| | | return parentController; |
| | | } |
| | | } |
| | | else if (matchController.tabBarController) |
| | | { |
| | | if ([matchController.tabBarController.selectedViewController isKindOfClass:[UINavigationController class]]) |
| | | { |
| | | return [(UINavigationController*)matchController.tabBarController.selectedViewController topViewController]; |
| | | } |
| | | else |
| | | { |
| | | return matchController.tabBarController.selectedViewController; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | UIViewController *matchParentController = matchController.parentViewController; |
| | | |
| | | while (matchParentController && |
| | | ([matchParentController isKindOfClass:[UINavigationController class]] == NO && |
| | | [matchParentController isKindOfClass:[UITabBarController class]] == NO && |
| | | [matchParentController isKindOfClass:[UISplitViewController class]] == NO)) |
| | | { |
| | | matchController = matchParentController; |
| | | matchParentController = matchController.parentViewController; |
| | | } |
| | | |
| | | return matchController; |
| | | } |
| | | } |
| | | |
| | | -(UIView*)superviewOfClassType:(Class)classType |
| | | { |
| | | UIView *superview = self.superview; |
| | | |
| | | while (superview) |
| | | { |
| | | if ([superview isKindOfClass:classType]) |
| | | { |
| | | //If it's UIScrollView, then validating for special cases |
| | | if ([superview isKindOfClass:[UIScrollView class]]) |
| | | { |
| | | NSString *classNameString = NSStringFromClass([superview class]); |
| | | |
| | | // 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. |
| | | // 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. |
| | | //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes |
| | | if ([superview.superview isKindOfClass:[UITableView class]] == NO && |
| | | [superview.superview isKindOfClass:[UITableViewCell class]] == NO && |
| | | [classNameString hasPrefix:@"_"] == NO) |
| | | { |
| | | return superview; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | return superview; |
| | | } |
| | | } |
| | | |
| | | superview = superview.superview; |
| | | } |
| | | |
| | | return nil; |
| | | } |
| | | |
| | | -(BOOL)_IQcanBecomeFirstResponder |
| | | { |
| | | BOOL _IQcanBecomeFirstResponder = NO; |
| | | |
| | | if ([self isKindOfClass:[UITextField class]]) |
| | | { |
| | | _IQcanBecomeFirstResponder = [(UITextField*)self isEnabled]; |
| | | } |
| | | else if ([self isKindOfClass:[UITextView class]]) |
| | | { |
| | | _IQcanBecomeFirstResponder = [(UITextView*)self isEditable]; |
| | | } |
| | | |
| | | if (_IQcanBecomeFirstResponder == YES) |
| | | { |
| | | _IQcanBecomeFirstResponder = ([self isUserInteractionEnabled] && ![self isHidden] && [self alpha]!=0.0 && ![self isAlertViewTextField] && !self.searchBar); |
| | | } |
| | | |
| | | return _IQcanBecomeFirstResponder; |
| | | } |
| | | |
| | | - (NSArray*)responderSiblings |
| | | { |
| | | // Getting all siblings |
| | | NSArray *siblings = self.superview.subviews; |
| | | |
| | | //Array of (UITextField/UITextView's). |
| | | NSMutableArray<UIView*> *tempTextFields = [[NSMutableArray alloc] init]; |
| | | |
| | | for (UIView *textField in siblings) |
| | | if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder]) |
| | | [tempTextFields addObject:textField]; |
| | | |
| | | return tempTextFields; |
| | | } |
| | | |
| | | - (NSArray*)deepResponderViews |
| | | { |
| | | NSMutableArray<UIView*> *textFields = [[NSMutableArray alloc] init]; |
| | | |
| | | for (UIView *textField in self.subviews) |
| | | { |
| | | if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder]) |
| | | { |
| | | [textFields addObject:textField]; |
| | | } |
| | | |
| | | //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458) |
| | | //Uncommented else (Bug ID: #625) |
| | | if (textField.subviews.count && [textField isUserInteractionEnabled] && ![textField isHidden] && [textField alpha]!=0.0) |
| | | { |
| | | [textFields addObjectsFromArray:[textField deepResponderViews]]; |
| | | } |
| | | } |
| | | |
| | | //subviews are returning in incorrect order. Sorting according the frames 'y'. |
| | | return [textFields sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) { |
| | | |
| | | CGRect frame1 = [view1 convertRect:view1.bounds toView:self]; |
| | | CGRect frame2 = [view2 convertRect:view2.bounds toView:self]; |
| | | |
| | | CGFloat x1 = CGRectGetMinX(frame1); |
| | | CGFloat y1 = CGRectGetMinY(frame1); |
| | | CGFloat x2 = CGRectGetMinX(frame2); |
| | | CGFloat y2 = CGRectGetMinY(frame2); |
| | | |
| | | if (y1 < y2) return NSOrderedAscending; |
| | | |
| | | else if (y1 > y2) return NSOrderedDescending; |
| | | |
| | | //Else both y are same so checking for x positions |
| | | else if (x1 < x2) return NSOrderedAscending; |
| | | |
| | | else if (x1 > x2) return NSOrderedDescending; |
| | | |
| | | else return NSOrderedSame; |
| | | }]; |
| | | |
| | | return textFields; |
| | | } |
| | | |
| | | -(CGAffineTransform)convertTransformToView:(UIView*)toView |
| | | { |
| | | if (toView == nil) |
| | | { |
| | | toView = self.window; |
| | | } |
| | | |
| | | CGAffineTransform myTransform = CGAffineTransformIdentity; |
| | | |
| | | //My Transform |
| | | { |
| | | UIView *superView = [self superview]; |
| | | |
| | | if (superView) myTransform = CGAffineTransformConcat(self.transform, [superView convertTransformToView:nil]); |
| | | else myTransform = self.transform; |
| | | } |
| | | |
| | | CGAffineTransform viewTransform = CGAffineTransformIdentity; |
| | | |
| | | //view Transform |
| | | { |
| | | UIView *superView = [toView superview]; |
| | | |
| | | if (superView) viewTransform = CGAffineTransformConcat(toView.transform, [superView convertTransformToView:nil]); |
| | | else if (toView) viewTransform = toView.transform; |
| | | } |
| | | |
| | | return CGAffineTransformConcat(myTransform, CGAffineTransformInvert(viewTransform)); |
| | | } |
| | | |
| | | |
| | | - (NSInteger)depth |
| | | { |
| | | NSInteger depth = 0; |
| | | |
| | | if ([self superview]) |
| | | { |
| | | depth = [[self superview] depth] + 1; |
| | | } |
| | | |
| | | return depth; |
| | | } |
| | | |
| | | - (NSString *)subHierarchy |
| | | { |
| | | NSMutableString *debugInfo = [[NSMutableString alloc] initWithString:@"\n"]; |
| | | NSInteger depth = [self depth]; |
| | | |
| | | for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "]; |
| | | |
| | | [debugInfo appendString:[self debugHierarchy]]; |
| | | |
| | | for (UIView *subview in self.subviews) |
| | | { |
| | | [debugInfo appendString:[subview subHierarchy]]; |
| | | } |
| | | |
| | | return debugInfo; |
| | | } |
| | | |
| | | - (NSString *)superHierarchy |
| | | { |
| | | NSMutableString *debugInfo = [[NSMutableString alloc] init]; |
| | | |
| | | if (self.superview) |
| | | { |
| | | [debugInfo appendString:[self.superview superHierarchy]]; |
| | | } |
| | | else |
| | | { |
| | | [debugInfo appendString:@"\n"]; |
| | | } |
| | | |
| | | NSInteger depth = [self depth]; |
| | | |
| | | for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "]; |
| | | |
| | | [debugInfo appendString:[self debugHierarchy]]; |
| | | |
| | | [debugInfo appendString:@"\n"]; |
| | | |
| | | return debugInfo; |
| | | } |
| | | |
| | | -(NSString *)debugHierarchy |
| | | { |
| | | NSMutableString *debugInfo = [[NSMutableString alloc] init]; |
| | | |
| | | [debugInfo appendFormat:@"%@: ( %.0f, %.0f, %.0f, %.0f )",NSStringFromClass([self class]), CGRectGetMinX(self.frame), CGRectGetMinY(self.frame), CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)]; |
| | | |
| | | if ([self isKindOfClass:[UIScrollView class]]) |
| | | { |
| | | UIScrollView *scrollView = (UIScrollView*)self; |
| | | [debugInfo appendFormat:@"%@: ( %.0f, %.0f )",NSStringFromSelector(@selector(contentSize)),scrollView.contentSize.width,scrollView.contentSize.height]; |
| | | } |
| | | |
| | | if (CGAffineTransformEqualToTransform(self.transform, CGAffineTransformIdentity) == false) |
| | | { |
| | | [debugInfo appendFormat:@"%@: %@",NSStringFromSelector(@selector(transform)),NSStringFromCGAffineTransform(self.transform)]; |
| | | } |
| | | |
| | | return debugInfo; |
| | | } |
| | | |
| | | -(UISearchBar *)searchBar |
| | | { |
| | | UIResponder *searchBar = [self nextResponder]; |
| | | |
| | | while (searchBar) |
| | | { |
| | | if ([searchBar isKindOfClass:[UISearchBar class]]) |
| | | { |
| | | return (UISearchBar*)searchBar; |
| | | } |
| | | else if ([searchBar isKindOfClass:[UIViewController class]]) //If found viewcontroller but still not found UISearchBar then it's not the search bar textfield |
| | | { |
| | | break; |
| | | } |
| | | |
| | | searchBar = [searchBar nextResponder]; |
| | | } |
| | | |
| | | return nil; |
| | | } |
| | | |
| | | -(BOOL)isAlertViewTextField |
| | | { |
| | | UIResponder *alertViewController = [self viewContainingController]; |
| | | |
| | | BOOL isAlertViewTextField = NO; |
| | | while (alertViewController && isAlertViewTextField == NO) |
| | | { |
| | | if ([alertViewController isKindOfClass:[UIAlertController class]]) |
| | | { |
| | | isAlertViewTextField = YES; |
| | | break; |
| | | } |
| | | |
| | | alertViewController = [alertViewController nextResponder]; |
| | | } |
| | | |
| | | return isAlertViewTextField; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation NSObject (IQ_Logging) |
| | | |
| | | -(NSString *)_IQDescription |
| | | { |
| | | return [NSString stringWithFormat:@"<%@ %p>",NSStringFromClass([self class]),self]; |
| | | } |
| | | |
| | | @end |