How to change style for nodes in NSOutlineView? - objective-c

I am trying to figure out how to change the style for group nodes in NSOutlineView.
With setSelectionHighlightStyle I can change the whole list style. But I want to change only the style for group nodes.
How can I do that?

When I ran into a similar problem, the method #Benedict mentions was never triggered for me. This is because NSOutlineView has a separate method:
- (void) outlineView:(NSOutlineView*)aTableView
willDisplayCell:(id)aCell
forTableColumn:(NSTableColumn*)aTableColumn
item:(id)item;
See the documentation for the NSOutlineViewDelegate protocol. (This formal protocol is new in 10.6 — in previous versions of OS X, the methods were implemented as a category on NSObject.)

NSOutlineView is a subclass of NSTableView. Implement the tableView:willDisplayCell:forTableColumn:row: in the NSOutlineView delegate.
In the tableView:willDisplayCell:forTableColumn:row: implement something like this:
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if ([aTableView isGroupRow: rowIndex]) {
//modify aCell
}
}

Related

How to call super class delegate method from subclass delegate method in Cocoa?

There is example class:
#interface OutlineViewController : NSOutlineView <NSOutlineViewDataSource, NSOutlineViewDelegate>
#end
#implementation OutlineViewController
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSTableCellView *result = nil;
if (myCondition)
{
// ...
return result;
} else {
// how return defult?
}
}
#end
Is there possibility call default realization from delegate method?
Just use the super keyword to refer to the parent class as you've done in your comment:
if (myCondition) {
//...your implementation here
}
else {
return [super outlineView:outlineview heightOfRowByItem:item];
}
For extra points, you might use -respondsToSelector: to check that super responds to the method in question.
Update: I just noticed that the superclass in this case is NSOutlineView itself. This is quite confusing -- views and view controllers are different things, so calling something descended from a view a "view controller" is not a good plan. Also, note that the docs advise that "Subclassing NSOutlineView is not recommended."
Nevertheless, I think I understand your question better now -- I think that by "default realization" you mean not the inherited version of the delegate method, but the behavior that the outline view would use if the delegate method weren't implemented at all. In this case, the answer is pretty simple: you can simply do what NSOutlineView itself would do. The documentation for -outlineView:heightOfRowByItem: says:
Implement this method to support an outline view with varying row heights.
For fixed row heights, on the other hand, NSOutlineView almost certainly uses the rowHeight property that it inherits from NSTableView. So, you can simply return rowHeight in those cases when you don't want to change the row height:
if (myCondition) {
//...your implementation here
}
else {
return outlineView.rowHeight;
}

Objective-C: Am I Properly Using this Delegate?

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.

NSOutlineView + XIB

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

An NSOutlineView with an empty datasource doesn't invoke its delegate methods?

Differently from a NSTableView, the delegate methods of an empty NSOutlineView are never invoked.
For instance, NSTableView method:
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
is invoked even if there are not rows.
But, NSOutlineView method:
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
is not invoked if there are not items.
I need to write arbitrary text on the first row of my outlineView when it is empty. But I can't use the above mentioned method like I do for NSTableView.
What's the solution ?
thanks
Have you implemented outlineView:numberOfChildrenOfItem: to give a correct answer when passed nil?
From your description it seems to me that it should return 1 (your initial line) when passed nil, but I can't be sure without more code and/or information.

NSTextField not calling delegate when inside an NSTableCellView

I have a fairly vanilla Source List (dragged out from the Object Library) in my app, with an NSTreeController as its data source. I set the NSTextField inside the DataCell to be editable, but I want to be able to turn that off for some cells. The way I figured you would do this, is with a delegate for the NSTextField, but none of the delegate methods I've tried get called. Is there something I'm missing? I have the delegate set with an outlet in my XIB, and it happens to be the delegate to the owner NSOutlineView, as well, implementing both the NSOutlineViewDelegate and NSTextFieldDelegate protocols.
Also, I can't use the old –outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate method either, since that only works with cell-based Outline Views (I'm assuming this is the case - the Outline View documentation doesn't appear to have been updated for Lion, though the analogous NSTableView documentation has, and those methods don't get called either).
Update
I reproduced this in a brand new test project, so it's definitely not related to any of my custom classes. Follow the steps below to create my sample project, and reproduce this problem.
In Xcode 4.1, create a new project, of type Mac OS X Cocoa Application, with no special options selected
Create two new files, SourceListDataSource.m and SourceListDelegate.m, with the contents specified below
In MainMenu.xib, drag a Source List onto the Window
Drag two Objects onto the dock (left side of the window), specifying the SourceListDataSource class for one, and the SourceListDelegate for the other
Connect the Outline View's dataSource and delegate outlets to those two objects
Select the Static Text NSTextField for the DataCell view inside the outline view's column
Turn on its Value binding, keeping the default settings
Connect its delegate outlet to the Source List Delegate object
Set its Behavior property to Editable
Build and Run, then click twice on either cell in the outline view.
Expected: The field is not editable, and there is a "well, should I?" message in the log
Actual: The field is editable, and no messages are logged
Is this a bug in the framework, or am I supposed to achieve this a different way?
SourceListDataSource.m
#import <Cocoa/Cocoa.h>
#interface SourceListDataSource : NSObject <NSOutlineViewDataSource>
#property (retain) NSArray *items;
#end
#implementation SourceListDataSource
#synthesize items;
- (id)init
{
self = [super init];
if (self) {
items = [[NSArray arrayWithObjects:#"Alo", #"Homora", nil] retain];
}
return self;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
if (!item) {
return [self.items objectAtIndex:index];
}
return nil;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
return !item ? self.items.count : 0;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
return NO;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
return item;
}
#end
SourceListDelegate.m
#import <Foundation/Foundation.h>
#interface SourceListDelegate : NSObject <NSOutlineViewDelegate, NSTextFieldDelegate> #end
#implementation SourceListDelegate
- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
return [outlineView makeViewWithIdentifier:#"DataCell" owner:self];
}
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
NSLog(#"well, should I?");
return NO;
}
#end
Subclass NSTableCellView, with an outlet for the text field, and set the text field delegate in awakeFromNib. After doing that, control:textShouldBeginEditing: gets called. I'm not sure why, but (edit:) if you set the delegate in the xib, the delegate methods aren't called – I had the same experience as you.
Alternatively, you can forego the delegate and conditionally set Editable using a binding, either to a boolean property of the model, or using a value transformer which acts on a model instance and returns a boolean. Use the Editable binding of the text field.
I've encountered this problem, too. Because I didn't want to lose the bindings, I did the following:
Binding editable of the TextField to the objectValue and set up a custom NSValueTransformer subclass.
The other proposed solutions above are not performant and will not work on modern versions of macOS. NSTableView calls acceptsFirstResponder on EVERY textField in the entire table when one is about to be edited. And first responder methods get called while you just scroll around the table. If you put some logging in those calls, you'll see them in action.
Additionally, assigning the textField's delegate anywhere other than IB is not needed and won't actually work because NSTableView (and therefore NSOutlineView) basically "take over" for the views they contain.
The Correct, Modern Approach:
Subclass NSTableView (or NSOutlineView) and do this:
final class MyTableView: NSTableView
{
override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool
{
// NSTableView calls -validateProposedResponder on cellViews' textFields A METRIC TON, even while just scrolling around, therefore
// do not interfere unless we're evaluating a CLICK on a textField.
if let textField: NSTextField = responder as? NSTextField,
(event?.type == .leftMouseDown || event?.type == .rightMouseDown)
{
// Don't just automatically clobber what the TableView returns; it'll return false here when delays are needed for double-actions, etc.
let result: Bool = super.validateProposedFirstResponder(responder, for: event)
// IF the tableView thinks this textField should edit, now we can ask the textField's delegate to confirm that.
if result == true
{
print("Validate first responder called: \(responder).")
return textField.delegate?.control?(textField, textShouldBeginEditing: textField.window?.fieldEditor(true, for: nil) ?? NSText()) ?? result
}
return result
}
else
{
return super.validateProposedFirstResponder(responder, for: event)
}
}
}
Notes:
This was written against macOS 11.3.1 and Xcode 12.5 for an application targeting macOS 11.
The isEditable property of the NSTextFields in your NSTableCellViews must be set to true. NSTableView's implementation of -validateFirstResponder will check that property first, so you need not do so in your delegate method.