i have NSTableView bound to an NSArrayController. in my model i have a BOOL field. i'm trying to bind that value to the column. it displays correctly (1 where value is YES and 0 where value is NO), but it's readonly. =( when i'm trying to edit a value i can't submit it -- when i press enter nothing happens, setter is never invoked. column is editable.
i can successfully bind it with IB -- i just bind it as usual and all works. but i can't do the same programmatically =(
that's how column is created and added:
NSTableColumn *column = [[[NSTableColumn alloc] initWithIdentifier:#"ok"] autorelease];
[column setEditable:YES];
[[column headerCell] setStringValue:#"OK"];
[column bind:#"value" toObject:self.arrC withKeyPath:#"arrangedObjects.ok" options:nil];
[table addTableColumn:column];
i have a problem only with BOOL values, if i bind the same column to some other field (just changing keyPath) all works fine.
it's readonly =(. when i'm trying to edit a value i can't submit it -- when i press enter nothing happens, setter is never invoked. column is editable.
And then, in your code snippet:
[column setEditable:NO];
Your column is not editable. That's why editing doesn't work. Change NO to YES.
By the way: Is there a reason you're displaying this value as text and not a checkbox?
You need to bind the table column, not the cell.
What is bound to arrC which I am assuming is your array controller?
Is arrC bound to an array? What's are the objects in the array bound to the controller? Coredata entities? NSMutableDictionaries?
You need a value transformer, specifically NSNegateBooleanTransformerName. Google for Apple's "Value Transformer Programming Guide"
Related
I have an issue with bindings between an NSComboBox element and an NSArrayController.
All bindings are setup in IB.
The NSComboBox element has the following bindings:
Content: bound to the NSArrayController instance, key: arrangedObjects
Content Values: bound to the NSArrayController instance, key: arrangedObjects.name
The NSArrayController element is bound in the following way:
Content Array: bound to File's Owner, key path: availableProperties (which is an NSMutableArray
In the code, I have a method which is called when the window opens and after some event fires.
The code is the following:
NSMutableArray* newAvailable = ...; //compute the new properties to be displayed.
//now I want to replace all the properties with the new one
if ([self.availableProperties count] > 0)
[self.availablePropertiesController removeObjects:self.availableProperties];
[self.availablePropertiesController addObjects:newAvailables];
where self.availableProperties is the NSMutableArray (the model) and self.availablePropertiesController is the NSArrayController
When the window opens the combo box is properly populated.
But when the event fires I execute the above statements, I can see the backing array correctly filled, but the combo box is completely empty.
Some ideas?
You’re close, you should just do:
NSMutableArray* newAvailable = ...; //compute the new properties to be displayed.
self.availableProperties = newAvailable;
You’ve already bound the arrayController’s to your ‘availableProperties’ variable, so all you have to do to change the UI is change your variable. That’s the beauty of bindings.
Also, your ‘availableProperties' should probably be an NSArray, not a NSMutableArray, because if you accidentally insert an object in the middle of the NSMutableArray the arrayController’s binding isn’t going to notice—it’ll only notice when the entire ‘availableProperties’ instance variable changes, not when something inside it mutates.
I have two coloumn in NSTableView as Name and Salary with 5-10 values. I want to sort these coloumn after click on header of both the column. There is lots of data present in Internet but I am not able to use these. Please help me to do this in cocoa.
Thanks in advance and appreciate any help.
Each table column has a method setSortDescriptorPrototype
Sort descriptors are ways of telling the array how to sort itself (ascending, descending, ignoring case etc.)
Iterate over each of the columns you want as sortable and call this method on each of those columns, and pass the required sort descriptor (In my case I'll be using the column identifier)
for (NSTableColumn *tableColumn in tableView.tableColumns ) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:tableColumn.identifier ascending:YES selector:#selector(compare:)];
[tableColumn setSortDescriptorPrototype:sortDescriptor];
}
After writing this piece of initialization code, NSTableViewDataSource has a method - (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray *)oldDescriptors that notifies you whenever a sort descriptor is changed, implement this method in the data source and send a message to the data array to sort itself
- (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
self.data = [self.data sortedArrayUsingDescriptors:sortDescriptors];
[aTableView reloadData];
}
This method will get fired each time a column header is clicked, and NSTableColumn shows a nice little triangle showing the sorting order.
I stumbled upon this question while looking for the easiest way of implementing something similar. Although the original question is old, I hope someone finds my answer useful! Please note that I am using Xcode 5.1.1
Ok so to do this you need to:
select the actual column you want to sort in your table.
In your Attributes Inspector you need to fill in two fields: Sort Key, and Selector.
In the Sort Key field, you need to enter the value of your Identifier. The value of your Identifier is located in your Identity Inspector.
In the Selector field you need to enter a suitable selector method based on the object type in the column. The default method is; compare:
Based on the Table View Programming Guide for Mac. The compare: method works with NSString, NSDate, and NSNumber objects. If your table column contains only strings, you may want to consider using the caseInsensitiveCompare: method if case sensitivity is unimportant. However, consider replacing these method signatures with the localizedCompare: or localizedCaseInsensitiveCompare: methods to take into the account the user’s language requirements.
Finally, you need to declare the tableView:sortDescriptorsDidChange: method in your Table View Controller in the format shown below:
-(void)tableView:(NSTableView *)mtableView sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
[listArray sortUsingDescriptors: [mtableView sortDescriptors]];
[tableView reloadData];
}
Just had lately the same issue to get tableView sorted.
My approach :
bind your sortDescriptors to tableview's arrayController
bind tableview's sortDescriptors to Arraycontroller's sort descriptor
perform the settings in attribute inspector (see Tosin's answer above)
Worked perfect for me. No need to set prototypes for columns or something else.
Thanks very much ,It is usefullly for my question.
my code like this
First, set unique values in the XIB interface,like name...
- (void)viewDidLoad {
[super viewDidLoad];
self.itemTableView.dataSource = self;
self.itemTableView.delegate = self;
self.itemTableView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleRegular;
self.itemTableView.usesAlternatingRowBackgroundColors = YES;
for (NSTableColumn *tableColumn in self.itemTableView.tableColumns ) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:tableColumn.identifier ascending:NO selector:#selector(compare:)];
[tableColumn setSortDescriptorPrototype:sortDescriptor];
}
}
-(void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray<NSSortDescriptor *> *)oldDescriptors{
NSLog(#"sortDescriptorsDidChange:%#",oldDescriptors);
[self.itemArr sortUsingDescriptors:[tableView sortDescriptors]];
[self.itemTableView reloadData];
}
I'm trying to display an NSPopover with additional information to a selected row of an NSTableView. For that I need to get a reference to the view representation of the selected row so I can "attach" my popover to it:
NSInteger row = [[self membersTableView] selectedRow];
NSTableRowView *aView = [[self membersTableView] rowViewAtRow: row makeIfNecessary: YES];
[self setQuickLookPopoverController: [QuickLookPopoverController showPopoverFor: anObject at: aView]];
In the above, the result of aView is always nil. According to Apple documentation, this is the method to obtain a view object, given a selected row. Especially the last sentence of the discussion is a bit weird:
Discussion This method will first attempt to return a currently
displayed view in the visible area. If there is no visible view, and
makeIfNecessary is YES, a prepared temporary view is returned. If
makeIfNecessary is NO, and the view is not visible, nil will be
returned.
In general, makeIfNecessary should be YES if you require a resulting
view, and NO if you only want to update properties on a view only if
it is available (generally this means it is visible).
An exception will be thrown if row is not within the numberOfRows. The
returned result should generally not be held onto for longer than the
current run loop cycle. It is better to call
rowViewAtRow:makeIfNecessary: whenever a view is required..
Why is this method always returning nil?
Solved it. I used NSTableView's method (NSRect) rectOfRow: (NSInteger) rowIndex which will give the frame of the required row.
Thanks for showing me the right direction, I had the same problem! I ended up doing the following, but note that I disabled selection of empty rows and that the following code is inside an IBAction:
[popOver showRelativeToRect:[sender bounds]
ofView:[sender rowViewAtRow:[sender selectedRow]
makeIfNecessary:YES]
preferredEdge:NSMaxXEdge];
I'm working on a Core Data document application that dynamically creates NSTableColumns. The data cell type may be a checkbox, slider, etc. Programmatically binding to all cell types works, except for NSTextFieldCell.
All NSTextFieldCells fail to bind, and after editing they return to their default value. This happens no matter if they're binding to a string, a number (with an NSNumberFormatter applied), or a date (NSDateFormatter applied). I'm using the following format to do all bindings:
NSDictionary *textFieldOpts = [NSDictionary dictionaryWithObjectsAndKeys:#"YES", NSContinuouslyUpdatesValueBindingOption, #"YES", NSValidatesImmediatelyBindingOption, nil];
[aCell bind:#"value" toObject:[[entryAC arrangedObjects] objectAtIndex:0] withKeyPath:#"numberData" options:textFieldOpts];
Again, these statements work if the cell type is anything but an NSTextFieldCell.
I threw in an -observeValueForKeyPath method to log when the value changes... and for other cell types (NSSliderCell for instance) I can see the value changing, but with the NSTextFieldCell, it never, ever updates.
Turns out that I needed to implement the NSTableView data source method setObjectValue to manually get the change from the NSTableView (View) and then manually set the data in the array controller (Model) similar to the code below:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
[[[entryAC arrangedObjects] objectAtIndex:rowIndex] setValue:(NSNumber *)anObject forKey:#"numberData"];
}
For some reason, the bindings to NSTextFieldCells, when set programmatically on a cell-by-cell basis, only work one way: to display the data only. Personally I would classify this as a bug...
I want to dynamically hide/show some of the columns in a NSTableView, based on the data that is going to be displayed - basically, if a column is empty I'd like the column to be hidden. I'm currently populating the table with a controller class as the delegate for the table.
Any ideas? I see that I can set the column hidden in Interface Builder, however there doesn't seem to be a good time to go through the columns and check if they are empty or not, since there doesn't seem to be a method that is called before/after all of the data in the table is populated.
In Mac OS X v10.5 and later, there is the setHidden: selector for NSTableColumn.
This allows columns to be dynamically hidden / shown with the use of identifiers:
NSInteger colIdx;
NSTableColumn* col;
colIdx = [myTable columnWithIdentifier:#"columnIdent"];
col = [myTable.tableColumns objectAtIndex:colIdx];
[col setHidden:YES];
I've done this with bindings, but setting them up programmatically instead of through Interface Builder.
This psuedo-snippet should give you the gist of it:
NSTableColumn *aColumn = [[NSTableColumn alloc] initWithIdentifier:attr];
[aColumn setWidth:DEFAULTCOLWIDTH];
[aColumn setMinWidth:MINCOLWIDTH];
[[aColumn headerCell] setStringValue:columnLabel];
[aColumn bind:#"value"
toObject:arrayController
withKeyPath:keyPath
options:nil];
[tableView addTableColumn:aColumn];
[aColumn release];
Of course you can add formatters and all that stuff also.
It does not work in the Interface Builder. However it works programatically. Here is how I bind a NSTableViewColumn with the identifier "Status" to a key in my NSUserDefaults:
Swift:
tableView.tableColumnWithIdentifier("Status")?.bind("hidden", toObject: NSUserDefaults.standardUserDefaults(), withKeyPath: "TableColumnStatus", options: nil)
Objective-C:
[[self.tableView tableColumnWithIdentifier:#"Status"] bind:#"hidden" toObject:[NSUserDefaults standardUserDefaults] withKeyPath:#"TableColumnStatus" options:nil];
I don't have a complete answer at this time, but look into Bindings. It's generally possible to do all sorts of things with Cocoa Bindings.
There's no Visibility binding for NSTableColumn, but you may be able to set the width to 0.
Then you can bind it to the Null Placeholder, and set this value to 0 - but don't forget to set the other Placeholders to reasonable values.
(As I said, this is just a start, it might need some tweaking).
A NSTable is just the class that paints the table. As you said yourself, you have some class you give the table as delegate and this class feeds the table with the data to display. If you store the table data as NSArray's within your delegate class, it should be easy to find out if one column is empty, isn't it? And NSArray asks your class via delegate method how many columns there are, so when you are asked, why not looking for how many columns you have data and report that number instead of the real number of columns you store internally and then when being asked for providing the data for (column,row), just skip the empty column.
There is no one time all the data is populated. NSTableView does not store data, it dynamically asks for it from its data source (or bound-to objects if you're using bindings). It just draws using data it gets from the data source and ditches it. You shouldn't see the table ask for data for anything that isn't visible, for example.
It sounds like you're using a datasource? When the data changes, it's your responsibility to call -reloadData on the table, which is a bit of a misnomer. It's more like 'invalidate everything'.
That is, you should already know when the data changes. That's the point at which you can compute what columns should be hidden.
#amrox - If I am understanding your suggestion correctly, you're saying that I should bind a value to the hidden property of the NSTableColumns in my table? That seems like it would work, however I don't think that NSTableColumn has a hidden property, since the isHidden and setHidden messages control the visibility of the column - which tells me that this isn't a property, unless I'm missing something (which is quite possible).
I would like to post my solution updated for Swift 4 using Cocoa bindings and the actual isHidden flag without touching the column widths (as you might need to restore the original value afterwards...). Suppose we have a Checkbox to toggle some column visibility (or you can always toggle the hideColumnsFlag variable in the example below in any other way you like):
class ViewController: NSViewController {
// define the boolean binding variable to hide the columns and use its name as keypath
#objc dynamic var hideColumnsFlag = true
// Referring the column(s)
// Method 1: creating IBOutlet(s) for the column(s): just ctrl-drag each column here to add it
#IBOutlet weak var hideableTableColumn: NSTableColumn!
// add as many column outlets as you need...
// or, if you prefer working with columns' string keypaths
// Method 2: use just the table view IBOutlet and its column identifiers (you **must** anyway set the latter identifiers manually via IB for each column)
#IBOutlet weak var theTableView: NSTableView! // this line could be actually removed if using the first method on this example, but in a real case, you will probably need it anyway.
// MARK: View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Method 1
// referring the columns by using the outlets as such:
hideableTableColumn.bind(.hidden, to: self, withKeyPath: "hideColumnsFlag", options: nil)
// repeat for each column outlet.
// Method 2
// or if you need/prefer to use the column identifiers strings then:
// theTableView.tableColumn(withIdentifier: .init("columnName"))?.bind(.hidden, to: self, withKeyPath: "hideColumnsFlag", options: nil)
// repeat for each column identifier you have set.
// obviously use just one method by commenting/uncommenting one or the other.
}
// MARK: Actions
// this is the checkBox action method, just toggling the boolean variable bound to the columns in the viewDidLoad method.
#IBAction func hideColumnsCheckboxAction(_ sender: NSButton) {
hideColumnsFlag = sender.state == .on
}
}
As you may have noticed, there is no way yet to bind the Hidden flag in Interface Builder as on XCode10: you can see the Enabled or Editable bindings, but only programmatically you will have access to the isHidden flag for the column, as it is called in Swift.
As noted in comments, the second method relies on the column identifiers you must manually set either via Interface Builder on the Identity field after selecting the relevant columns or, if you have an array of column names, you can enumerate the table columns and assign the identifiers as well as the bindings instead of repeating similar code lines.
I found a straightforward solution for it.
If you want to hide any column with the Cocoa binding technology:
In your instance of the NSArrayController, create an attribute/parameter/slot/keyed value which will have NSNumber 0 if you want a particular column to be hidden and any value if not.
Bind the table column object's maxWidth parameter to the data slot, described in (1). We will use the maxWidth bound parameter as a message receiver.
Subclass the NSTableColumn:
import Cocoa
class Column: NSTableColumn {
/// Observe the binding messages
override func setValue(_ value: Any?, forKey key: String) {
if key == "maxWidth" && value != nil { // Filters the signal
let w = value as! NSNumber // Explores change
if w == NSNumber(integerLiteral: 0) {
self.isHidden = true
} else {
self.isHidden = false
}
return // No propagation for the value change
}
super.setValue(value, forKey: key) // Propagate the signal
}
}
Change the class of the column to Column.