NSOutlineView ObjectValueForTableColumn extra call triggers BAD ACCESS - objective-c

When testing I have found that outlineView:objectValueForTableColumn:byItem: is being called one extra time after the last visible row is populated resulting in a EXC_BAD_ACCESS error.
So if my display is showing 10 rows, after row 9 is populated objectValueForTableColumn is called again (without outlineView:child:ofItem: and outlineView:isItemExpandable: being called). The extra call always happens after the last visible row is populated.
Here is my code for the outlineView. There are 2 columns and 114 records in my test data set.
// (1)
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
if (!item) {
NSInteger parentCount = [[Parent countParents:self.dataSetID usingManagedObjectContext:self.context] integerValue];
return parentCount;
}
Parent *thisParent = item;
NSInteger childCount = [[thisParent.child allObjects] count];
return childCount;
}
// (2)
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
NSArray *parentArray = [Parent parentData:self.dataSetID usingManagedObjectContext:self.context];
Parent *thisParent = [parentArray objectAtIndex:index];
if (!item) {
return thisParent;
}
NSArray *children = [NSArray arrayWithObject:[thisParent.child allObjects]];
Child *thisChild = [children objectAtIndex:index];
return thisChild;
}
// (3)
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
if ([item isKindOfClass:[Parent class]]) {
Parent *thisParent = item;
if ([[thisParent.child allObjects] count] > 0) {
return YES;
}
}
return NO;
}
// (4)
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
if (!item) {
return nil;
}
if ([tableColumn.identifier isEqualToString:#"column1"]) {
if ([item isKindOfClass:[Parent class]]) {
Parent *thisParent = item;
return thisParent.name;
}
Child *thisChild = item;
return thisChild.name;
}
// This is column2
if ([item isKindOfClass:[Parent class]]) {
Parent *thisParent = item;
return thisParent.age;
}
Child *thisChild = item;
return thisChild.age;
}
I've noticed that the methods are called in the order: 1,2,3,4,4,2,3,4,4... to populate the two column NSOutlineView. For the last visible row the order is: 2,3,4,4,4 with the last call to method #4 (outlineView:objectValueForTableColumn:byItem:) causing the exception.
I can not tell you the values being passed to the method because it breaks on the call. Even if the first thing in the method is a log statement it doesn't get executed.
So I'm stumped. Any ideas why this is breaking? Did I goof on the implementation?

In - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
you do not appear to use item - you should be returning the child item at the specified index of a given item.
Incidentally your code is inefficient - you should be trying to make these run as fast as possible.
You probably want:-
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
if (item == nil)
return [[Parent parentData:self.dataSetID usingManagedObjectContext:self.context] objectAtIndex:index];
return [[NSArray arrayWithObject:[item.child allObjects]] objectAtIndex:index];
}

Related

How to update row values and children when row of outline view is expanded each time it is clicked

I am new in objective c. And I have been looking for examples/answer that describes the outline view expansion of items. Now what I am trying to do is, mark all items as expandable. Now when the user clicks to expand the item, it calls the API, inserts its children if any, and then reload the table view. The Code is given below:
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item{
//initially make all items expandable to check and load data later on
return YES;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item{
if (item == nil) {
return RootLists.count;
}
if ([item isKindOfClass:[NSDictionary class]]) {
reloadCheck=true;
return [[item objectForKey:#"children"] count];
}
return 0;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item{
if (item == nil){
return [RootLists objectAtIndex:index];
}
if ([item isKindOfClass:[NSDictionary class]]) {
return [[item objectForKey:#"children"] objectAtIndex:index];
}
return nil;
}
-(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item{
NSView *rowView;
rowView=[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)];
[rowView setWantsLayer:YES];
NSTextField *textField;
[textField setMaximumNumberOfLines:1];
[textField setStringValue:[item valueForKey:#"Name"]];
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
[rowView addSubview:textField];
return rowView;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item{
NSMutableArray *getchildrenlist=[database GetData:[item valueForKey:#"id"]];
if (getchildrenlist.count>0) {
//If children are already present then return 1
return 1;
}
else
{
//If children are not present then hit API, get children online and then reload the view with children
dispatch_group_t DispatchGroup = dispatch_group_create();
dispatch_group_enter(DispatchGroup);
[self ExpandDataApi:1 FolderId:[item valueForKey:#"id"] DispatchGroup:DispatchGroup];
dispatch_group_notify(DispatchGroup,dispatch_get_main_queue(),^{
[self reloadData:item];
[self->_Outline_View reloadData];
});
return 1;
}
}
Now the issue is: the table reloads after each click, but never expands it with new children added to the row. Instead, it collapses all items. But after it is clicked again (same item clicked twice), the item expands correctly with new children.
What I want is: reload table and expand that row with its new children after getting new values.
I was able to add items to view by following code
[self->_Outline_View insertItemsAtIndexes:[NSIndexSet indexSetWithIndex:row] inParent:item withAnimation:NSTableViewAnimationSlideDown];
and remove item from row by code:
[self->_Outline_View removeItemsAtIndexes:[NSIndexSet indexSetWithIndex:row] inParent:item withAnimation:NSTableViewAnimationSlideDown];
and then refresh it by using code:
[self->_Outline_View reloadItem:item];
[self->_Outline_View expandItem:item];

EXC_BAD_ACCESS with NSOutlineView and stringWithFormat

I am experiencing a bad access error when an item of my NSOutlineView is expanded. When NSStrings are allocated with stringWithFormat:, there is an EXC_BAD_ACCESS error when expanding the outline. When they are replaced with strings in the form of #"string", there is no error.
I assume something is releasing with ARC, but I don't know how to keep it from happening. What doesn't look right here?
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
if(!item)
return [_characterList count];
else if( [item isKindOfClass:[Character class]] )
return 3;
return 0;
}
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
if( [item isKindOfClass:[Character class]] )
return YES;
return NO;
}
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
if (!item)
return (Character*)[_characterList objectAtIndex:index];
else {
NSLog(#"%#", item);
Character *characterItem = (Character*)item;
switch (index) {
case 0:
return [NSString stringWithFormat:#"Api key: %#", [characterItem apiKey]];
break;
case 1:
return [NSString stringWithFormat:#"Access Mask: %#", [characterItem mask]];
break;
case 2:
return #"Last Updated: today";
break;
default:
break;
}
}
return nil;
}
-(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
if([item isKindOfClass:[Character class]])
return [(Character*)item name];
else
return item;
return nil;
}
The solution I have come up with (but don't particularly like). Replace outlineView:objectValueForTableColumn:byItem: with the following code.
-(NSView*)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
NSTableCellView *cell = [outlineView makeViewWithIdentifier:#"characterColumn" owner:self];
if([item isKindOfClass:[Character class]]) {
[cell.textField setStringValue:[item name]];
} else if([item isKindOfClass:[NSString class]]) {
[cell.textField setStringValue:item];
}
return cell;
}
Basically what this does is exactly what I'd expect the cell code to do, but it appears to retain things properly. Any insight from the masses?
EDIT: Here's the deal. NSOutlineView's dataSource delegate methods are a little bit more particular about ownership. It's not something that you have to deal with usually with a vanilla NSTableView, since there are not multiple levels of item. Basically, you need to create all of the objects for display elsewhere and make sure they are managed in memory elsewhere, because NSOutlineViewDataSource isn't going to do any of that for you.

NSTableView Drawing Count

I have two tables (NSTableView) on a window. Well, the number is irrelevant. Anyway, the source of the first table (tableView1) is NSMutableArray with NSMutableDictionary. And I have the following code.
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
if (aTableView == tableView1) {
return [itemArray1 count];
} else {
...
}
}
- tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)TableColumn row:(NSInteger)rowIndex {
// tableView1
if (aTableView == tableView1) {
NSString *foldername = [[itemArray1 objectAtIndex:rowIndex] objectForKey:key1a];
NSString *count = [[itemArray1 objectAtIndex:rowIndex] objectForKey:key1c];
if ([[TableColumn identifier] isEqualToString:#"folder"]) {
return foldername;
}
if([[TableColumn identifier] isEqualTo:#"count"]){
return count;
}
else {
return #"";
}
// tableView2
} else {
...
}
}
I get what I want, and the second column shows the number of items (count). I wonder how I can possibly draw this number like the SidebarDemo example project? (Shown at the bottom...) This project utilizes - (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item to show a static number (42). Can I show the number like that with NSTableView as well? I could just draw a circled number if it were an iOS application.
Thank you for your advice.
tableView:viewForTableColumn:row: (NSTableViewDelegate) should work similar.

How do i make a simple view-based NSOutlineView?

For learning purposes i would like to convert a cell-based NSOutlineView to a view-based one,
basically i would like the following:
instead of a normal cell, i'd like an 'image and text table cell view'
the image can be the stock NSApplicationIcon and the text can just be 'hello world' :)
I'd like to do this without using bindings and NSTreeController
Here is the 'worlds simplest NSOutlineView' example http://www.cocoasteam.com/Cocoa_Steam/Worlds_Simplest_Demo.html
I wonder if someone could modify it to make it view-based and work like i said above :) :)
I've tried looking at apple examples, and searching elsewhere on the internet but i still can't get it to work - so thanks very much in advance :)
I have created a little sample project which does just that.
Display a list of items
Edit the items in a master-detail fashion
Remove and add items
Usage of bindings
Check out besi/mac-quickies on github.
Most of the stuff is either done in IB or can be found in the AppDelegate
OK, so you want an NSOutlineView with ImageAndTextCell cells, right?
Let's do one of the most typical examples of this kind : a simple file explorer.
What we'll need :
an NSOutlineView (put an outline to your AppDelegate, as fileOutlineView)
create 3 columns in the Outline with the following Identifiers (set them up in Interface Builder) : NameColumn, SizeColumn, ModifiedColumn
Now, as for the rest, I'll do it all programmatically, so that you get a good idea of what's going on...
How to set it up (e.g. in - (void)awakeFromNib):
// set the Data Source and Delegate
[fileOutlineView setDataSource:(id<NSOutlineViewDataSource>)self];
[fileOutlineView setDelegate:(id<NSOutlineViewDelegate>)self];
// set the first column's cells as `ImageAndTextCell`s
ImageAndTextCell* iatc = [[ImageAndTextCell alloc] init];
[iatc setEditable:NO];
[[[fileOutlineView tableColumns] objectAtIndex:0] setDataCell:iatc];
Connecting the dots :
/*******************************************************
*
* OUTLINE-VIEW DATASOURCE
*
*******************************************************/
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
if ([item isFolder])
return YES;
else
return NO;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
if (item==nil)
{
// Root
return [[filePath folderContentsWithPathAndBackIgnoringHidden] count];
}
else
{
if ([item isFolder])
{
return [[item folderContentsWithPathAndBackIgnoringHidden] count];
}
else
{
return 0;
}
}
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
if (item == nil)
{
// Root
return [[filePath folderContentsWithPathAndBackIgnoringHidden] objectAtIndex:index];
}
if ([item isFolder])
{
return [[item folderContentsWithPathAndBackIgnoringHidden] objectAtIndex:index];
}
// File
return nil;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)theColumn byItem:(id)item
{
if ([[theColumn identifier] isEqualToString:#"NameColumn"])
{
return [item lastPathComponent];
}
else if ([[theColumn identifier] isEqualToString:#"SizeColumn"])
{
if ([item isFolder]) return #"--";
else return [NSString stringWithFormat:#"%d",[item getFileSize]];
}
else if ([[theColumn identifier] isEqualToString:#"ModifiedColumn"])
{
if ([item isFolder]) return #"";
else return [NSString stringWithFormat:#"%#",[item getDateModified]];
}
// Never reaches here
return nil;
}
/*******************************************************
*
* OUTLINE-VIEW DELEGATE
*
*******************************************************/
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
{
return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
{
return NO;
}
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
[cell setDrawsBackground:NO];
if ([item isFileHidden]) [cell setTextColor:[NSColor grayColor]];
else [cell setTextColor:[NSColor whiteColor]];
if ([[tableColumn identifier] isEqualToString:#"NameColumn"])
{
if ([item isFolder])
[cell setImage:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] size:15.0];
else
[cell setImage:[[NSWorkspace sharedWorkspace] iconForFile:item] size:15.0];
if ([item isFileHidden])
{
[cell setFileHidden:YES];
}
else
{
[cell setFileHidden:NO];
}
}
}
Hint : ImageAndTextCell class can be found here. You'll also notice a few other methods I'm using, which are obviously NOT supported by Cocoa (e.g. isFileHidden, isFolder or folderContentsWithPathAndBackIgnoringHidden) but it's not that difficult to implement them yourself...)
To return view to OutlineView column Instead of using datasource method that return objectValue:
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)theColumn byItem:(id)item
USE THE DATASOURCE METHOD THAT RETURN VIEW!!!!!!!!:
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
everything else is the same(minimal req is the first three datasource methods, you don't need the delegate methods) but,
you can't use willdisplaycell its called only for cell based , do everything to the view in the viefortablecolumn method like this:
if ([[tableColumn identifier] isEqualToString:#"YourColumnIdentifier"]){
NSTableCellView *cell = [outlineView makeViewWithIdentifier:#"YourViewsIdentifier" owner:self];
[cell.textField setStringValue:[(YourItem *)item name]];
[cell.imageView setImage:[(YourItem *)item image]];
return cell;
}
return nil;
and don't forget to set identifiers , and to set the OutlineView to be View Based(in IB ...).
Check TableViewPlayground, also View Based NSTableView Basic to Advanced from WWDC 2011.

NSOutlineView Data Source

I've created an NSOutlineView(Sourcelist) in Xcode, given it an outlet, and made my class implement NSOutlineViewDataSource. I then [outlineview setDataSource:], and I see that it creates the cells with the number of items from my array, but they are all blank.
sub1 = [NSArray arrayWithObjects:#"General Data",#"Most Played", #"Misc",#"Log", nil];
sub2 = [NSArray arrayWithObjects:#"General Data", #"Most Played", #"Misc",#"Log", nil];
items = [NSArray arrayWithObjects:sub1,sub2,nil];
[outlin setDataSource:self];
[outlin reloadData];
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
return [self outlineView:outlineView numberOfChildrenOfItem:item] != 0;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
if (item)
if ([item isKindOfClass:[NSArray class]])
return [item count];
else
return 0;
else
return [items count];
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
if (item) {
return [item objectAtIndex:index];
}
else
return [items objectAtIndex:index];
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)theColumn byItem:(id)item
{
if (item) {
if ([item isKindOfClass:[NSArray class]])
return #"YES"; // Return name for this node
else
return item;
}
else
return #"List Root";
}
Everything appears to be in order, an it kind of works, except all the text is blank. It creates all the root items and such just without text.
Maybe you forgot to build a table column for your NSOutlineView like this:
NSTableColumn *titleColumn = [[[NSTableColumn alloc] initWithIdentifier:#"identifier"] autorelease];
[[titleColumn headerCell] setStringValue:#"Header"];
[self addTableColumn:titleColumn];
[self setOutlineTableColumn:titleColumn];