Lately, I've been part of a team working on a utility library for iOS called Thundercats (on Github). We're about to start working on some changes and upgrades in preparation for a 2.0 release, so we figured it might be a good time to get some of our code publicly reviewed.
The library is written in Objective-C and is set up to work with Cocoapods, but we want to make sure we the library works well from iOS 6.0 and up and with both Swift and Objective-C.
This code is from the UIView
extension. It provides an easy means of doing some common recursive searching.
UIView+TCAdditions.h
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, TCSearchStrategy) {
TCSearchStrategyBreadthFirst,
TCSearchStrategyDepthFirst
};
@interface UIView (TCAdditions)
/**
* Calls resignFirstResponder on this view or its subviews (1 level down) if the view is the first responder.
*/
- (void)tc_findAndResignFirstResponder;
/**
* Returns a flattened subview hierarchy. All subviews within this view's subview hierarchy are returned.
*
* @return An array of all the view's subviews in its subview hierarchy.
*/
- (NSArray *)tc_getAllSubviewsRecursively;
/**
* Returns the first subview it encounters that satisfies the condition block. This method uses a breadth first search strategy.
*
* @param conditionBlock The block to apply to each subview.
*
* @return The first subview encountered that satisfies the condition block.
*/
- (UIView *)tc_subviewThatSatisfiesBlock:(BOOL (^)(UIView *))conditionBlock;
/**
* Returns the first subview it encounters that satisfies the condition block. This method can perform a breadth first search strategy or a depth first search strategy.
*
* @param searchStrategy The search strategy to use.
* @param conditionBlock The block to apply to each subview.
*
* @return The first subview encountered that satisfies the condition block.
*/
- (UIView *)tc_findSubviewUsingSearchStrategy:(TCSearchStrategy)searchStrategy
thatSatisfiesBlock:(BOOL (^)(UIView *view))conditionBlock;
@end
UIView+TCAdditions.m
#import "UIView+TCAdditions.h"
@implementation UIView (TCAdditions)
- (void)tc_findAndResignFirstResponder
{
if ([self isFirstResponder])
{
[self resignFirstResponder];
return;
}
for (UIView *subview in [self subviews])
{
if ([subview isFirstResponder])
{
[subview resignFirstResponder];
return;
}
}
}
- (NSArray *)tc_getAllSubviewsRecursively
{
NSMutableArray *subviews = [NSMutableArray new];
for (UIView *subview in self.subviews)
{
[subviews addObject:subview];
[subviews addObjectsFromArray:[subview tc_getAllSubviewsRecursively]];
}
return subviews;
}
- (UIView *)tc_subviewThatSatisfiesBlock:(BOOL (^)(UIView *))conditionBlock
{
return [self tc_findSubviewUsingSearchStrategy:TCSearchStrategyBreadthFirst
thatSatisfiesBlock:conditionBlock];
}
- (UIView *)tc_findSubviewUsingSearchStrategy:(TCSearchStrategy)searchStrategy
thatSatisfiesBlock:(BOOL (^)(UIView *))conditionBlock
{
switch (searchStrategy)
{
case TCSearchStrategyBreadthFirst:
return [self tc_breadthFirstSubviewThatSatisfiesBlock:conditionBlock];
case TCSearchStrategyDepthFirst:
return [self tc_depthFirstSubviewThatSatisfiesBlock:conditionBlock];
}
}
- (UIView *)tc_depthFirstSubviewThatSatisfiesBlock:(BOOL (^)(UIView *))conditionBlock
{
for (UIView *subview in self.subviews)
{
if (conditionBlock(subview))
{
return subview;
}
else
{
UIView *view = [subview tc_depthFirstSubviewThatSatisfiesBlock:conditionBlock];
if (view)
{
return view;
}
}
}
return nil;
}
- (UIView *)tc_breadthFirstSubviewThatSatisfiesBlock:(BOOL (^)(UIView *))conditionBlock
{
NSMutableArray *nextViewsToCheck = [self.subviews mutableCopy];
NSInteger i = 0;
while (i < nextViewsToCheck.count)
{
UIView *view = nextViewsToCheck[i];
if (conditionBlock(view))
{
return view;
}
else
{
i++;
[nextViewsToCheck addObjectsFromArray:view.subviews];
}
}
return nil;
}
@end
Any comments improving the readability, efficiency, or just making the code more idiomatic Objective-C would all be more than welcome.