Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unique ID on NSViews

Is there any kind of ID that can be used and set in the .nib/.xib via Xcode that can be queried at runtime to identify a particular view instance from code?

In particular when having multiple copies of the same NSView subclass in our interface how can we tell which one we're currently looking at?

like image 300
Jay Avatar asked Dec 13 '11 16:12

Jay


3 Answers

Here's how to simulate "tags" in OSX without subclassing.

In iOS:

{
    // iOS:
    // 1. You add a tag to a view and add it as a subView, as in:
    UIView *masterView = ... // the superview
    UIView *aView = ... // a subview
    aView.tag = 13;
    [masterView addSubview:aView];

    // 2.  Later, to retrieve the tagged view:
    UIView *aView = [masterView viewWithTag:13];
    // returns nil if there's no subview with that tag
}

The equivalent in OSX:

#import <objc/runtime.h> // for associated objects
{
    // OSX:
    // 1.  Somewhere early, create an invariant memory address
    static void const *tag13 = &tag13; // put at the top of the file

    // 2.  Attach an object to the view to which you'll be adding the subviews:
    NSView *masterView = ... // the superview
    NSView *aView = ...  // a subview
    [masterView addSubview:aView];
    objc_setAssociatedObject(masterView, tag13, aView, OBJC_ASSOCIATION_ASSIGN);

    // 3.  Later, to retrieve the "tagged" view:
    NSView *aView = objc_getAssociatedObject(masterView, tag13);
    // returns nil if there's no subview with that associated object "tag"
}

Edit: The associated object "key" (declared as const void *key) needs to be invariant. I'm using an idea by Will Pragnell (https://stackoverflow.com/a/18548365/236415). Search Stack Overflow for other schemes for making the key.

like image 163
Jeff Avatar answered Oct 29 '22 16:10

Jeff


Generic NSView objects cannot have their tag property set in Interface Builder. The tag method on NSView is a read-only method and can only be implemented in subclasses of NSView. NSView does not implement a setTag: method.

I suspect the other answers are referring to instances of NSControl which defines a -setTag: method and has an Interface Builder field to allow you to set the tag.

What you can do with generic views is use user-defined runtime attributes. This allows you to pre-set the values of properties in your view object. So if your view defined a property like so:

@property (strong) NSNumber* viewID;

Then in the user-defined attributes section of the Identity inspector in Interface Builder, you could add a property with the keypath viewID, the type Number and the value 123.

In your view's -awakeFromNib method, you can then access the value of the property. You will find that in the example above, the viewID property of your view will have been pre-set to 123.

like image 44
Rob Keniger Avatar answered Oct 29 '22 16:10

Rob Keniger


In Interface Builder, there is a way to set the "identifier" of an NSView. In this case, I'll use the identifier "54321" as the identifier string.

NSView Conforms to the NSUserInterfaceItemIdentification Protocol, which is a unique identifier as an NSString. You could walk through the view hierarchy and find the NSView with that identifier.

So, to build on this post about getting the list of NSViews, Get ALL views and subview of NSWindow, you could then find the NSView with the identifier you want:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSView *viewToFind = [self viewWithIdentifier:@"54321"];
}  

- (NSView *)viewWithIdentifier:(NSString *)identifier
{
    NSArray *subviews = [self allSubviewsInView:self.window.contentView];

    for (NSView *view in subviews) {
        if ([view.identifier isEqualToString:identifier]) {
            return view; 
        }
    }

    return nil;
}

- (NSMutableArray *)allSubviewsInView:(NSView *)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 (NSView *view in currentSubviews) {
            for (NSView *subview in view.subviews) [newSubviews addObject:subview];
        }

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

    }

    for (NSView *view in allSubviews) {
        NSLog(@"View: %@, tag: %ld, identifier: %@", view, view.tag, view.identifier);
    }

    return allSubviews;
}

Or, since you are using an NSView subclass, you could set the "tag" of each view at runtime. (Or, you could set the identifier at run-time.) The nice thing about tag, is that there is a pre-built function for finding a view with a specific tag.

// set the tag
NSInteger tagValue = 12345;
[self.myButton setTag:tagValue];

// find it 
NSButton *myButton = [self.window.contentView viewWithTag:12345];
like image 15
Chris Livdahl Avatar answered Oct 29 '22 16:10

Chris Livdahl