NSViews上的唯一标识符

13

在使用Xcode时,是否有任何可以在.nib/.xib中设置和使用的ID,可以在运行时从代码中查询以识别特定视图实例?

特别是在我们的界面中有多个相同的NSView子类副本时,我们如何确定我们当前正在查看哪个副本?

4个回答

15
在Interface Builder中,有一种方法可以设置NSView的“标识符”。在这种情况下,我将使用标识符字符串“54321”作为标识符。
NSView符合NSUserInterfaceItemIdentification Protocol,它是一个唯一的NSString标识符。您可以遍历视图层次结构并找到具有该标识符的NSView。
因此,在这篇关于获取NSViews列表的文章Get ALL views and subview of NSWindow的基础上,您可以找到具有所需标识符的NSView。
- (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;
}

或者,由于您正在使用NSView子类,您可以在运行时设置每个视图的“标记”。 (或者,您可以在运行时设置标识符。)标记的好处在于,有一个预先构建的函数可用于查找具有特定标记的视图。

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

// find it 
NSButton *myButton = [self.window.contentView viewWithTag:12345];

12

通用的NSView对象无法在Interface Builder中设置其tag属性。在NSView上,tag方法是只读方法,只能在NSView的子类中实现。 NSView没有实现setTag:方法。

我怀疑其他答案所提到的是NSControl的实例,它定义了一个-setTag:方法,并且有一个Interface Builder字段可以让你设置标签。

对于通用视图,您可以使用用户定义的运行时属性。这使您可以预设视图对象中属性的值。因此,如果您的视图定义了以下属性:

@property (strong) NSNumber* viewID;

然后在Interface Builder中Identity inspector的用户自定义属性部分,您可以添加一个具有键路径viewID、类型Number和值123的属性。

然后,在您的视图的 -awakeFromNib 方法中,您可以访问该属性的值。在上面的示例中,您的视图的 viewID 属性将被预设为 123


听起来很不错,谢谢提示!我会尝试的。哎呀,在原始的Cocoa NSView中没有预定义的东西可用,但我想我还是太过于以PowerPlant/MFC的方式思考了;-) - Jay
1
对于通用视图,下面的答案可以奏效。只需在xib中设置标识符,然后使用view.identifier进行程序访问即可。 - Colin
你有没有想过为什么在NSView中标签是只读的逻辑? - Mercurial
不知何故,我无法在Interface Builder中显示我的NSView子类中的新成员字段。最终决定使用view.identifier,因为我只需要在windowDidLoad中找到某些控件一次,所以在那里分析字符串是可以的。 - mojuba
就我个人而言,在某些情况下,考虑到我的视图层次结构,我现在有时会检查输入视图的所有子视图(或父级)以查找特定类别的视图。这通常有助于找到“我的集合视图项中那个文本字段”或“我的内容视图的父剪辑视图”之类的东西。 - Jay

2

以下是如何在OSX模拟“标签”而无需子类化。

在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
}

在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"
}

编辑:关联对象“key”(声明为const void *key)需要是不变的。我正在使用Will Pragnell的一个想法(https://dev59.com/OWQo5IYBdhLWcg3wfPUa#18548365)。 在Stack Overflow上搜索其他制作密钥方案。


-1

这是一种简单的方法,在OSX中获取NSView标签而无需子类化。

尽管NSView的标签属性是只读的,但是一些继承自NSView的对象具有读/写标签属性。 例如,NSControlNSImageView具有读/写标签属性。

因此,简单地使用NSControl而不是NSView,并禁用(或忽略)NSControl的内容即可。

- (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
}

稍后,如果您使用viewWithTag:获取视图,请确保指定返回类型为NSControl(或NSImageView)。


无法在NSView上设置标签,因此在一般情况下无法工作。 - Jay
Jay,我不建议你使用NSView。相反,使用一个允许设置标签的子类。你仍然拥有所有的NSView参数。我在许多情况下都使用它,效果很好。我的建议解决了OP的问题,所以如果你做了-1,请将其删除。 - Jeff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接