12
votes

Is there a way I can get ALL the views and subviews and subviews of these subviews (you get the idea...) of an NSWindow?

Thanks.

7
Welcome to StackOverflow! Why not change you username from user635064 to something more unique? Also, you'll be more likely to get help if you mark correct answers as such.Moshe
Agreed about the user name, but don't know why you are telling me about marking question correct? I always mark a question correct if it helped me out...user635064
Simply add your views to an NSMutableArray to enumerate later.Sparky

7 Answers

10
votes

Here is a category on NSView:

@interface NSView (MDRecursiveSubviews)
- (NSArray *)md__allSubviews;
@end

@implementation NSView (MDRecursiveSubviews)

- (NSArray *)md__allSubviews {
    NSMutableArray *allSubviews = [NSMutableArray arrayWithObject:self];
    NSArray *subviews = [self subviews];
    for (NSView *view in subviews) {
        [allSubviews addObjectsFromArray:[view md__allSubviews]];
    }
    return [[allSubviews copy] autorelease];
}

@end

With a quick nib file I created with a view hierarchy, it printed this:

[RecursiveSubviewsAppDelegate awakeFromNib] allSubviews == (
    "<NSView: 0x10390dfd0>",
    "<NSView: 0x103c07ae0>",
    "<NSView: 0x100129cc0>",
    "<NSButton: 0x100115ce0>",
    "<NSButton: 0x100116900>",
    "<NSButton: 0x1001165c0>",
    "<NSButton: 0x100116130>",
    "<NSButton: 0x100114ad0>",
    "<NSButton: 0x100115910>",
    "<NSButton: 0x100115090>",
    "<NSScrollView: 0x103b07a30>",
    "<NSClipView: 0x103b07d40>",
    "<NSTextView: 0x103b083c0>\n
Frame = {{0.00, 0.00}, {159.00, 58.00}},
Bounds = {{0.00, 0.00}, {159.00, 58.00}}\n
Horizontally resizable: NO, Vertically resizable: YES\n
MinSize = {159.00, 58.00}, MaxSize = {463.00, 10000000.00}\n",
    "<NSScroller: 0x1001145b0>",
    "<NSScroller: 0x100114840>",
    "<NSScrollView: 0x10390ea00>",
    "<NSClipView: 0x10390ef10>",
    "<NSTableView: 0x10390f570>",
    "<NSScroller: 0x103b06f10>",
    "<NSScroller: 0x103b07460>",
    "<NSClipView: 0x1039105d0>",
    "<NSTableHeaderView: 0x103910300>",
    "<_NSCornerView: 0x103911c20>"

One note of concern I should add is that it's unclear to me how this would be useful, except as a debugging tool. But even then, there are probably easier ways of doing things.

9
votes

There's a private message that can be sent on NSView to print the control hierarchy.

[NSView _subtreeDescription] gives you the whole hierarchy of the NSView i.e. its children and their children.

4
votes
static void dumpViews(NSView* v, int level) {
    NSString* indent = @"";
    for (int i = 0; i < level; i++) {
        indent = [indent stringByAppendingString:@"    "];
    }
    NSLog(@"%@%@ %@", indent, [v class], NSStringFromRect(v.frame));
    if (v.subviews != null) {
        for (id s in v.subviews) {
            dumpViews(s, level + 1);
        }
    }
}

- (void) windowControllerDidLoadNib: (NSWindowController*) controller {
    NSWindow* window = controller.window;
    dumpViews(window.contentView, 0);
    ...
3
votes

If I understood you correctly, you would have to create a method that calls itself recursively. Something like this:

- (NSArray *)allSubviewsOfView:(NSView *)view
{
  NSMutableArray *subviews = [[view subviews] mutableCopy];
  for (NSView *subview in [view subviews])
    [subviews addObjectsFromArray:[self allSubviewsOfView:subview]]; //recursive
  return subviews;
}

You would then call something like

NSArray *allSubviewsOfWindow = [self allSubviewsOfView:[window contentView]];

to get your views. (And don't forget to do memory management if you're not using GC.)

2
votes

Not sure about infinite recursion in the other answers but you definitely can't modify an array while you are doing a fast enumeration on it. Here's a method I wrote for iOS that iterates through all subviews until there aren't any more left to explore. I assume the same or similar would work for NSView. Hope this helps.

(Edit: now… if I had just looked a little further down my search results, I would have found this really easy way to do it first: How can I loop through all subviews of a UIView, and their subviews and their subviews )

- (NSMutableArray *)allSubviewsInView:(UIView *)parentView {

    NSMutableArray *allSubviews     = [[NSMutableArray alloc] initWithObjects: nil];
    NSMutableArray *currentSubviews = [[NSMutableArray alloc] initWithObjects: parentView, nil];
    NSMutableArray *newSubviews     = [[NSMutableArray alloc] initWithObjects: parentView, nil];

    while (newSubviews.count) {

        [newSubviews removeAllObjects];

        for (UIView *view in currentSubviews) {

            for (UIView *subview in view.subviews) [newSubviews addObject:subview];

        }

        [currentSubviews removeAllObjects];
        [currentSubviews addObjectsFromArray:newSubviews];
        [allSubviews addObjectsFromArray:newSubviews];

    } 

    NSLog(@"\n%d total subviews:\n%@",allSubviews.count, allSubviews);

    return allSubviews;

}

Logged results from a sample view:

18 total subviews:
(
    "<UIRoundedRectButton: 0x6a7a590; frame = (26 20; 72 37); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x6a772d0>>",
    "<UISwitch: 0x6a7f930; frame = (26 76; 79 27); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x6a7fa20>>",
    "<UIImageView: 0x685e4a0; frame = (82 20; 139 139); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x685eca0>>",
    "<UITextView: 0x6893e40; frame = (20 196; 192 79); text = 'Lorem ipsum dolor sit er ...'; clipsToBounds = YES; autoresize = RM+BM; layer = <CALayer: 0x687c330>; contentOffset: {0, 0}>",
    "<UIView: 0x6a88af0; frame = (26 304; 198 123); autoresize = RM+BM; tag = 1; layer = <CALayer: 0x6a88b20>>",
    "<UIButtonLabel: 0x6a7f410; frame = (0 0; 0 0); clipsToBounds = YES; hidden = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6a7f4d0>>",
    "<_UISwitchInternalView: 0x6a7fa50; frame = (-1 0; 79 27); layer = <CALayer: 0x6a79b90>>",
    "<UITextSelectionView: 0x6894070; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x68940d0>>",
    "<UIImageView: 0x68924b0; frame = (0 72; 192 7); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x6892520>>",
    "<UIImageView: 0x6894100; frame = (185 0; 7 79); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x6894180>>",
    "<UIWebDocumentView: 0x7416600; frame = (0 0; 192 394); text = 'Lorem ipsum dolor sit er ...'; opaque = NO; userInteractionEnabled = NO; layer = <UIWebLayer: 0x6895330>>",
    "<UIView: 0x6a88b50; frame = (10 10; 80 80); autoresize = W+H; tag = 2; layer = <CALayer: 0x6a88b80>>",
    "<UIImageView: 0x6a80610; frame = (1 0; 77 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6a80650>>",
    "<UIView: 0x6a806f0; frame = (1 0; 77 27); clipsToBounds = YES; alpha = 0; layer = <CALayer: 0x6a80720>>",
    "<UIView: 0x6a88fa0; frame = (5 5; 50 50); autoresize = W+H; tag = 3; layer = <CALayer: 0x6a88fd0>>",
    "<UIImageView: 0x6a808e0; frame = (-2 0; 79 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6a84d30>>",
    "<UIImageView: 0x6a84c00; frame = (-2 0; 131 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6a84d90>>",
    "<UIImageView: 0x6a84c40; frame = (49 0; 29 27); userInteractionEnabled = NO; layer = <CALayer: 0x6a84c80>>"
)
0
votes

The above solution will not work if there are views within views. We would need to do a recursive solution as shown below.

-(void) printViewHierarchy: (UIView * ) view withTag: (NSInteger) tag {
  if (view == nil) {
    return;
  }
  if (view == nil || [view tag] == tag) {
    NSLog(@"%@", view);
    return;
  }
  for (UIView * subview in [view subviews]) {
    [self printViewHierarchy: subview withTag: tag];
  }
}

- (void) findAllViews {
    
    UIView * baseView = [UIView new];
    [baseView setTag: 1];
    
    UIView * secondRowA = [UIView new];
    [secondRowA setTag: 2];
    
    UIView * secondRowB = [UIView new];
    [secondRowB setTag: 3];
    
    UIView * thirdRowA = [UIView new];
    [thirdRowA setTag: 4];
  
    UIView * thirdRowB = [UIView new];
    [thirdRowB setTag: 5];
    
    [secondRowA addSubview: thirdRowA];
    [secondRowB addSubview: thirdRowB];
    
    [baseView addSubview: secondRowA];
    [baseView addSubview: secondRowB];
  
    [self printViewHierarchy: baseView withTag: 6];
}
-2
votes
NSArray *arrofView = [[self view] subviews];