i try to get my head around view-based NSTableViews on OS X.
My Problem is that in the loaded view cell (NSView subclass) the subviews are not initialized when i try to assign the values in my delegate.
At the moment the correct count and the correct view is displayed, but i cannot access the subviews to assign the proper values.
What i have done so far:
Created the xib with the custom view cell in Interface Builder.
Created the custom class for the cell and assigned it in IB.
This work fine and i can see the properties in the Debugger. Correct class
and the properties are IBOulets that are wired to the correct fields.
I can see the call to:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
In my NSViewController:awakeFromNib i do:
// Make my view controller the delegate
_applicationTableView.delegate = self;
// set the correct datasource
_applicationTableView.dataSource = [NWDataHolder sharedInstance];
// register the nib with the custom cell
[_applicationTableView registerNib:cellNib forIdentifier:#"ApplicationListViewCell"];
In my - (NSView *)tableView:viewForTableColumn:row: i do:
NWApplicationListViewCell *cell = [_applicationTableView makeViewWithIdentifier:#"ApplicationListViewCell" owner:self];
The correct class and cell is returned and i have access to the properties.
The problem is, that subview is not initialized, the property is nil and the new value could not be set:
Log(#"Application Name Label %#", cell.applicationNameLabel); => nil
I`ve seen some hints that the subviews are initialized lazyly, but i cannot find a way to make the eager initialize.
Any suggestions what i'm doing wrong ?
Thanks,
Oliver
Fixed the problem. But i do not understand why this really happens.
I wired the fields in IB to the File Owner, but not to my ApplicationViewCell.
After wiring the property to both everything works fine.
Related
I have a NSTableView that is created programmatically. I have several options for customizing the cells in each column based on the column type and datasource (ie, it's very easy to have buttons or checkboxes based on the column type and what is in the datasource).
Now I need to be able to fully customize the cell, so I'm attempting to load an NSView from a xib and return it from the tables delegate's viewForTableColumn method. I haven't used IB much outside of iOS and I'm not very well versed as to how the various outlets and class types should be set, especially when the majority of the UI is created outside of IB. I've read many posts here and on other sites but the majority of examples either create all of the UI in IB or none of it.
Currently I have TestCell.xib which was created by selecting View from the New File dialog. I've also created an objective-c class called TestCell. In IB I've set the view's class to TestCell, and I've dragged outlets for a label control and a button to the TestCell class.
In the table's delegate class I have the following:
- (NSView*)tableView:(NSTableView*)tableView viewForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row {
NSView* view = [tableView makeViewWithIdentifier:customRowXibName owner:self];
if( view == nil ) {
NSArray* nibObjects = nil;
if( [[NSBundle mainBundle] loadNibNamed:customRowXibName owner:self topLevelObjects:&nibObjects] ) {
view = [nibObjects lastObject];
}
}
return view;
}
However, the table view doesn't show anything. I'm also getting the following errors for both controls in the view when loading the xib:
Failed to connect (button) outlet from (TableListViewDelegate) to (NSButton): missing setter or instance variable
I'm assuming that's because I'm setting owner to self when loading the xib.
My questions are:
In IB, what should the File's Owner placeholder be set to? Currently it's set to TestCell but I don't believe that is correct.
Is it ok to use "TestCell" as the identifier? Does this identifier need to be set in IB? Or do I need to call registerNib:forIdentifier on the table view?
When calling loadNibNamed, what should owner be set to?
I was able to get this to work by doing the following:
In IB, set the File's Owner to be the class that is loading the Xib (in this case, the NSTableViewDelegate).
In the delegate, create an outlet for your custom cell and hook it up in IB (I used the Connection Inspector with the File's Owner selected).
In tableView:viewForTableColumn:row call:[tableView makeViewWithIdentifier:#"Xib Name" owner:self]
If that returns nil, then call:[[NSBundle mainBundle] loadNibNamed:#"Xib Name" owner:self topLevelObjects:&nibObjects] with nibObjects being a nil NSArray*.
If loadNibNamed returns YES, then the outlet you created in the delegate should now point to the newly loaded view. Make sure to set the views identifier to #"Xib Name" so you can make use of cached views.
I have a mainWindowController that contains a tabView (which I'm using to switch between views on the main window).
I have view controllers (each with a nib file) for each view. One of the views, view A, contains a tableView. I need to use a delegate method to accomplish something.
After an hour or two or web research and reading up on delegates (new concept to me), I finally got my program to achieve the result I wanted it to for view A.
Here's the interface declaration for view A:
#interface ViewAController : NSViewController <NSTableViewDelegate>
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
As you can see, I'm using NSTableViewDelegate and I need to disable editing of table columns. The implementation looks like this for the method:
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSLog(#"shouldEditTableColumn called");
return NO;
}
I used NSLog to make sure the function is being called.
Now in the SAME view controller (view A), I disable editing by clicking a button:
- (IBAction)turnOffEditing:(id)sender
{
[self.tableView setDelegate:self];
[self tableView:self.tableView shouldEditTableColumn:self.columnTableName row:0];
[self tableView:self.tableView shouldEditTableColumn:self.columnTableName row:1];
NSLog(#"turnOffEditing");
}
As you can see, I get the tableView from the view controller and assign the delegate to self.
I then call the shouldEditTableColumn method on self.
Now, everything works. However, is this the correct way to use a delegate? If I need to use more delegate methods for NSTableView for view A (the only view which will have a table), I'm assuming I can define them in View A's controllers as I did previously?
Usually, the delegate is the delegate from the start. That is, it's a bit strange to set the delegate in the -turnOffEditing: action method. Of course, for my suggestion to work, you'd want to return some dynamic value, like the value of a boolean flag instance variable, from the delegate method.
Also, you shouldn't be calling the delegate method yourself in the action method. That does nothing. The delegate is a thing which the frameworks call when they need to make a decision about how to behave.
So, I'd change your code to something like:
#property BOOL editingDisabled;
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSLog(#"shouldEditTableColumn called");
return self.editingDisabled;
}
- (IBAction)turnOffEditing:(id)sender
{
self.editingDisabled = TRUE;
NSLog(#"turnOffEditing");
}
You'd want to set the delegate during setup. A good approach is to simply connect the table view's delegate outlet to your controller in the NIB.
Just getting into NSOutlineViews and see them a useful control.
Is it possible to show a Xib as the root item??
Fritzables.
Yes, when you use a view based NSOutlineView. First register the nib you want to display for a cell using registerNib:forIdentifier: (windowDidLoad in a window controller would be a good place, awakeFromNib is also a possibility).
NSNib *cellNib = [[NSNib alloc] initWithNibNamed:#"MyCell" bundle:nil];
[self.outlineView registerNib:cellNib forIdentifier:#"MyIdentifier"];
Next in your outlineView:viewForTableColumn:item: you get a (possibly recycled) instance of your nib by using the earlier specified identifier:
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSView *cellView = [outlineView makeViewWithIdentifier:#"MyIdentifier" owner:self];
// optional configuration here
return cellView;
}
I've got a blog post + mac app sample code that demonstrates this.
Yes. You just have to switch it to use view based cells.
In your delegate, implement outlineView:viewForTableColumn:item: to provide the correct XIB.
Yes it is Possible, NSOutlineView is another great visual control available in Mac OS X. Being descendant of the NSTableView It represents the hierarchical data. You can collapse and expand nodes, see parents and their children. In this article we will describe how to use NSOutlineView to show DASchema object which by its nature is a good example of the tree-like data.
For more info check Here
I am having real troubles passing an NSIndexPath to my new view. This is how the app works:
I have a UIBarButtonItem in my nab br, tap that and you get a popover view, this shows a bunch of stuff. I need to get an NSIndexPath from my main view, to this popover view.
I have a property for the NSIndexPath in my popover view class and the popover transition is connected up in my storyboard.
Then I have this code to pass the index path across views:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"statsPopover"])
{
StatsViewController *statsVC = [segue destinationViewController];
statsVC.selectedIndex = stageSelectionTable.indexPathForSelectedRow;
}
}
However, while this gets called, the index path isn't actually sent between views. My index path on the popover is always the default, 0,0 row and section.
You say you know from debugging/logging that the method is running, your if statement is triggered, and stageSelectionTable.indexPathForSelectedRow has the value you expect.
Doing that kind of diagnosis puts you on the right path to solving your issue. Keep at it -- if you test some other things with NSLog or the debugger you should be able to find the problem.
Do you know that statsVC is non-nil? If it's nil, your message to set selectedIndex does nothing.
Is statsVC the class you expect? If, say, the (table?) view in your popover is embedded in a navigation controller, segue.destinationViewController will point to the nav controller, and you'll need to look into its child view controllers array to find the one you're looking for.
Is whatever accessor method your StatsViewController class is using for its selectedIndex property working right? (This shouldn't be a problem if it's a synthesized accessor for a strong/retain property.)
In your StatsViewController class, are you trying to work with selectedIndex before it actually gets set? Setting a property in prepareForSegue:sender: and then using it in viewDidLoad should work fine, but using it in awakeFromNib might not, and using it in an init... method definitely won't.
I have an application that intends to create a popup window when a button is clicked. The popup window will load from a nib file. And so, the button is clicked and the window happily pops up. BUT, its awakeFromNib method gets called twice. Here's the code;
Application Delegate:
...
-(IBAction)myButton:(id)sender{
printf("[settings]: button pressed\n");
Config_SelectorSetup *selectorSetup = [[Config_SelectorSetup alloc] initWithWindowNibName:#"Config_SelectorSetup"];
printf("about to load\n");
[[selectorSetup window] makeKeyAndOrderFront:sender];
}
Config_SelectorSetup.m
- (id) initWithWindowNibName:(NSString *)windowNibName{
printf("[initWithWindowNibName]\n");
if( self = [super initWithWindowNibName:windowNibName] ){
...
}
return self;
}
- (void)awakeFromNib{
printf("[awakeFromNib]\n");
[self startScreen];
}
And here is the output:
[settings]: button pressed
[initWithWindowNibName]
about to load
[awakeFromNib]
[awakeFromNib]
Analyzing the call stack, first time it's called by [NSObject performSelector:] the second one by [NSIBObjectData nibInstantiateWithOwner:topLevelObjects:].
Can someone tell what am I doing wrong?
Thanks
Does Config_SelectorSetup.xib contain a Config_SelectorSetup object besides File's Owner?
Try logging self in awakeFromNib -
NSLog(#"self = %p", self);
Does it print the same address each time? If it's printing different addresses, chances are you have a Config_SelectorSetup object in your nib.
How many outlets do you have in your class and what is that class subclassing? I found with certain subclasses (NSDocument for instance), if you have multiple outlets connected, each nib object will fire the awakeFromNib method upon loading. NSLog your outlets to see if they output nil or an address.
If you create an object from a nib and specify the NSWindowController as the owner, the window controller will get an awakeFromNib.
For example, a common case is where the controller is a delegate for an NSTableView and the method
(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
is making the views like this,
return [tableView makeViewWithIdentifier:tableColumn.identifier
owner:self];
Note how self (the window controller) is passed as owner, which will cause it to see an awakeFromNib message every time this line is executed.
In this case it's better to pass nil as the owner, and not rely on getting awakeFromNib for table cell views here.
I don't know what object is being created with your controller specified as the owner in your case, but this should put you on the right track.