View-based NSOutlineView without NIB?

不想你离开。 提交于 2019-11-29 14:28:59

问题


NSOutlineView is a subclass of NSTableView. And currently, NSTableView supports two implementations.

  • Cell-based.
  • View-based.

To make OSX 10.8 Finder style side bar (with automatic gray Icon styling), need to use view-based table view with source-list highlight style.

With NIBs, this is typical job. Nothing hard. (see SidebarDemo) But I want to avoid any NIBs or Interface Builder. I want make the side bar purely programmatically.

In this case, I have big problem. AFAIK, there's no way to supply prototype view for specific cell. When I open .xib file, I see <tableColumn> is containing <prototypeCellViews>. And this specifies what view will be used for the column. I can't find how to set this programmatically using public API.

As a workaround, I tried to make cell manually using -[NSTableView makeViewWithIdentifier:owner:] and -[NSTableView viewAtColumn:row:makeIfNecessary:], but none of them returns view instance. I created a NSTableCellView, but it doesn't have image-view and text-field instances. And I also tried to set them, but the fields are marked as assign so the instances deallocated immediately. I tried to keep it by forcing retaining them, but it doesn't work. NSTableView doesn't manage them, so I am sure that table view don't like my implementation.

I believe there's a property to set this prototype-view for a column. But I can't find them. Where can I find the property and make system-default NSOutlineView with source-list style programmatically?


回答1:


If you follow the example in SidebarDemo, they use a subclass of NSTableCellView for the detail rows. In order to emulate the InterfaceBuilder mojo, you can hook everything together in the constructor. The rest is the same as the demo (see outlineView:viewForTableColumn:item:).

@interface SCTableCellView : NSTableCellView
@end

@implementation SCTableCellView

- (id)initWithFrame:(NSRect)frameRect {
  self = [super initWithFrame:frameRect];
  [self setAutoresizingMask:NSViewWidthSizable];
  NSImageView* iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 6, 16, 16)];
  NSTextField* tf = [[NSTextField alloc] initWithFrame:NSMakeRect(21, 6, 200, 14)];
  NSButton* btn = [[NSButton alloc] initWithFrame:NSMakeRect(0, 3, 16, 16)];
  [iv setImageScaling:NSImageScaleProportionallyUpOrDown];
  [iv setImageAlignment:NSImageAlignCenter];
  [tf setBordered:NO];
  [tf setDrawsBackground:NO];
  [[btn cell] setControlSize:NSSmallControlSize];
  [[btn cell] setBezelStyle:NSInlineBezelStyle];
  [[btn cell] setButtonType:NSMomentaryPushInButton];
  [[btn cell] setFont:[NSFont boldSystemFontOfSize:10]];
  [[btn cell] setAlignment:NSCenterTextAlignment];
  [self setImageView:iv];
  [self setTextField:tf];
  [self addSubview:iv];
  [self addSubview:tf];
  [self addSubview:btn];
  return self;
}

- (NSButton*)button {
  return [[self subviews] objectAtIndex:2];
}

- (void)viewWillDraw {
  [super viewWillDraw];
  NSButton* btn = [self button];
  ...



回答2:


In addition to @jeberle 's answer, I need to note something more.

  • The key to keep the text-field and image-view is adding them as subviews of the NSTableCellView.

  • Set NSTableView.rowSizeStyle to a proper value (non-Custom which is default value) to make the table-view layout them automatically. Otherwise, you have to layout them completely yourself.

  • Do not touch frame and autoresizing stuffs if you want to use predefined NSTableViewRowSizeStyle value. Otherwise, the layout might be broken.

  • You can adjust row-height by providing private func outlineView(outlineView: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat delegate method. Setting NSTableView.rowHeight is not a good idea because it needs NSTableView.rowSizeStyle set to Custom which will turn off cell text/image layout management provided by default.

  • You can reuse row/cell views by settings NSView.identifier property. (example)




回答3:


Here's @jeberle's code re-written in Swift 4 (five years later!):

class ProgrammaticTableCellView: NSTableCellView {
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)

        self.autoresizingMask = .width
        let iv: NSImageView = NSImageView(frame: NSMakeRect(0, 6, 16, 16))
        let tf: NSTextField = NSTextField(frame: NSMakeRect(21, 6, 200, 14))
        let btn: NSButton = NSButton(frame: NSMakeRect(0, 3, 16, 16))
        iv.imageScaling = .scaleProportionallyUpOrDown
        iv.imageAlignment = .alignCenter
        tf.isBordered = false
        tf.drawsBackground = false
        btn.cell?.controlSize = .small
        // btn.bezelStyle = .inline                  // Deprecated?
        btn.cell?.isBezeled = true                   // Closest property I can find.
        // btn.cell?.setButtonType(.momentaryPushIn) // Deprecated?
        btn.setButtonType(.momentaryPushIn)
        btn.cell?.font = NSFont.boldSystemFont(ofSize: 10)
        btn.cell?.alignment = .center

        self.imageView = iv
        self.textField = tf
        self.addSubview(iv)
        self.addSubview(tf)
        self.addSubview(btn)
    }

    required init?(coder decoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    var button: NSButton {
        get {
            return self.subviews[2] as! NSButton
        }
    }
}

Edit: I found a link (that will inevitably rot away – it was last revised in 2011) to Apple's SidebarDemo that @jeberle based his code on.



来源:https://stackoverflow.com/questions/19464136/view-based-nsoutlineview-without-nib

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!