Jay
Jay

Reputation: 6638

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?

Upvotes: 13

Views: 8409

Answers (4)

Jeff
Jeff

Reputation: 2699

Here's a simple way get NSView tags in OSX without subclassing.

Although NSView's tag property is read-only, some objects that inherit from NSView have a read/write tag property. For example, NSControl and NSImageView have a r/w tag properties.

So, simply use NSControl instead of NSView, and disable (or ignore) NSControl things.

- (void)tagDemo
{
    NSView *myView1 = [NSView new];
    myView1.tag = 1; // Error: "Assignment to readonly property"

    // ---------

    NSControl *myView2 = [NSControl new]; // inherits from NSView
    myView2.tag = 2; // no error
    myView2.enabled = NO; // consider
    myView2.action = nil; // consider

    // ---------

    NSImageView *myView3 = [NSImageView new]; // inherits from NSControl
    myView3.tag = 3; // no error
    myView3.enabled = NO; // consider
    myView3.action = nil; // consider
}

Later, if you use viewWithTag: to fetch the view, be sure to specify NSControl (or NSImageView) as the returned type.

Upvotes: -1

Jeff
Jeff

Reputation: 2699

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.

Upvotes: 2

Chris Livdahl
Chris Livdahl

Reputation: 4740

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];

Upvotes: 15

Rob Keniger
Rob Keniger

Reputation: 46020

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.

Upvotes: 12

Related Questions