Expanding NSOutlineView programmatically - objective-c

My requirement is something like that,
On top of NSOutlineview there would be a search textfield and NSOutline view may contains the nested Groups,
if a item / row is matching with the search query, then it should be displayed, which its all parent node till root level,
the way i am doing is as follows,
1 -- Depending upon the what user has entered , re-building tne NSOutlineview datasource ( Nothing but an NSArray )
2 -- Have implemented all delegate method,
Now my problem is Group is not getting expanded automatically, I know i should call
-expandItem:expandChildren:,
but my questions is from where i should call this, i called it after reload data but it didn't work
i tried to call it something like this ,,,,
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)aTableColumn byItem:(id)item {
if(aTableColumn == nil){
[self log:#"aTableColumn is NIL "];
return nil;
}
if([[aTableColumn identifier] isEqualToString:#"firstColumn"] ){
if ( [myOutlineView shouldExpandIt:item]){
[myOutineView expandItem:item];
}
return item;
}
return item;
}
but its faulting perhaps going to recursive loop
Any other way to achieve the same.

Thanks to all of you to look into this,
By putting log, i found out to be , it seems to be [NSOutlineView reloadData ] seems to be blocking call,
so after that if i need to expand / collapse group , i am using
[pCTOutlineView expandItem:pValue];
and working so far....

Related

Displaying an object hierarchy within NSOutlineView - cannot get it to work correctly, any tips?

So, I want to use NSOutlineView in my application to display a specific, for a lack of a better name, hierarchy.
To explain, here's how it looks in the code:
I have a protocol that declares a few methods used by the objects I'd like to display:
#protocol OutlineViewItem <NSObject>
#required -(BOOL)hasChildren; //Tells whether the object has children
#required -(NSInteger)numberOfChildren; //returns 0 if none or number of children
#required -(id)getChildren; //return NSMutableArray containing children
#required -(NSString*)getDisplayableName; //returns a string that would be displayed in NSOutlineView
#end
As it can be guessed, these methods should make my task a little bit easier.
Then, I've got a following hierarchy of objects (all of them implement that protocol) ->
Main application contains one instance of a Project class, that contains a NSMutableArray of Subproject class instances, that contains a NSMutableArray of SubprojectItem class instances.
An example of how I'm using these protocol methods in Project class (subprojects is the aforementioned NSMutableArray:
-(BOOL)hasChildren{
if(subprojects == nil || [subprojects count] < 1){
return NO;
}
return YES;
}
-(NSInteger)numberOfChildren{
if(subprojects == nil){
return 0;
}
return [subprojects count];
}
-(id)getChildren{
return subprojects;
}
-(NSString*)getDisplayableName{
return name;
}
Subproject and SubprojectItem classes implement these methods in a similar way.
In my application, I've defined the main window class (ProjectWindow) to implement NSOutlineViewDataSource and Delegate protocols, and I've bound NSOutlineView's data source and delegate to ProjectWindow.
in ProjectWindowClass, I've implemented methods as follow:
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
return item == nil ? project : [item getChildren];
//if I understand it correctly, it return the children of a given node.
//if item is nil, it should return the root, that is, project, or the children of item.
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
return item == nil? YES : [item hasChildren];
//Same as above: project is expendable, other nodes can be expanded if contain children
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
return item == nil? 1 : [item numberOfChildren];
//Same as above: there's 1 project, or it returns num of children;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
return item == nil ? #"ROOT" : [item getDisplayableName];
//I think that's what is going to be displayed in NSOutlineView, next to the expendable arrow
}
However, when I try to run it, I'm met with a following exception:
2013-08-23 22:45:12.930 myProject[1903:303] -[__NSArrayM hasChildren]: unrecognized selector sent to instance 0x101a16f30
If I understood the whole NSOutlineViewDataSource, it should either return the root item, if requested with item == nil, of children of item if item != nil. But, although I've thought that it's how it should be, it doesn't work, and application hangs.
So, how should I implement all these data source methods to make it work as intended?
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
return item == nil ? project : [item getChildren];
//if I understand it correctly, it return the children of a given node.
Not all of them at once!
outlineView:child:ofItem: takes an index because it's expected to return the child at that index. It's supposed to return one child at a time.
You're telling the outline view that the array of all children of the project is every child of the project. I don't think that's what you meant.
You want:
return item == nil ? project : item[index];
(By the way, you should not name a variable index. There is a function by that name, so if you forget or misspell the variable's declaration, hilarity will ensue when you then use the index function as an array index.)
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
return item == nil ? #"ROOT" : [item getDisplayableName];
//I think that's what is going to be displayed in NSOutlineView, next to the expendable arrow
Yes, so you want a localized string. Use NSLocalizedString around the #"ROOT" string. Better yet, move that to a method in the project, and just ask for the item's displayable name unconditionally.

CoreData How to filter NSFetchedResultsController with Search and ARC

I found this perfect answer to search through an NSFetchedResultsController : https://stackoverflow.com/a/4481896/1486928
EDIT : project showing the issue : http://cl.ly/2x0C0N0E4240
It seems really great except it wasn't written to use with ARC, I tried to just remove all the "retain" "release" "autorelease".
It still works, well mostly, the thing is, when I enter a char in the searchbar it shows the filtered table as expected but it only takes 1 char (if you add more it doesn't do anything) and after that every other "search" will show the results of the first search that only took 1 char.
I've been at it for 2 days putting NSlog anywhere to see when every methods are called but still couldn't find how to make it work :(
Thanks !
Edit : here is .m http://pastebin.com/9U4TfbA6
Edit : here is .h http://pastebin.com/S9aaNRFE
Also if it can help the search works when I comment this :
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController_! = nil)
{
return fetchedResultsController_;
}
...
}
And this :
- (NSFetchedResultsController *)searchFetchedResultsController {
if (searchFetchedResultsController_ != nil)
{
return searchFetchedResultsController_;
}
...
}
But it mess up other things :/
I guess that you are messing up with the search display controller delegate methods,
and especially you need to check this method
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString;
Because this method will reload your table view for every character which you type in your search field as the name itself suggests shouldReloadTableForSearchString
Edit:
Well you need to implement 2 delegate methods of UISearchBar because all your UISearchDisplayController delegate methods are same and those 2 methods are
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText;
This tells the delegate that the user changed the search text.
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
This is used if the text in a specified range should be replaced with given text.
and please note these methods are called several times, i mean for the every character added and deleted in the search bar and because you are setting the searchFetchedResultsController to nil every time the search text changes
just comment out this part, it will work well
/*if (searchFetchedResultsController_ != nil)
{
NSLog(#"Returned !nil searchController");
return searchFetchedResultsController_;
}*/

When does objectValueForTableColumn: get called?

I am currently going through the 3rd Edition of the Mac OSX Cocoa book from the Big Nerd Ranch guys and I am editing my program to include table views. Here the code from the AppController.m file where I have to implement the required protocol methods from the TableView:
-(id)init{
self = [super init];
voiceArray = [NSSpeechSynthesizer availableVoices];
speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
[speechSynth setDelegate:self];
return self;
}
-(int)numberOfRowsInTableView:(NSTableView *)tv{
NSLog(#"Getting number of rows in table view: %lu", [voiceArray count]);
return [voiceArray count];
}
-(id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
NSString *v = [voiceArray objectAtIndex:row];
NSDictionary *dict = [NSSpeechSynthesizer attributesForVoice:v];
NSLog(#"Voice Name %#", [dict objectForKey:NSVoiceName]);
return [dict objectForKey:NSVoiceName];
}
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
int row = [tableView selectedRow];
NSLog(#"row selected %d", row);
if(row == 1){
return;
}
NSString *selectedVoice = [voiceArray objectAtIndex:row];
[speechSynth setVoice:selectedVoice];
NSLog(#"new voice %# ", selectedVoice);
}
When the app first loads I get the following output:
2012-05-27 15:02:29.040 Speakline[42836:f03] Getting number of rows in
table view: 24 2012-05-27 15:02:29.042 Speakline[42836:f03] row
selected 2 2012-05-27 15:02:29.043 Speakline[42836:f03] new voice
com.apple.speech.synthesis.voice.Alex 2012-05-27 15:02:29.162
Speakline[42836:f03] Voice Name Agnes 2012-05-27 15:02:29.163
Speakline[42836:f03] Voice Name Albert
I want to make sure I understand fully what is going on here. In order to do this I have a couple of questions.
It looks like the numberOfRowsInTableView: method was automatically called after the init method. Is this correct?
How often does objectValueForTableColumn: get called? What events prompt that method to get called? Also, in the code there, the return statement confused me. Where exactly does this return value go?
As a side note they wanted me to connect the Outlets and the AppController via control+clicking (via the connections panel) and linking them in that way. What alternatives are there avaialble for connecting delegates and datasources to different kinds of views without doing this? I am assuming that adding NSTableViewDelegate in the controller header file might be one way. If you have the option of control+click connecting all your views to outlets and so on vs programmatically setting it all up is it just a matter of preference at this point? It just seems to me that in order to understand what is going on it might be better to just write the code yourself.
1) Both numberOfRowsInTableView and objectValueForTableColumn:Row: get called soon after the nib is unarchived (I'm not sure of the exact order of things), so the table view can be populated with data.
2) The loading of the table and reloading or adding new data as well as scrolling will cause objectValueForTableColumn:row: to be called (by the table view). It gets called for each row that is populated. The value goes to the table view, that's how it gets its data.
3) The other way to connect delegates is to do it in code with setDelegate:, but putting NSTableViewDelegate in the .h file does not do that -- that's just a promise to the compiler that you will implement some or all of the delegate messages.

NSTableView doesn't refresh?

I have an NSTableView whose data source is an NSMutableArray. The count of the array is returned in the numberOfRowsInTableView: method.
The table is refreshed by make a selection in other components such as an NSPopupButton, which changes the content of the array and performs the reloadData method.
The problem is that sometimes I am getting kind of data being written all over each other. So for example if the count is 4 and then after an action is performed it is 10, the additional 6 records are written below the first 4. It is only after I manually scroll the table up and down that the data (correct data) is displayed.
The console displays a message that
-[NSCFArray objectAtIndex:]: index(-1(or possibly larger)) beyond bounds (1056).
I understand that the error message seems to point out that it is an array beyond bounds, however how can that be when I am only returning, say [array objectAtIndex: rowIndex] in the
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
method?
Also, the count return is returned in the numberOfRowsInTableView is always being correct (I know this since I am logging the count using a timer every few seconds).
Any ideas? Thanks!
Edit 1:
I am returning the data as follows:
pc = [[PrinterClass alloc]init];
pc = [ads_printers_array objectAtIndex:rowIndex];
if (aTableColumn == tc_make)
return [pc make];
if (aTableColumn == tc_model)
return [pc model];
if (aTableColumn == tc_driver_name)
return [pc ppd];
if (aTableColumn == tc_rdp_driver)
{
if ([pc decider] == 1)
return [pc rdp];
else return [pc model];
}
The array that I mention above is filled with object of a class that I created called PrintersClass.
To reload the data, it's just reload data as usual:
[ads_rdp_driver_table reloadData];
With ads_rdp_driver_table being the tableView name.
The thing is that this problem is coming up at random intervals at not at specific points. That is why I can't put my finger on it..
Are you 100% sure you have connected the delegate and datasource ?
You can also do this in code
ads_rdp_driver_table.datasource = self;
ads_rdp_driver_table.delegate = self;
add this is in f.ex. awake from nib.
You also have to connect the tableview itself to an iboutlet, otherwise you can not call it by name.

Obtain a stringValue from NSTableView

I have a simple NSTableView which I have loaded with data from a NSMutableArray. When I select a row (entry) in the tableView and modify it, I can find the row index, what I cannot get is the edited entry out as a string so I can modify the array. I can find lots of information on selecting rows, etc., but not on how to get the actual modified string. I keep thinking this should be real simple. Help please.
Part of my code:
- (IBAction)toDoEdit:(id)sender // Accept the edited data
{
NSString *toDoItem = [[toDoTableCell:toDoTableView dataCellFoTableColumn:0 row:rowToBeEdited] stringValue];
// I get the error "dataCellForTableColumn' method cannot be found.
[toDoArray replaceObjectAtIndex:rowToBeDeleted withObject:toDoItem];
[toDoTableView reloadData];
[toDoTableView deselectRow:rowToBeDeleted];
}
~~~~~~~~~~~
// This method should return the cell value of the selected row
- toDoTableCell:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return toDoTableCell; // No errors here but toDoTableCell is nil.
}
The 'Add' data to tableView works, 'Delete' data from tableView works, I just cannot get the edited data out of tableView so I can reload the data with the corrections.
What you are looking for is an NSTableView Delegate method:
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
This will return the NSCell that is in the row and column that you specify. From the NSCell you should be able to extract the value that you need. Depending on how you are using your NSCell you would either call [cell stringValue] or [cell objectValue].
Try this:
– tableView:setObjectValue:forTableColumn:row:
in - NSTableViewDataSource Protocol Reference
--- Edited based on comment ---
Above method is called whenever user tries to edit a table row, it also provides user with changed value as parameter. If you are trying to edit the row in table itself then it should serve your purpose. You can simply check the objectValue obtained as parameter and verify if it is correct or not. In case it is incorrect you can modify the obtained value and set it in todoArray.
Briefly:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
// below case is an example, you can add your own
if([anObject isEqualToString:#"Incorrect"])
{
anObject = #"Correct";
}
// Considering todoArray is array of dictionary items containing keys as table-column identifiers
NSMutableDictionary *originalData = [todoArray objectAtIndex:rowIndex];
[originalData setValue:anObject forKey:[aTableColumn identifier]];
[toDoTableView reloadData];
}
To get the value being edited you can simply use this code in above method, before setting the new value:
NSString *editedValue = [[todoArray objectAtIndex:rowIndex] valueForKey:[aTableColumn identifier]];
Hope this helps :)
It is simple. Read up on Cocoa Bindings and NSArrayController.
Take a look at the NSTableView methods selectedColumn, selectedColumnIndexes, selectedRow and selectedRowIndexes. I guess they should provide you with the needed information.
Now you can query the model, i.e. the array, for the data you need.