Finding Subview of a Particular Class in Cocoa

Fredrik Olsson

Many of the UIView subclasses in Cocoa, and especially Cocoa Touch are created by combining many different simple views into a more complex view. UITableViewCell is a good example, concisting of almost a dozen subviews, UIImageView for the image, UILabel for the text, etc.

It is often nice to be able to reuse existing functionality when you subclass UITableViewCell. Properties such as image and textColor maps directly to the embedded UILabel and UIImageView subviews, and reimplementing them all when introducing a custom layout is tedious and error prone. Would it not be better to be able to get access to the original subview instances and just add a custom layout to them?

You can find subviews by class

Would it not be nice if the Cocoa Touch framework had a method such as this:

- (UIView*)viewWithKindOfClass:(Class)aClass;

Kind of like what viewWithTag:, does but instead of searching for a subview matching a tag, it searches for a subview matching a class.

Good news for us is that this is very easy to implement, by just adding a new category to UIView. Add a new .h/.m file pair to your Xcode project and type this down in the header file:

#import <UIKit/UIKit.h>
 
@interface UIView (CWViewWithKindOfClass)
 
- (UIView*)viewWithKindOfClass:(Class)aClass;
 
@end

Now we have to implement it. The existing viewWithTag: method is not documented as such, but my experimentation tells me that it uses a breadth first search algorithm. Quite logical as the wanted subview most commonly is close to the root view, and we want to find it fast. Since viewWithTag: always returns the first found matching view, we can do the same assumption.

The quite short implementation goes like this:

#import "UIView+CWViewWithKindOfClass.h"
 
@implementation UIView (CWViewWithKindOfClass)
 
- (UIView*)viewWithKindOfClass:(Class)aClass;
{
  NSMutableArray* nodeQueue = [NSMutableArray arrayWithObject:self];
  while ([nodeQueue count] > 0) {
    UIView* node = [nodeQueue objectAtIndex:0];
    [nodeQueue removeObjectAtIndex:0];
    if ([node isKindOfClass:aClass]) {
      return node;
    } else {
      [nodeQueue addObjectsFromArray:node.subviews];
    }
  }
  return nil;
}
 
@end

And that is it, not you can use it with something like for example this:

UITabBar* tabBar = [myTabBarController.view viewWithKindOfClass:[UITabBar class]];

Wich is an introduction to a future post, where I will discuss how to access the managed UITabBar of a UITabBarController, and introduce a UITabBarDelegate without interfering with the delegate already in place from the Cocoa Touch framework.

Fredrik Olsson
Consultant at Jayway

Tags: , , , ,

4 comments ↓

#1 Sara on 06.23.09 at 22:52

Using the above method, I was able to get the UITabBar and hide it. I have a question though. After hiding the UITabBar, do you know how to resize the rest of the view?

Thanks,
Sara

#2 Jonah on 07.30.09 at 22:34

Sara, Can you show us the code for how you were able to use this method to hide your UITabBar?
Thanks!

#3 satya on 08.07.09 at 8:16

Hi,

I have a Tabbarcontroller that contains a navigation controller which inturn contains a tableview controller .Upon selection in tableview controller I push another Tablview controller which should always be shown in landscape mode only.But my view doesnt rotate to landscape. Earlier it worked fine when I have only navigation controller. Now as my UI changed I had to put my Nav controller in a Tab controller.

Can you tell me what could be the problem?

Thanks
Satya

#4 No one in particular on 01.16.10 at 1:33

> *Many of the UIView subclasses in Cocoa, and especially Cocoa Touch*

Cocoa doesn’t have UIView.

Leave a Comment