-
Notifications
You must be signed in to change notification settings - Fork 0
/
UIView+Helpers.m
126 lines (105 loc) · 4.46 KB
/
UIView+Helpers.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//
// UIView+Helpers.m
// UIViewHelpers
//
// Created by Matt Holden on 1/8/13.
// Copyright (c) 2013 Matt Holden. All rights reserved.
//
#define INFINITE_DEPTH -1 // Only used internally for private API
#define SuppressPerformSelectorLeakWarning(LeakyCode) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
LeakyCode; \
_Pragma("clang diagnostic pop") \
} while (0)
#import "UIView+Helpers.h"
@interface UIView (HelpersPrivate)
-(NSArray*)subviewsMatchingClass:(Class)aClass
includeSubclasses:(BOOL)includeSubclasses
maxDepth:(NSInteger)maxDepth;
@end
@implementation UIView (Helpers)
-(NSArray*)subviewsPassingTest:(BOOL(^)(UIView *subview, BOOL *stop))test {
__block BOOL stop = NO;
NSArray*(^__block __unsafe_unretained capturedEvaluateAndRecurse)(UIView*);
NSArray*(^evaluateAndRecurse)(UIView*);
evaluateAndRecurse = ^NSArray*(UIView *view) {
NSMutableArray *myPassedChildren = [[NSMutableArray alloc] init];
for (UIView *subview in [view subviews]) {
BOOL passes = test(subview, &stop);
if (passes) [myPassedChildren addObject:subview];
if (stop) return myPassedChildren;
[myPassedChildren addObjectsFromArray:capturedEvaluateAndRecurse(subview)];
}
return myPassedChildren;
};
capturedEvaluateAndRecurse = evaluateAndRecurse;
return evaluateAndRecurse(self);
}
-(NSArray*)subviewsPassingTest:(BOOL(^)(UIView *subview, BOOL *stop))test
maxDepth:(NSInteger)maxDepth {
if (maxDepth < 0) {
[NSException raise:NSInvalidArgumentException format:@"maxDepth must be >= 0"];
}
// Wrap "test" block argument in a new block that checks
// subview depth prior to executing "test."
BOOL(^newTest)(UIView *subview, BOOL*stop) = ^BOOL(UIView* subview, BOOL *stop) {
int currentDepth = 0;
UIView *node = subview;
while (!([node isEqual:self])) {
node = [node superview];
// If "subview" is deeper than maxDepth, we can safely exclude
// this subview without executing "test"
if (++currentDepth > maxDepth) return NO;
}
return test(subview, stop);
};
return [self subviewsPassingTest:newTest];
}
-(UIView*)firstSubviewPassingTest:(BOOL(^)(UIView *subview))test {
BOOL(^innerTestBlock)(UIView *subview, BOOL *stop) = ^BOOL(UIView *subview, BOOL *stop) {
// Stop the moment we have a passing test
BOOL passed = *stop = test(subview);
return passed;
};
NSArray *returnArray = [self subviewsPassingTest:innerTestBlock];
return [returnArray count] ? returnArray[0] : nil;
}
-(NSArray*)subviewsMatchingClass:(Class)aClass {
return [self subviewsMatchingClass:[aClass class]
includeSubclasses:NO
maxDepth:INFINITE_DEPTH];
}
-(NSArray*)subviewsMatchingClass:(Class)aClass
maxDepth:(NSInteger)depth {
return [self subviewsMatchingClass:[aClass class]
includeSubclasses:NO
maxDepth:depth];
}
-(NSArray*)subviewsMatchingClassOrSubclass:(Class)aClass {
return [self subviewsMatchingClass:[aClass class]
includeSubclasses:YES
maxDepth:INFINITE_DEPTH];
}
-(NSArray*)subviewsMatchingClassOrSubclass:(Class)aClass
maxDepth:(NSInteger)depth {
return [self subviewsMatchingClass:[aClass class]
includeSubclasses:YES
maxDepth:depth];
}
#pragma mark Private methods
-(NSArray*)subviewsMatchingClass:(Class)aClass
includeSubclasses:(BOOL)includeSubclasses
maxDepth:(NSInteger)maxDepth {
SEL comparisonSelector = includeSubclasses ? @selector(isKindOfClass:) : @selector(isMemberOfClass:);
if (maxDepth == INFINITE_DEPTH)
return [self subviewsPassingTest:^BOOL(UIView *subview, BOOL *stop) {
SuppressPerformSelectorLeakWarning(return (BOOL)[subview performSelector:comparisonSelector withObject:aClass]);
}];
else
return [self subviewsPassingTest:^BOOL(UIView *subview, BOOL *stop) {
SuppressPerformSelectorLeakWarning(return (BOOL)[subview performSelector:comparisonSelector withObject:aClass]);
} maxDepth:maxDepth];
}
@end