12

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.

user635064
  • 6,219
  • 12
  • 54
  • 100
  • 3
    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 Mar 27 '11 at 03:08
  • 2
    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 Mar 27 '11 at 03:29
  • Simply add your views to an NSMutableArray to enumerate later. – Sparky Apr 26 '18 at 05:47

7 Answers7

10

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.

NSGod
  • 22,699
  • 3
  • 58
  • 66
  • Works for iOS, too: NS -> UI. – clozach Aug 28 '12 at 16:24
  • Doesn't work with ARC: *** -[NSMutableArray addObjectsFromArray:]: array argument is not an NSArray – Sparky Apr 26 '18 at 03:11
  • The "easier ways of doing things" is to simply add your views to an NSMutableArray, and have that NSMutableArray accessible on a larger scope. It's far more efficient to do that also. – Sparky Apr 26 '18 at 05:46
9

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.

Rob van der Veer
  • 1,148
  • 1
  • 7
  • 20
Samit
  • 186
  • 2
  • 4
4
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);
    ...
Leo
  • 790
  • 8
  • 10
3

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.)

Flavien Volken
  • 19,196
  • 12
  • 100
  • 133
Enchilada
  • 3,859
  • 1
  • 36
  • 69
  • A recursive method will never end as you designed it. – Moshe Mar 27 '11 at 03:10
  • Thanks, although this will recurse infinitely until stack overflow, I will add a base case. But is this the best way? – user635064 Mar 27 '11 at 03:32
  • 1
    @Enchilada: Don't forget to release the array. @user635064: No, it will not recurse infinitely. It could if you added a view as a subview of one of its own subviews, but then it would be a closed cycle, not reachable from a window's `contentView`. – Peter Hosey Mar 27 '11 at 03:41
  • 2
    @Enchilada: The real problem with this method is not that it recurses infinitely, but that you are enumerating an array you are mutating. – Peter Hosey Mar 27 '11 at 03:44
  • Why wont this recurse indefinitely? As long as there is no explicit `return` statement (or similar control statement) the method will recurse indefinitely. – Moshe Mar 27 '11 at 04:44
  • @Moshe: it will only recurse as deep as there are views in the view hierarchy. Unless you can create a nib file or programmatically create a view hierarchy that has an infinite number of subviews, the very act of which will result in the "first" infinite recursion rather than one returned from that method. – NSGod Mar 27 '11 at 05:07
  • 1
    Bah, can't edit my previous comment. Here's a better one: @Moshe: it will only recurse as deep as there are views in the view hierarchy. So, no infinite recursion unless you can create a nib file or programmatically create a view hierarchy that has an infinite number of subviews (the very act of which will result in the "first" infinite recursion rather than any encountered in calling that method). – NSGod Mar 27 '11 at 05:16
  • Why won't this recurse indefinitely? Each call calls itelf, there's no stop to it. You'll never reach the second view, no matter how many there are. Also, why not use the method `subviews`? It works and is build in to the SDK. – Moshe Mar 27 '11 at 05:18
  • @Moshe: eventually—take an `NSButton` for example—when it calls this method, `[self subviews]` will be an empty array, which won't be recursed into, and will allow the `for/in` statement to move on to the next item. – NSGod Mar 27 '11 at 05:33
  • @Moshe: A view is not a subview of itself. – Peter Hosey Mar 28 '11 at 08:09
  • I'd suggest renaming the local 'subviews' to something else like `collectedViews` to avoid confusion – Rob van der Veer Jul 28 '13 at 10:43
2

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>>"
)
Community
  • 1
  • 1
smlxl
  • 49
  • 1
  • 4
0

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];
}
barryjones
  • 2,149
  • 1
  • 17
  • 21
-2
NSArray *arrofView = [[self view] subviews];
Jayendra
  • 73
  • 1
  • 8