NSTableView sort not getting correct ID - objective-c

I'm using a Table View in a Cocoa application. I have set the double click action to do the following method when it occurs:
- (void)doubleClickInTable:(id)sender {
int rowIndex = [sender selectedRow];
if (rowIndex != -1) {
[userEditController setData:[[self users] objectAtIndex:rowIndex]];
[self showUserEditPanel];
}
}
As you can see, the EditController receives the object that is being edited. This object is the object that is located at rowIndex of the source array. This works very well most of the time, but once I started testing the sorts it is setting the wrong object. This is because the index of the clicked row in the table is different then the source array due to the sort moving rows around.
How do I fix this issue?

You could create a sorted array using the same sort descriptors, and retrieve the object at rowIndex in that. The other way, if you're using an array controller, would be to retrieve the object at that index in the controller's arrangedObjects array, which is already sorted (hence its name).

Related

Update Selected Core Data Object from Table View

I have hit a bit of a brick wall and am looking for some help with a Cocoa OSX app i am trying to put together.
I have a single entity in core data, which is being populated from a Dictionary pulled from the net. The core data objects are then displayed in a TableView using bindings and an array controller.
Now, i want the ability to detect the selected object in the table, then when a button is pressed in the GUI for it to update a specific attribute of the selected entity.
This is where i have hit a brick wall, lots of info on how to pull/update objects when pulled with a predicate, and lots on how to bind directly to the array controller to add/remove/delete. But nothing on how to update a hidden property with a value that's stored in code.
Any help/pointers greatly appreciated, especially if it's OSX rather than iOS orientated!
Thanks
Gareth
Actually i managed to work this out.
First i implemented a function that gets the current selected object from the array controller and returns it.
-(Tweet*)getCurrentSelectedTweet {
if ([[self.twitterClientsController selectedObjects] count] > 0) {
return [[self.twitterClientsController selectedObjects] objectAtIndex: 0];
} else {
return nil;
}
}
Then i use this function bound to an IBAction to call it and modify the object:
- (IBAction)approveTweet:(id)sender {
Tweet *selectedTweet = [self getCurrentSelectedTweet];
if (selectedTweet) {
selectedTweet.approved = [NSNumber numberWithBool:TRUE];
NSLog(#"%#", selectedTweet);
}
}

Binding label's value to the count property of an NSArray

I want to show in a label the current number of elements in an NSArray called pages. Following other guides I did the following:
Created an NSArrayController in IB (called pagesController) and bounded it to the NSArray pages
Bounded the value property of the label to the NSArrayController with Controller Key = arrangedObjects and Model Key Path = #count
The problem is that when the program is running the label always shows "0". To check if the things are working correctly I tried to log a message when the user clicks on another button (the button basically inserts a new element in the pages array): NSLog(#"count = %d", [self.pagesController valueForKeyPath:#"arrangedObjects.#count"]); in this case the output is correct, i.e. the current number of elements in the array printed is correct.
Where am I mistaking?
You many not be using KVC correctly. Your array controller will only be notified with the pages property is redefined, not when objects are added to the array. Try wrapping your code with the appropriate change notifications:
[self willChangeValueForKey:#"pages"];
[pages addObject:someObject];
[self didChangeValueFOrKey:#"pages"];

reloadData in NSTableView but keep current selection

I have anNSTableView showing the contents of a directory. I watch for FSEvents, and each time I get an event I reload my table view.
Unfortunately, the current selection then disappears. Is there a way to avoid that?
Well, you can save selection before calling reloadData and restore it after that.
NSInteger row = [self.tableView selectedRow];
[self.tableView reloadData];
[self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
This worked for me in some cases. But if you insert some items BEFORE the selected row, you should andjust your row variable by adding count of added items to it.
Swift 4.2
Create an extension and add a method which preserves selection.
extension NSTableView {
func reloadDataKeepingSelection() {
let selectedRowIndexes = self.selectedRowIndexes
self.reloadData()
self.selectRowIndexes(selectedRowIndexes, byExtendingSelection: false)
}
}
Do this in case you use the traditional way of populating table views (not NSArrayController).
It depends on how you populate your NSTableView.
If you have the table view bound to an NSArrayController, which in turn contain the items that your table view is displaying, then the NSArrayController has an option to preserve the selection. You can select it (or not) from within Interface Builder as a property on the NSArrayController. Or you can use the setPreservesSelection method in code.
However, if you completely replace the array of items that the NSArrayController manages each time you get your FSEvents, then maybe the preservation of selection cannot work. Unfortunately the Apple docs on this property of NSArrayController are a bit vague as to when it can and cannot preserve the selection.
If you are not using an NSArrayController, but maybe using a dataSource to populate the table view, then I think you'll have to manage the selection yourself.
In the case of using Data Source, Apple Documentation in the header file on reloadData() is that
The selected rows are not maintained.
To get around, you can use reloadDataForRowIndexes(rowIndexes: NSIndexSet, columnIndexes: NSIndexSet). As mentioned in the same header file
For cells that are visible, appropriate dataSource and delegate methods will be called and the cells will be redrawn.
Thus the data will be reloaded, and the selection is kept as well.
A variant on #silvansky's answer.
This one has no need to keep track of count of items inserted/deleted. And it maintains multiple selection.
The idea is to...
1. create an array of selected objects/nodes from the current selection.
2. refresh the table using reloadData
3. for each object obtained in step 1, find/record it's new index
4. tell the table view/outline view to select the updated index set
- (void)refresh {
// initialize some variables
NSIndexSet *selectedIndexes = [self.outlineView selectedRowIndexes];
NSMutableArray *selectedNodes = [NSMutableArray array];
NSMutableIndexSet *updatedSelectedIndex = [NSMutableIndexSet indexSet];
// 1. enumerate all selected indexes and record the nodes/objects
[selectedIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[selectedNodes addObject:[self.outlineView itemAtRow:idx]];
}];
// 2. refresh the table which may add new objects/nodes
[self.outlineView reloadData];
// 3. for each node in step 1, find the new indexes
for (id selectedNode in selectedNodes) {
[updatedSelectedIndex addIndex:[self.outlineView rowForItem:selectedNode]];
}
// 4. tell the outline view to select the updated index set
[self.outlineView selectRowIndexes:updatedSelectedIndex byExtendingSelection:NO];
}

How to edit data of NSArrayController (or to use something else)

i created a nsarraycontroller to display its data on my nstableview. this is working. but how do i modify this data now?
[arrayController addObject:[...]]
adds an object, how do i get it back?
i tried:
NSMutableArray *data = [arrayController mutableArrayValueForKey:#"column1"];]
but then i get this error
2011-05-29 19:25:50.125 TestApp[1665:903] [<NSArrayController 0x113808500> valueForUndefinedKey:]: this class is not key value coding-compliant for the key column1.
(the objects in my arraycontroller representing the rows in my view are of a class (named FileEntry) consisting of 4 properties and i added these as keys to my nsarraycontroller)
i thought kvc-compilant means every attribute has a getter and setter? (and isnt that what properties do?).
i tested my class if i could use this function:
FileEntry *entry = [[FileEntry alloc] initWithUrl:#"test"]; //(this adds the string "test" to the property "fileurl")
NSLog(#"%#\n", [entry valueForKey:#"fileurl"]);
and it returns:
> 2011-05-29 19:31:54.760 TestApp[1718:903] test
and it works. so how can my class not be kvc-compilant?
anyway, i also tried to use the tableviews datasource instead but cant get those 2 functions to work. would that be of more use than the nsarraycontroller? is it even possible to modify data of the nsarraycontroller?
Usually you need a contend array for the NSArrayController to handle, so you could simply take this array and use the NSMutableArray methods. For example you have in your MyController.h a declaration of an array with its setter and getter methods, and you only have to bind the contend array of the array controller to it. Then you can take two buttons for the add and remove methods of the array controller to add something. Now you must bind the value of the column to e.g. FileEntry.fileurl of your array controller managed objects, Now you should be able to add stuff to the table view. And you can also Edit the names in the table view. If you want to get some stuff out of the array in your program somewhere, you can use an outlet of your table view like this:
NSInteger row = [myTableViewOutlet selectedRow];
FileEntry* myEntry = [myArray objectAtIndex:row];

Using Array Controllers to restrict the view in one popup depending on the selection in another. Not core data based

I am working on an app that is not core data based - the data feed is a series of web services.
Two arrays are created from the data feed. The first holds season data, each array object being an NSDictionary. Two of the NSDictionary entries hold the data to be displayed in the popup ('seasonName') and an id ('seasonID') that acts as a pointer (in an external table) by matches defined for that season.
The second array is also a collection of NSDictionaries. Two of the entries hold the data to be displayed in the popup ('matchDescription') and the id ('matchSeasonId') that points to the seasonId defined in the NSDictionaries in first array.
I have two NSPopUps. I want the first to display the season names and the second to display the matches defined for that season, depending on the selection in the first.
I'm new at bindings, so excuse me if I've missed something obvious.
I've tried using ArrayControllers as follows:
SeasonsArrayController:
content bound to appDelegate seasonsPopUpArrayData.
seasonsPopup:
content bound to SeasonsArrayController.arrangedObjects; content value bound to SeasonsArrayController.arrangedObjects.seasonName
I see the season names fine.
I can obviously follow a similar route to see the matches, but I then see them all, instead of restricting the list to the matches for the season highlighted.
All the tutorials I can find seem to revolve around core data and utilise the relationships defined therein. I don't have that luxury here.
Any help very gratefully received.
This is not an answer - more an extension of the previous problem.
I created MatchesArrayController and subclassed it from NSArrayController to allow some customisation.
Following the example in 'Filtering Using a Custom Array Controller' from 'Cocoa Bindings Topics', I followed the same idea as above:
MatchessArrayController: content bound to appDelegate matchesPopUpArrayData.
matchesPopup: content bound to MatchesArrayController.arrangedObjects; content value bound to MatchesArrayController.arrangedObjects.matchDescription.
I've derived the selected item from seasonPopUp:sender and used this to identify the seasonId.
The idea is to change the arrangedObjects in MatchesArrayController by defining the following in;
- (NSArray *)arrangeObjects:(NSArray *)objects
{
if (searchString == nil) {
return [super arrangeObjects:objects];
}
NSMutableArray *filteredObjects = [NSMutableArray arrayWithCapacity:[objects count]];
NSEnumerator *objectsEnumerator = [objects objectEnumerator];
id item;
while (item = [objectsEnumerator nextObject]) {
if ([[[item valueForKeyPath:#"matchSeasonId"] stringValue] rangeOfString:searchString options:NSAnchoredSearch].location != NSNotFound) {
[filteredObjects addObject:item];
}
}
return [super arrangeObjects:filteredObjects];
}
- (void)searchWithString:(NSString *)theSearchString {
[self setSearchString:theSearchString];
[self rearrangeObjects];
}
- (void)setSearchString:(NSString *)aString
{
[aString retain];
[searchString release];
searchString=aString;
}
I've used NSLog to check that things are happening the way they are supposed to and all seems ok.
However, it still doesn't do what I want.
[self rearrangeObjects]; is supposed to invoke the arrangeObjects method but doesn't. I have to call it explicity
(i.e.[matchesArrayController arrangeObjects:matchesPopUpArrayData]; )
Even then, although filteredObjects gets changed the way it is supposed to, the drop down list does not get updated the way I want it to.