基于视图的NSTableView编辑

时间:2015-02-02 15:27:20

标签: cocoa nstableview

我不确定我做错了什么。由于我无法找到关于此问题的任何其他问题(甚至是文档),因此通常情况下通常对其他人没有问题。

我只是想尝试使用基于NSTableView的视图来支持编辑它的内容。即该应用程序显示带有一列和多行的NSTableView,其中包含带有一些内容的NSTextField。我希望能够(双)单击一个单元格并编辑单元格的内容。所以基本上是基于NSTableView的单元格的正常行为,其中实现了tableView:setObjectValue:forTableColumn:row:方法。

我分析了Apple的TableViewPlayground示例代码中的Complex TableView示例(支持编辑单元格内容),但我找不到启用编辑的设置/代码/开关。

这是一个简单的示例项目(Xcode 6.1.1,SDK 10.10,基于故事板):

部首:

#import <Cocoa/Cocoa.h>

@interface ViewController : NSViewController

@property (weak) IBOutlet NSTableView *tableView;

@end

实现:

#import "ViewController.h"

@implementation ViewController
{
    NSMutableArray* _content;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _content = [NSMutableArray array];

    for(NSInteger i = 0; i<10; i++) {
        [_content addObject:[NSString stringWithFormat:@"Item %ld", i]];
    }
}

#pragma mark - NSTableViewDataSource
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return _content.count;
}

#pragma mark - NSTableViewDelegate
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    NSTableCellView* cell = [tableView makeViewWithIdentifier:@"CellView" owner:self];
    cell.textField.stringValue = _content[row];
    return cell;
}

- (IBAction)endEditingText:(id)sender {
    NSInteger row = [_tableView rowForView:sender];
    if (row != -1) {
        _content[row] = [sender stringValue];
    }
}
@end

故事板文件如下所示: Storyboard

表视图的数据源和委托设置为视图控制器。 运行此应用程序时,表视图显示10个测试行,但无法编辑其中一行。

为什么?我在这里想念的是什么?

我仔细检查了NSTableView的所有属性(及其内容)与Apple的TableViewPlayground示例中的相同。经过几个小时的搜索文档和互联网获取有用的提示没有任何成功,我有点沮丧。您在基于视图的NSTableViews上找到的所有内容都是不可编辑的样本,或者是关于可编辑内容的非常模糊的信息。当然,在可编辑的基于单元格的NSTableViews上有大量的信息,文档和样本...

我的示例项目的zip可以在这里下载: TableTest.zip

3 个答案:

答案 0 :(得分:46)

尽管问题和答案中存在用于编辑基于NSTableView的视图的所有部分,但我仍然无法将它们放在一起。以下演示使用Xcode 6.3.2在Swift中进行,但对于Objective-C cavemen / womens应该很容易理解。完整的代码清单就在最后。

让我们从这里开始:

  

NSTableViewDataSource协议参考

     

设定值

     

- tableView:setObjectValue:forTableColumn:row:

Swift:  
optional func tableView(_ aTableView: NSTableView,
              setObjectValue anObject: AnyObject?,
              forTableColumn aTableColumn: NSTableColumn?,
              row rowIndex: Int)

Objective-C:
- (void)tableView:(NSTableView *)aTableView
   setObjectValue:(id)anObject
   forTableColumn:(NSTableColumn *)aTableColumn
              row:(NSInteger)rowIndex
     

讨论:此方法适用于基于单元格的表视图,不得与基于视图的表视图一起使用。在   基于视图的表,使用目标/操作来设置视图中的每个项目   细胞

如果你像我一样,你搜索了NSTableViewDelegate和NSTableViewDataSource协议,寻找某种编辑方法。但是,上面引用的讨论告诉你事情要简单得多。

1)如果您在Xcode中查看TableView的文档大纲,您会看到以下内容:

enter image description here

TableView中的单元格由Table Cell View表示。单元格包含一些项目,默认情况下,其中一个项目是NSTextField。但是文档大纲中的NSTextField在哪里?!好吧,文档大纲中的控件有一个图标,看起来就像名字旁边的滑块。看一看。在单元格内部,您会看到旁边有滑块图标的内容。而且,如果您在文档大纲中选择该行,然后打开Identity Inspector,您将看到它是NSTextField:

enter image description here

您可以认为这只是一个普通的旧NSTextField。

实施NSTableViewDataSource协议方法时:

import Cocoa

class MainWindowController: NSWindowController,
                            NSTableViewDataSource {
    ...
    ...
    var items: [String] = []  //The data source: an array of String's
    ...
    ...

    // MARK: NSTableViewDataSource protocol methods

    func numberOfRowsInTableView(tableView: NSTableView) -> Int {
        return items.count
    }

    func tableView(tableView: NSTableView,
                   objectValueForTableColumn tableColumn: NSTableColumn?,
                   row: Int) -> AnyObject? {
        return items[row]
    }

}

.. TableView获取第二个方法返回的值,并将其分配给外部objectValue中名为Table Cell View的属性 - 换句话说,TableView不使用返回的值来设置NSTextField(即内部Table View Cell)。这意味着您的数据源项目不会显示在TableView中,因为NSTextField是显示项目的内容。要设置NSTextField的值,您需要将NSTextField的value连接或绑定到objectValue属性。您可以在Bindings Inspector中执行此操作:

  

警告:确保在选择要绑定的对象之前,不要检查Bind to复选框。如果你检查一下   复选框首先,一个对象将插入到您的文档大纲中,   如果你没有注意到它,你运行时会出现错误   程序。如果您不小心首先检查Bind to复选框,请执行   确保删除文档中自动添加的对象   大纲。 Xcode为添加的对象创建一个单独的部分,因此很容易在文档大纲中找到它。

enter image description here

2)回溯片刻,您可能熟悉将按钮连接到操作方法,此后如果您点击按钮操作方法将执行。另一方面,使用NSTextField,您通常会声明一个IBOutlet,然后使用它来获取或设置NSTextField stringValue

但是,NSTextField也可以触发 action 方法的执行。什??!但你不能像按钮一样点击NSTextfield!然而,NSTextField有一个触发器,就像一个按钮单击,这将导致执行 action 方法,触发器是:完成编辑NSTextField 。 NSTextField如何知道您何时完成编辑?有两种方法:

  1. 你点击了返回。

  2. 您点击其他控件。

  3. 您可以在属性检查器中选择触发器:

    enter image description here

    3)正如@Paul Patterson在回答中所示,您需要做的下一件事是将NSTextField Behavior设置为可编辑在属性检查器中。

    4)然后将NSTextField连接到要执行的操作方法。如果您还没有使用以下技巧将控件连接到操作方法,那么您应该尝试一下:

      

    在Project Navigator中选择您的.xib文件,以便窗口和   显示其控件。然后单击助理编辑器(   最右侧Xcode窗口顶部的two_interlocking_rings图标 - 将显示您的Controller文件(如果显示其他文件,则使用跳转栏导航到您的Controller文件)。然后按Control +拖动   从NSTextField(在文档大纲中)到您的位置   要在其中创建操作方法的控制器文件:

    enter image description here

    发布时,您会看到此弹出窗口:

    enter image description here

    如果输入的信息与显示的信息相同,则会在文件中输入以下代码:

    @IBAction func onEnterInTextField(sender: NSTextField) {
    
    }
    

    并且......已经完成了NSTextField和action方法之间的连接。 (您也可以使用这些步骤创建和连接IBOutlet。)

    5)在操作方法中,您可以从TableView获取当前选定的行,即刚刚编辑过的行:

    @IBAction func onEnterInTextField(sender: NSTextField) {    
        let selectedRowNumber = tableView.selectedRow  //tableView is an IBOutlet connected to the NSTableView
    
    }
    

    然后我对如何在TableView中获取所选行的文本感到困惑,然后回到我通过TableView和协议方法查看的文档。但是,我们都知道如何获取NSTextField的stringValue,对吧? 发件人是您正在编辑的NSTextField:

    @IBAction func onEnterInTextField(sender: NSTextField) {
        let selectedRowNumber = tableView.selectedRow  //My Controller has  an IBOutlet property named tableView which is connected to the TableView 
    
        if selectedRowNumber != -1 {  //-1 is returned when no row is selected in the TableView
            items[selectedRowNumber] = sender.stringValue  //items is the data source, which is an array of Strings to be displayed in the TableView
        }
    
    }
    

    如果您未在数据源中插入新值,则下次TableView需要显示行时,原始值将再次显示,覆盖已编辑的更改。请记住,TableView从数据源检索值 - 而不是NSTextField。然后NSTextField显示TableView分配给单元格的objectValue属性的任何值。

    最后一件事:我收到一条警告,说我无法将NSTextField连接到类中的动作,如果该类不是TableView的委托者...... .so我将TableView的代理插座连接到File的所有者:

    enter image description here

    我之前已将File的所有者设置为我的Controller(= MainWindowController),因此在我建立连接后,MainWindowController(包含NSTextField的action方法)成为TableView的委托,警告消失了。

    随机提示:

    1)我发现启动编辑NSTextField的最简单方法是在TableView中选择一行,然后点击Return。

    2)默认情况下,NSTableView有两列。如果选择文档大纲中的一列,然后单击键盘上的Delete,则可以创建一个列表 - 但TableView仍然显示列分隔符,因此看起来仍然有两列。要删除列分隔符,请在文档大纲中选择Bordered Scroll View - Table View,然后拖动其中一个角来调整TableView的大小 - 单个列将立即调整大小以占用所有可用空间。

    对第1步和第2步的信用,以及随机提示#2:OS X的可可编程(第5版,2015年)。

    完整代码列表:

    //
    //  MainWindowController.swift
    //  ToDo
    //
    
    //import Foundation
    import Cocoa
    
    class MainWindowController: NSWindowController,
                                NSTableViewDataSource {
    
        //@IBOutlet var window: NSWindow? -- inherited from NSWindowController
        @IBOutlet weak var textField: NSTextField!
        @IBOutlet weak var tableView: NSTableView!
    
        var items: [String] = []  //The data source: an array of String's
    
        override var windowNibName: String {
            return "MainWindow"
        }
    
        @IBAction func onclickAddButton(sender: NSButton) {
            items.append(textField.stringValue)
            tableView.reloadData()  //Displays the new item in the TableView
    
        }
    
        @IBAction func onEnterInTextField(sender: NSTextField) {
            let selectedRowNumber = tableView.selectedRow
    
            if selectedRowNumber != -1 {
                items[selectedRowNumber] = sender.stringValue
            }
        }
    
    
        // MARK: NSTableViewDataSource protocol methods
    
        func numberOfRowsInTableView(tableView: NSTableView) -> Int {
            return items.count
        }
    
        func tableView(tableView: NSTableView,
                       objectValueForTableColumn tableColumn: NSTableColumn?,
                       row: Int) -> AnyObject? {
            return items[row]
        }
    
     }
    

    Connections Inspector显示File&#39的所有者(= MainWindowController)的所有连接:

    enter image description here

答案 1 :(得分:25)

默认情况下,每个单元格(NSTableCellView的实例)都有一个NSTextField。当您编辑单元格时,您实际编辑的是此文本字段。 Interface Builder使此文本字段不可编辑:

enter image description here

您需要做的就是将行为弹出窗口设置为Editable。现在,您可以使用返回匹配或单个 - 单击来编辑文本字段。

答案 2 :(得分:0)

只需对已接受的答案进行更正-您应该使用tableView.row(for:NSView)和tableView.column(for:NSView。获取行和列。其他方法可能并不可靠。

@IBAction func textEdited(_ sender: Any) {
        if let textField = sender as? NSTextField {

            let row = self.tableView.row(for: sender as! NSView)
            let col = self.tableView.column(for: sender as! NSView)
            self.data[row][col] = textField.stringValue

            print("\(row), \(col), \(textField.stringValue)")

            print("\(data)")
        }
    }