I have an outline view delegate and am overriding outlineView:dataCellForTableColumn:item: to make the cells in my outline view into buttons (see this question). Here is the code form my delegate:
- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item;
{
MyCell * myCell = [[MyCell alloc] init];
// return nil;
return myCell;
}
Doing this has a strange side effect. In my outline view's data source, the method outlineView:objectValueForTableColumn:byItem: always gets a null value for tableColumn.
The code is:
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
printf("tableColumn:%s\ttable identifier: %s\n", [[tableColumn className] cString], [[tableColumn identifier] cString]);
return [item valueForKey:[tableColumn identifier]];
}
And the output is:
tableColumn:(null) table identifier: (null)
What's strange is that this only happens when I implement the outlineView:dataCellForTableColumn:item: method. What am I missing here?
EDIT:
Modifying the delegate function like this seems to fix the problem:
- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item;
{
printf("delegate column identifier: %s\n", [[tableColumn identifier] cStringUsingEncoding:NSASCIIStringEncoding]);
if (tableColumn == nil)
{
return nil;
}
MyCell * aCustomCell = [[MyCell alloc] init];
return aCustomCell;
}
However, I don't really understand what's going on here. If anyone can explain, that would be helpful. Thanks!
NSOutlineView has the ability for you to use a single cell to draw an entire row, rather than a separate cell for each column. It first asks for a cell passing in a nil table column. If you return a cell for that call, it uses it to draw the whole row, otherwise it continues and asks for a separate cell for each column. So, as you discovered, the solution is to return a nil cell when passed a nil table column, so things will draw normally.
Related
I created a tableview and added image & text table view cell designed it now i thought to add another label in the row and assigned some values to the label based on the rows but it is not working and one more problem i cant hide label when the label in cell while in view i can able to hide
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSImage *profile=[NSImage imageNamed:#"header_menubg.png"];
NSString *identifer=[tableColumn identifier];
if([identifer isEqualToString:#"Mainidentifier"])
{
cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
[cellView.textField setStringValue:[_homearray objectAtIndex:row]];
[cellView.imageView setImage:profile];
return cellView;
}
if(row==0)
{
_countLabel.stringValue=#"40";
[cellView.textField setHidden:YES];
}
return nil;
}
And even i tried to hide text field in some rows that too not working what may be the problem
I'm trying to implement drag and drop in my NSOutlineView but none of the example code, tutorials or other SO questions that I've found seem to work for my situation. I have an NSOutlineView with its content bound to an NSTreeController. The tree controller's Content Array is bound to an NSMutableArray of custom objects that have childern objects of the same type. In the outline view I can add and remove objects at any level in the heirarchy. So far so good.
To implement drag and drop I created and NSObject sublass that will serve as the outline view's dataSource. I have implemented a few methods, based on sample code and posts I found on Stack Overflow. I can initiate a drag, but when I do the drop, outlineView: acceptDrop: item: childIndex: is called but all of the values except for childIndex: are nil. The value for childIndex tells me the index of the drop location within the array but not which node I am at within the heirarchy.
I assume that all the other values passed in outlineView: acceptDrop: ... are nil because I haven't fully implemented dataSource, I'm only using it to control the drag and drop operation. Do I need to set up more pasteboard information when I start the drag? How do I find out what node I'm at when the drop occurs? Why are all the values in outlineView: acceptDrop: ... nil?
Here is the implementation of the outline views dataSource:
\ #implementation TNLDragController
- (void)awakeFromNib {
[self.titlesOutlineView registerForDraggedTypes:[NSArray arrayWithObject:#"Event"]];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard {
NSLog(#"starting a drag");
NSString *pasteBoardType = #"Event";
[pboard declareTypes:[NSArray arrayWithObject:pasteBoardType] owner:self];
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView
validateDrop:(id < NSDraggingInfo >)info
proposedItem:(id)item
proposedChildIndex:(NSInteger)index {
NSLog(#"validating a drag operation");
return NSDragOperationGeneric;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index {
NSLog(#"accepting drag operation");
//todo: move the object in the data model;
NSIndexPath *path = [self.treeController selectionIndexPath]; // these three values are nil too.
NSArray *objects = [self.treeController selectedObjects];
NSArray *nodes = [self.treeController selectedNodes];
return YES;
}
// This method gets called by the framework but the values from bindings are used instead
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
return NULL;
}
/*
The following are implemented as stubs because they are required when
implementing an NSOutlineViewDataSource. Because we use bindings on the
table column these methods are never called. The NSLog statements have been
included to prove that these methods are not called.
*/
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
NSLog(#"numberOfChildrenOfItem");
return 1;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
NSLog(#"isItemExpandable");
return YES;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
NSLog(#"child of Item");
return NULL;
}
#end
the implementation I described in this question was, in fact working just fine, but I made a rookie mistake when trying to determine if it was working. I set a breakpoint in - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index in order to examine the values that were being passed into the method. I'm using ARC and the values were never referenced within the method so ARC never retained them, making them unavailable to the debugger!
First of all, I searched for 3 days and have not found what I am looking for.
Second point, I am VERY new to Xcode programming so PLEASE keep your answer as simple and/or detailed as possible!
Inside my NSTableView I need the first column to be text followed by X number of checkbox columns with the last column to be text. The X number of columns is a variable based on the number of entries I read from a SQLite file.
My questions are:
How do I define at runtime what type and how many columns I have?
How do I know whether a checkbox value is checked or not?
How do I add rows to the tableview if I don't know the number of cells it has?
Thanks for taking the time to answer!
Best regards,
Igor
You want to create a table at runtime, and you dont know how many columns will be there!!!
Thats a big deal.
Read NSTableView documentation and you will find addTableColumn: method. in the loop go on to create colmn and dont forget to give identifier to all columns.
And over this you want to have a checkbox. Creating checkbox is not difficult either.
EDIT:
For checkbox implementation, find a project here.
Draw a checkBoxCell in the column you want to have checkbox. Or you can do it programatically.
I did it through IB.
You should create an array that will store the states for each(number of rows) checkboxes.
- (id)init {
self = [super init];
if (self) {
states=[[NSMutableArray alloc] initWithObjects:#"1", #"0", #"1", nil];
}
return self; }
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
NSLog(#"Num of rows---- %ld", [names count]);
return [names count];
}
Check for tableIdentifier having value "check".
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if ([[tableColumn identifier] isEqualTo:#"name"]) {
return [names objectAtIndex:row];
}
else if ([[tableColumn identifier] isEqualTo:#"check"]) {
return [states objectAtIndex:row];
}
else if ([[tableColumn identifier] isEqualTo:#"states"]) {
return [states objectAtIndex:row];
}
return 0;
}
Update the value of checkbox as per on/off.
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)value forTableColumn:(NSTableColumn *)column row:(NSInteger)row {
[states replaceObjectAtIndex:row withObject:value];
[tableView reloadData];
}
I have a NSTableView with a single NSTableCellView column that let's say, has an icon, name and an optional date.
When you edit a row, I want to replace the whole view with a simple NSTextField, and I will do some parsing to that text and extract that optional date, if present.
My question is, how would you implement this editing mechanism?
I tried returning a different view in the tableView:viewForTableColumn:row, something like:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
BOOL isSelected = [tableView isRowSelected:row];
if (isSelected)
{
NSView *view = [tableView makeViewWithIdentifier:#"editor" owner:self];
....snip....
return view;
}
else
{
TaskView *view = [tableView makeViewWithIdentifier:#"view" owner:self];
....snip....
return view;
}
}
and then whenever the selected row changes, trying to request a refresh on that row.
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
NSTableView *table = [aNotification object];
NSUInteger rowIndex = [table selectedRow];
[table reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex]
columnIndexes:[NSIndexSet indexSetWithIndex:0]];
}
It doesn't quite work, and the code feels a bit dirty.
It must be a better way of doing this, and I can't seem to find in the docs or online.
I have window with 3 table views (10.7.2, Xcode 4.2).
They are all created in IB and NSButtonCells are connected with outlets.
I created controller class and I filled all three views with some sample data:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
return 10;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
NSButtonCell *buttonCell;
if(aTableView == dimensionTable) {
[dimensionButtonCell setTitle:#"Dimension"];
buttonCell = dimensionButtonCell;
}
else if(aTableView == shopTable) {
[shopButtonCell setTitle:#"Shop"];
buttonCell = shopButtonCell;
}
else if(aTableView == countryTable) {
[countryButtonCell setTitle:#"Country"];
buttonCell = countryButtonCell;
}
return buttonCell;
}
I have 2 questions:
I cannot change checkbox state through GUI. I can change it programatically, though. It blinks a bit, when you hold down mouse button, but doesn't allow change...
I tried to fill data as with views, without outlets to cells. It didn't work. Are NSButtonCell cels within cell views somehow different as view based Table Views or "normal" cel based Table Views?
After long struggle I manage to find the solution for the problem. One part of the problem was simple bug at the data model side, but it wasn't crucial, something much more difficult was to be done with NSTableView delegate and datasource.
THere were mainly 3 difficulties that prevented good understanding and managing this problem:
apple's documentation lacks of any reasonable explanation about differences and typical usage of - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndexin table view's data source and - (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row of its delegate. While it may seem that you would need latter method, because NSButtonCells are custom NSCells it turns out it is not necessary, but I left it at the end anyway.
internal conversions in NSTableView methods
problem is not documented almost anywhere on the net
Here are steps you should do:
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
buttonCell = [aTableColumn dataCell];
NSString *columnKey = [aTableColumn identifier];
return buttonCell;
}
You can see this method has to be implemented whether you use it or not.
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
buttonCell = [tableColumn dataCell];
NSString *columnKey = [tableColumn identifier];
if(tableView == dimensionTable) {
// returnObject = #"Dimension";
// [dimensionButtonCell setTitle:#"Dimension"];
// buttonCell = dimensionButtonCell;
}
else if(tableView == shopTable) {
[buttonCell setState:[[mySelectedShops objectAtIndex:row] integerValue]];
[buttonCell setTitle:[myShops objectAtIndex:row]];
}
else if(tableView == countryTable) {
[buttonCell setState:[[mySelectedCountries objectAtIndex:row] integerValue]];
[buttonCell setTitle:[myCountries objectAtIndex:row]];
}
return buttonCell;
}
you can see I used second method, however objectValueForTableColumn could be used solely.
You can also see, I have NSMutableArray mySelectedShops and mySelectedCountries to hold NSInteger (1 or 0) wrapped in NSNumber for each row in Table View.
If you set the state or integerValue of NSCell makes no difference. Both will check and uncheck NSButtonCell with values 1 or 0 of NSInteger type.
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
NSString *columnKey = [aTableColumn identifier];
if(aTableView == dimensionTable) {
// [dimensionButtonCell setTitle:#"Dimension"];
// buttonCell = dimensionButtonCell;
}
else if(aTableView == shopTable) {
[mySelectedShops replaceObjectAtIndex:rowIndex withObject:[NSNumber numberWithInteger:[(NSCell*)anObject integerValue]]];
}
else if(aTableView == countryTable) {
[mySelectedCountries replaceObjectAtIndex:rowIndex withObject:[NSNumber numberWithInteger:[(NSCell*)anObject integerValue]]];
}
}
Although I passed NSInteger value to NSCell object, anObject here is of __NSCFBoolean type, which means something doesn't work as expected. To be able to replace object value to arrays I have casted it to NSCell only to get integerValues. It actually works without cast as well, so it is another mystery to me, but I like it more that way.
It is clear Apple is moving to view based cells like in UITableView. Still, I hope this will help to somebody.