【问题标题】:How to setup a NSTableView with a custom cell using a subview如何使用子视图设置带有自定义单元格的 NSTableView
【发布时间】:2011-05-04 13:36:55
【问题描述】:

我正在尝试使用 ArrayControllerBindings 设置带有自定义单元格的 NSTableView。为此,我向自定义单元格添加了一个子视图。数据连接似乎有点工作。虽然,似乎有一个我无法解决的重绘问题。当我加载应用程序时,只渲染了一些单元格。当我滚动浏览行或选择一个时,渲染会发生变化。

我创建了一个example project on github 来说明问题所在。

单元格渲染的实际源码可以在here找到:

// CustomCell.m
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {

  if (![m_view superview]) {
    [controlView addSubview:m_view];
  }

  // The array controller only gets wrapped data items pack by the NSObjectTransformer.
  // Therefore, objectValue returns a NSObjectWrapper.
  // Unpack the wrapper to retreive the data item.
  DataItem* dataItem = [(NSObjectWrapper*)[self objectValue] original];
  [[m_view name] setStringValue:dataItem.name];
  [[m_view occupation] setStringValue:dataItem.occupation];
  [m_view setFrame:cellFrame];
}

似乎父 controlView 没有重绘。我可以以某种方式强制它吗?

【问题讨论】:

    标签: objective-c nstableview nscell nstextfieldcell custom-cell


    【解决方案1】:

    这几乎肯定不是最佳实践方式,我稍后会解释原因:不过,它似乎确实有效。将您的单元类的 drawInteriorWithFrame:inView: 方法替换为以下内容:

    - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
        DataItem* dataItem = [(NSObjectWrapper*)[self objectValue] original];
        [[m_view name] setStringValue:dataItem.name];
        [[m_view occupation] setStringValue:dataItem.occupation];
        [m_view setFrame:cellFrame];
    
        NSData *d = [m_view dataWithPDFInsideRect:[m_view bounds]];
        NSImage *i = [[NSImage alloc] initWithData:d];
        [i setFlipped:YES];
    
        [i drawInRect:cellFrame fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
    }
    

    问题是整个表只创建了一个NSCell。这就是单元格的工作方式:表格视图创建一个单元格,然后调用 setObject... 然后调用 drawInterior... 一遍又一遍地让单元格绘制整个表格。从效率的角度来看,这很棒(NSCell 类是在 25mhz 是一台快速计算机时设计的,因此它旨在最大限度地减少对象分配的数量),但在这里会产生问题。

    在您的代码中,您使用值填充视图,并设置其框架,如果需要,将其添加为表格视图的子视图。但是,由于您只有一个 NSCell 实例,因此只能有一个视图:您获取了您拥有的单个视图,只是将其移到表格的行中。

    要正确执行此操作,您需要一些数据结构来跟踪您作为 NSTableView 的子视图添加的所有视图,并且当单元格在 drawInterior... 方法中更新一个时,您需要查找正确的一个是并更新。您还需要在代码中分配所有这些视图(或者至少将视图移动到可以加载多个副本的单独笔尖),因为您的笔尖中只有一个,并且复制视图是痛。

    我写的代码很杂乱,因为它的效率真​​的很低。我所做的是每次视图需要绘制时,我将视图绘制到屏幕外的图像缓冲区中,然后将缓冲区绘制到表格视图中的正确位置。这样做,我避免了只有一个视图的问题,因为代码只是在需要时获取并绘制其内容的新副本。

    【讨论】:

    • 如果我错了请纠正我:我认为,通过只向表格视图提供NSObjectWrapper 对象,表格视图可以根据需要生成副本(使用copyWithZone:)。这意味着它永远不会触及包裹的CustomCellCustomCell 只会在调用 drawInteriorWithFrame 时公开其子视图。 - 我不确定我是否理解问题。
    • 好的,我检查的方法是下载您的项目并记录任何 CustomCell 对象的内存地址(使用NSLog(@"%x", self);)。只创建了其中一个。它就像橡皮图章一样用来依次绘制每一行。我认为另一个混淆点是单元格本身不能有子视图:您正在向表格视图本身添加子视图。如果你这样做,你必须跟踪你在某处添加的所有子视图,也许在一个可变数组中。因为只有一个单元格,所以需要检查行号并为每一行实例化一个新视图。
    • 另一种方法可以用于此特定示例:只需创建一个NSAttribtuedString,然后将两行文本放入其中,中间有一个换行符,并带有设置文本大小的属性。然后只需将其作为文本单元格的对象值传入,它应该可以正常工作,不需要子类化单元格。
    • 目的是能够在界面构建器中直观地布局表格单元格(视图)。这就是为什么我选择了您在示例实现中看到的开销。 - 太糟糕了,Apple 让 MacOS 变得如此困难。 - 我想,我会放弃这个并手动“绘制”内容。非常感谢您提供宝贵的提示!
    • 没问题。您可能还喜欢 Lion 中即将推出的内容! (顺便说一句,你试过我的代码了吗?我不知道减速会有多糟糕,在我的机器上看起来还不错,但我确实有一个四核 iMac。如果你在一些速度较慢的 Mac 上测试它并且没问题,那么它确实可以满足您的需求,并允许您在 IB 中进行布局。)
    【解决方案2】:

    编辑:请参阅我的其他答案以获得解释

    你实现copyWithZone:了吗?您需要确保在该方法中复制或重新创建视图,否则不同的单元格最终将共享一个视图(因为 NSTableView 复制其单元格)。

    【讨论】:

    • 我在NSObjectWrapper.m 中实现了copyWithZone,它提供给表格视图(如上面的代码cmets 中所述)。
    • 我的意思是在 CustomCell.m 中实现它。 NSCell 使用 NSCopying 协议,根据文档“如果子类从其超类继承 NSCopying 并声明其他实例变量,则子类必须重写 copyWithZone: 以正确处理自己的实例变量,首先调用超类的实现。”跨度>
    • 不,我没有。我在 InterfaceBuilder 中使用值转换器 NSObjectTransformer 仅管理表视图中的 NSObjectWrapper 对象。我试图使example on github 易于理解。如果您克隆项目,可能会更好地理解。
    猜你喜欢
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    • 2017-11-13
    • 2017-10-15
    • 1970-01-01
    • 1970-01-01
    • 2017-10-01
    • 1970-01-01
    相关资源
    最近更新 更多