为什么我的NSTableView(基于视图)中的数据不会显示?

时间:2015-04-17 05:35:17

标签: cocoa xcode6 nstableview

我按照advice here关于如何为我项目的单一窗口设置MainWindowController: NSWindowController。我使用Cocoa类创建.h / .m文件,然后选中了Also create .xib for User Interface选项。结果,Xcode自动将一个窗口(我将其重命名为MainWindow.xib)连接到我的MainWidowController。

接下来,我删除了默认MainMenu.xib文件中的窗口(在Interface Builder中我选择了窗口图标,然后我点击了删除键)。之后,我能够成功构建我的项目,MainWindow.xib中的控制器窗口正确显示,上面有几个按钮。

然后我尝试将NSTableView添加到我的MainWindowController窗口。在Xcode中,我将NSTableView的必需delegatedatasource出口拖到File's Owner,这是我的MainWindowController,我在MainWindowController.m中实现了我认为可以创建NSTableView的方法显示我的数据:

- tableView:viewForTableColumn:row:
- numberOfRowsInTableView:

现在,当我构建项目时,我没有收到任何错误,但数据并没有出现在NSTableView中。

我的代码如下。欢迎任何提示!

//
//  AppDelegate.h
//  TableViews1
//

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>


@end

...

//
//  AppDelegate.m
//  TableViews1
//

@interface AppDelegate ()

@property (weak) IBOutlet NSWindow *window;
@property (strong) MainWindowController* mainWindowCtrl;
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application

    [self setMainWindowCtrl:[[MainWindowController alloc] init] ];
    [[self mainWindowCtrl] showWindow:nil];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application
}

@end

...

//

//  MainWindowController.h
//  TableViews1
//


#import <Cocoa/Cocoa.h>

@interface MainWindowController : NSWindowController

@end

...

//
//  MainWindowController.m
//  TableViews1
//


#import "MainWindowController.h"
#import "Employee.h"

@interface MainWindowController () <NSTableViewDataSource, NSTableViewDelegate>

@property (strong) NSMutableArray* employees;
@property (weak) IBOutlet NSTableView* tableView;
@end


@implementation MainWindowController

- (NSView*)tableView:(NSTableView *)tableView
  viewForTableColumn:(NSTableColumn *)tableColumn
                 row:(NSInteger)row {

    Employee* empl = [[self employees] objectAtIndex:row];

    NSString* columnIdentifier = [tableColumn identifier];
    //The column identifiers are "firstName" and "lastName", which match my property names. 
    //You set a column's identifier by repeatedly clicking on the TableView until only 
    //one of the columns is highlighted, then select the Identity Inspector and change the column's 'Identifier' field.

    NSString* emplInfo = [empl valueForKey:columnIdentifier];  //Taking advantage of Key-Value coding

    NSTableCellView *cellView =
        [tableView makeViewWithIdentifier:columnIdentifier
                                    owner:self];

    NSLog(@"The Table view is asking for employee: %@", [empl firstName]);

    [[cellView textField] setStringValue:emplInfo];
    return cellView;
}


- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    return [[self employees] count];
}


- (void)windowDidLoad {
    [super windowDidLoad];

    // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.

    Employee* e1 = [[Employee alloc] initWithFirstName:@"Joe" lastName:@"Blow"];
    Employee* e2 = [[Employee alloc] initWithFirstName:@"Jane" lastName:@"Doe"];

    [self setEmployees:[NSMutableArray arrayWithObjects:e1, e2, nil]];

    //Test to see if the employees array was populated correctly:
    Employee* e = [[self employees] objectAtIndex:0];
    NSLog(@"Here is the first employee: %@", [e firstName]);
    //I see the output: "Here is the first employee: Joe"


}

- (id)init {
    return [super initWithWindowNibName:@"MainWindow"];
}

- (id)initWithWindowNibName:(NSString *)windowNibName {
    NSLog(@"Clients cannot call -[%@ initWithWindowNibName] directly!",
          [self class]
    );

    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

@end

...

//
//  Employees.h
//  TableViews1

#import <Foundation/Foundation.h>

@interface Employee : NSObject

@property NSString* firstName;
@property NSString* lastName;

- initWithFirstName:(NSString*)first lastName:(NSString*)last;

@end

...

//
//  Employees.m
//  TableViews1
//

#import "Employee.h"

@implementation Employee

- (id)initWithFirstName:(NSString *)first lastName:(NSString *)last {
    if (self = [super init]) {
        _firstName = first;  //I read that you shouldn't use the accessors in init methods.
        _lastName = last;
    }

    return self;

}

@end

文件所有者(= MainWindowController)连接:

File's Owner connections

NSTableView连接:

enter image description here

对评论的回应

以下为什么在[self tableView] reloadData]末尾调用-windowDidLoad,如评论中所述,不起作用:

我的_tableView实例变量 - 由我在MainWindowController.m中的@property声明创建 - 并没有指向任何东西;因此呼吁:

[[self tableView] reloadData]

我认为相当于调用:

[nil reloadData]

没有做任何事情。

我从未在_tableView方法中为-init实例变量分配任何内容,也没有通过在Interface Builder中的某处拖动插座来为其分配值。为了解决这个问题,我选择了Project Navigator(左侧窗格)中的MainWindow.xib(控制器窗口),然后在中间窗格(Interface Builder)中,我选择了代表文件的多维数据集。所有者(在右侧窗格中选择Identity Inspector显示File的所有者是MainWindowController)。然后在右窗格中,我选择了Connections Inspector,它显示了一个名为tableView的出口,它是我在MainWindowController.m中声明的IBOutlet变量。

接下来,我将tableView outlet拖到中间窗格中的TableView上:

enter image description here

这样做会将NSTableView对象分配给由MyWindowControler.m中的@property声明创建的_tableView实例变量:

@property (weak) IBOutlet NSTableView* tableView;

作为实验,我断开了插座,然后注释掉了tableview的@property声明,并且在Connections Inspector中不再出现tableView插座。另外,如果我改变声明:

@property (weak) IBOutlet NSTableView* tableView; 

为:

@property (weak) NSTableView* tableView;

...然后tableView出口不会出现在Connections Inspector中。那个实验回答了我关于是否应该将属性声明为IBOutlet的几个问题:如果你需要将Interface Builder中的一个对象分配给你的一个变量,那么将变量声明为IBOutlet。

此后,在[self tableView] reloadData]结束时调用-windowDidLoad成功填充TableView。但是,我没有看到任何调用reloadData的教程,甚至Apple's guide都没有这样做。

所以,我仍然对于调用-reloadData是否是黑客还是正确的做事方式感到困惑。

  

如果没有它,你的桌面视图就会对你无比无聊   期望它甚至不用向数据源索取数据。

我假设NSTableView在准备好自己显示时自动查询其数据源,并且我的代码需要能够在那时提供数据。

2 个答案:

答案 0 :(得分:1)

我没有看到你在任何地方向你的桌面视图发送-reloadData。将它粘贴到-windowDidLoad的末尾将是一个好地方。如果没有它,你的表视图就会对你的期望无能为力,因为它甚至不愿意向数据源询问数据。

据他所知,数据根本没有准备/可用,为什么要尝试呢?更重要的是, 应该 时,它会尝试吗?考虑到UI可能没有完成加载/连接到出口,或者它的数据源可能处于易受攻击的状态(如dealloc期间/之后的拆除)并且可能导致发送数据源请求,因此在任何时候都可以尝试它是相当粗鲁的。在崩溃等等。

答案 1 :(得分:-1)

两件事:

1,在windowDidLoad中设置employees数组时设置一些断点,而当表首次尝试填充自身并调用numberOfRowsInTableView实现时。如果后者发生在前者之前,那么在创建数组后需要添加reloadData。

第二,我个人总是在我的表中使用NSCell而不是NSViews,所以我总是在表的数据源中实现objectValueForTableColumn。因此,当您使用NSView对象并实现viewForTableColumn时,我不确定您是否需要做一些不同的事情。你有没有使用NSCell的原因?