HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

iOS Utility methods for UIView

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
iosutilitymethodsuiviewfor

Problem

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

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


Solution

I have only some small suggestions. The

- (NSArray *)tc_getAllSubviewsRecursively


method creates a (temporary) mutable array for each subview in the
hierarchy. It might be more effective to create only a single array
and passing this array around to append subviews recursively:

- (NSArray *)tc_getAllSubviewsRecursively {
    NSMutableArray *subviews = [NSMutableArray new];
    [self tc_addSubviewsRecursivelyTo:subviews];
    return subviews;
}

// Private helper method:
- (void)tc_addSubviewsRecursivelyTo:(NSMutableArray *)subviews {
    for (UIView *subview in self.subviews) {
        [subviews addObject:subview];
        [subview tc_addSubviewsRecursivelyTo:subviews];
    }
}


If you decide to use Xcode 7 and the latest OS X SDK at some point
then you can use the Objective-C "Lightweight Generics" to specify
that the returned array contains UIView objects:

- (NSArray *)tc_getAllSubviewsRecursively;


so that this causes a compiler warning:

NSString *foo = [self.view tc_getAllSubviewsRecursively][0];
// Incompatible pointer types initializing 'NSString *' with an expression of type 'UIView *'


The

- (void)tc_findAndResignFirstResponder;


method searches only one level deep for the first responder. You could
implement it using the recursive search as

- (void)tc_findAndResignFirstResponder {
    if ([self isFirstResponder]) {
        [self resignFirstResponder];
        return;
    }

    UIView *subview = [self tc_subviewThatSatisfiesBlock:^BOOL(UIView *view) {
        return [view isFirstResponder];
    }];
    [subview resignFirstResponder];
}


where the final [subview resignFirstResponder] does nothing if
the search return nil.

This would become even more simpler if you change the search methods
to include the receiver itself in the search instead of starting
the search at its subviews.

In both tc_depthFirstSubviewThatSatisfiesBlock: and
tc_breadthFirstSubviewThatSatisfiesBlock the else is not necessary
because the method returns in the if case. This saves one
indentation level:

- (UIView *)tc_depthFirstSubviewThatSatisfiesBlock:(BOOL (^)(UIView *))conditionBlock
{
    for (UIView *subview in self.subviews) {
        if (conditionBlock(subview)) {
            return subview;
        }
        UIView *view = [subview tc_depthFirstSubviewThatSatisfiesBlock:conditionBlock];
        if (view) {
            return view;
        }
    }
    return nil;
}

Code Snippets

- (NSArray *)tc_getAllSubviewsRecursively
- (NSArray *)tc_getAllSubviewsRecursively {
    NSMutableArray *subviews = [NSMutableArray new];
    [self tc_addSubviewsRecursivelyTo:subviews];
    return subviews;
}

// Private helper method:
- (void)tc_addSubviewsRecursivelyTo:(NSMutableArray *)subviews {
    for (UIView *subview in self.subviews) {
        [subviews addObject:subview];
        [subview tc_addSubviewsRecursivelyTo:subviews];
    }
}
- (NSArray<UIView *> *)tc_getAllSubviewsRecursively;
NSString *foo = [self.view tc_getAllSubviewsRecursively][0];
// Incompatible pointer types initializing 'NSString *' with an expression of type 'UIView *'
- (void)tc_findAndResignFirstResponder;

Context

StackExchange Code Review Q#97830, answer score: 7

Revisions (0)

No revisions yet.