Get ALL views and subview of NSWindow
Asked Answered
T

7

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.

Tapes answered 27/3, 2011 at 2:58 Comment(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.Butterfield
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...Tapes
Simply add your views to an NSMutableArray to enumerate later.Melodiemelodion
S
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.

Sedimentary answered 27/3, 2011 at 5:12 Comment(3)
Works for iOS, too: NS -> UI.Ringtail
Doesn't work with ARC: *** -[NSMutableArray addObjectsFromArray:]: array argument is not an NSArrayMelodiemelodion
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.Melodiemelodion
V
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.

Vincenz answered 2/11, 2012 at 8:50 Comment(0)
F
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);
    ...
Falsework answered 28/7, 2013 at 10:23 Comment(0)
V
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.)

Various answered 27/3, 2011 at 3:8 Comment(11)
A recursive method will never end as you designed it.Butterfield
Thanks, although this will recurse infinitely until stack overflow, I will add a base case. But is this the best way?Tapes
@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.Oruntha
@Enchilada: The real problem with this method is not that it recurses infinitely, but that you are enumerating an array you are mutating.Oruntha
Why wont this recurse indefinitely? As long as there is no explicit return statement (or similar control statement) the method will recurse indefinitely.Butterfield
@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.Sedimentary
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).Sedimentary
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.Butterfield
@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.Sedimentary
@Moshe: A view is not a subview of itself.Oruntha
I'd suggest renaming the local 'subviews' to something else like collectedViews to avoid confusionPrasad
W
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>>"
)
Wert answered 15/5, 2012 at 20:53 Comment(0)
T
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];
}
Tailpipe answered 28/2, 2015 at 20:43 Comment(0)
L
-2
NSArray *arrofView = [[self view] subviews];
Lavina answered 25/6, 2012 at 11:41 Comment(1)
The OP was looking for subviews and their subviews (etc., I presume).Spindry

© 2022 - 2024 — McMap. All rights reserved.