Animating NSTableView with beginning and ending array states - objective-c

I've been looking over NSTableView's moveRowAtIndex:toIndex method for animating rows in a table. It's not really helpful for sorting though from what I can tell. My interpretation of how it works is, if I want to move row 0 to row 4, then the rows in between are handled appropriately. However, if I have a table view with an array backing it, and then I sort the array, I want the table view to animate from the old state to the new state. I don't know which items were the ones that moved vs the ones that shift to accommodate the moved ones.
Example:
[A,B,C,D] --> [B,C,D,A]
I know that row 0 moved to row 3 so I would say [tableView moveRowAtIndex:0 toIndex:3]. But if I apply some custom sort operation to [A,B,C,D] to make it look like [B,C,D,A], I don't actually know that row 0 moved to row 3 rather than rows 1,2, and 3 moving to rows 0,1, and 2. I would think that I should just be able to specify all of the movements (row 0 moved to row 4, row 1 moved to row 0, etc.) but the animation doesn't look correct when I try that.
Is there a better way to do this?
Edit: I found this site, which seems to do what I want but seems like a bit much for something that should be simple (at least I think it should be simple)

The documentation for moveRowAtIndex:toIndex: says, "Changes happen incrementally as they are sent to the table".
The significance of 'incrementally' can be best illustrated with the transformation from ABCDE to ECDAB.
If you just consider the initial and final indexes, it looks like:
E: 4->0
C: 2->1
D: 3->2
A: 0->3
B: 1->4
However, when performing the changes incrementally the 'initial' indexes can jump around as you transform your array:
E: 4->0 (array is now EABCD)
C: 3->1 (array is now ECABD)
D: 4->2 (array is now ECDAB)
A: 3->3 (array unchanged)
B: 4->4 (array unchanged)
Basically, you need to tell the NSTableView, step-by-step, which rows need to be moved in order to arrive at an array identical to your sorted array.
Here's a very simple implementation that takes an arbitrarily sorted array and 'replays' the moves required to transform the original array into the sorted array:
// 'backing' is an NSMutableArray used by your data-source
NSArray* sorted = [backing sortedHowYouIntend];
[sorted enumerateObjectsUsingBlock:^(id obj, NSUInteger insertionPoint, BOOL *stop) {
NSUInteger deletionPoint = [backing indexOfObject:obj];
// Don't bother if there's no actual move taking place
if (insertionPoint == deletionPoint) return;
// 'replay' this particular move on our backing array
[backing removeObjectAtIndex:deletionPoint];
[backing insertObject:obj atIndex:insertionPoint];
// Now we tell the tableview to move the row
[tableView moveRowAtIndex:deletionPoint toIndex:insertionPoint];
}];

Related

How to Modify UITableView Data Source Array Properly

Say I would like to remove rows or add rows to a table. Is it smarter for me to modify the data source and let the table do updates or use UITableView's insertRowsAtIndexPaths:withAnimation: and deleteRowsAtIndexPaths:withAnimation: selectors? Whenever I try to modify the actual data source array, whose size is also used to determine in the the data source's tableView:numberOfCellsInSection: protocol method, I am thrown an error saying that the table must have the same number of cells per section before and after a table update. The data source array itself is an NSMutableArray; does this array get automatically updated when I use the deleteRowsAtIndexPaths and insertRowsAtIndexPaths selectors? I would assume not. But how do I add or remove rows while also updating the data source array?
Yes.
NSMutableArray* array = #["a", "b", "c"]
NSLog(#"%#", array[1])
// prints "b"
[array removeObjectAtIndex:1]
// ["a", "c"]
NSLog(#"%#", array[1])
// now prints "c".

NSMutableArray: Push all objects one index forward?

I need some sort of queue system in my game, trying to figure out the right way to go.
If I have a NSMutableArray, called customerQueue, and add 4 objects to that array. There will now be objects add index, [0],[1],[2],[3].
Then I wanna deal with customer number 1, that is index [0] in customerQueue. Since the customer is no longer in the queue, I can remove it from the array. So far so good.
However, now I want the remaining 3 objects in the queue to take a "step forward", like
object at index [1] moves to index [0] and object at index [2] goes to [1]...,you get the point.
I can´t find a method in NSMutableArray.h for this, so, can it be done this way? Any similar approach?
A category for NSMutableArray that turns it into a queue is explained here.
In your case all you really need to do is to remove the first object:
[customerQueue removeObjectAtIndex:0];
This is done automatically when you remove the first element. You can never have an array which doesn't have the first index. When you remove the object at index 0, the object at index 1 automatically moves to index 0.

Detect number of different strings in Array

Basically I need a function which counts the amount of different values in an array and one other function to give me the actual count of each different value in my array.
I have an Array which contains a changing amount of values:
Array = (This, This, This, This, Is, Is, Is, Is, Is, It, It, It, It)
I want to make a list view, each section should contain the different categories like:
This
- Element 1
- Element 2
- ...
Is
- Element 1
- ...
It
- Element 1
- ...
So I need the number 3 for my number of sections and the number of child-elements for each section.
How can I achieve that? Is there a better way than a for-statement with counting indexes for each section?
Thank you!
Add each array element to an NSCountedSet. Then the count of the set is the number of distinct objects you added, and you can use countForObject: to ask the set how many there are of each distinct object.
First, make o copy of your array, to work with.
To count different values, remove all duplicates from the copied
array, and copiedArray.count will tell you what you need. (search on
your own how to remove duplicates).
To get the actual count of each group: you have the copied array already without duplicates, so make a for loop within the copied array:
for (NSString *stringer in copiedArrayWithoutDuplicates){
int counter = 0;
for(NSString *iniStr in initialArrayWithDuplicates){
if([stringer isEqualToString: iniStr]){
counter++;
}
}
NSLog(#"string %# was detected %i times", stringer, counter);
}

Using NSTableView for logging?

I've been trying to work out how to use table views and I'm a little stuck if I'm honest. I wanted to use a tableview with a limited number of rows (say 50 max). It starts of empty, with 0 rows. Then I wanted to do something along the lines of:
[self logMessage:#"Waiting for response"];
Which inserts a new row at the bottom with the above text. If I do another call to this pseudo function:
[self logMessage:#"Server response received"];
It should insert yet another new row below the previous row, and ensure it is visible. Once the above limit of 50 is reached, and a new message is inserted, I wanted the oldest message to be removed. All of this would be scrollable, with the latest being visible by default.
Am I looking at the right thing to do this? Eventually, I was hoping to have this in a nice little drawer below the main window, which I can then toggle from the main menu if needed. But as I said, I can't work out how to use a table view properly, it doesn't seem to be as straight forward as other objects are.
Any example code would be greatly appreciated!
Since log viewer is a read-only application of a UITableView, the way you do it is rather straightforward once you understand the basics. Recall that table views rely on their data models to provide them with the correct information that needs to be displayed.
A data model for "the last fifty lines of log" could be as simple as an NSMutableArray: use insertObject:atIndex: to add lines, and removeLastObject to remove the "overflow" lines, like this:
NSMutableArray *logLines = [NSMutableArray array]; // <<== this goes into the init method
-(void) addLogLine:(NSString*)line {
[logLines insertObject:line atIndex:0];
while (logLines.count > 50) {
[logLines removeLastObject];
}
}
Now you can use logLines as your table's "model": the data provider can tell how many lines there are by looking at logLines.count; the content of each row in the table will be the object at the corresponding index in logLines, and so on. Take a look at the UITableView section of your favorite iOs tutorial for the "boilerplate code" that needs to be written in order to display array elements in a UITableView.

Getting the displayed image out of an NSImageCell

I have an NSImageCell table column whose valuePath is bound to a path supplied by my object through an NSArrayController.
My NSTableViewDelegate implements the -tableView:heightOfRow: method, in order to have variable row height. I need to calculate row height based on the dimensions of the image displayed in the aforementioned column.
Right now, I'm getting horrible performance, though, since I'm calling [[NSImage alloc] initWithContentsOfFile:<path>] for each iteration. Is there any way to load the image representation that each NSImageCell has already retrieved for display?
I'm happy with the performance exhibited by using the valuePath binding, and would rather not cache each image on my own, as there will be many of them, each somewhat large.
I tried the NSTableColumn method -dataCellForRow:, which sounded perfect, except the cell returned returns an objectValue that seems to be the last row for which data was loaded.
Update (solution, unsatisfactory)
I figured out an approximate solution (posted simultaneously below), but it seems clumsy (and I've already seen it fail at random, irreproducible times), and I am still looking for a better solution.
I'm using the -tableView:willDisplayCell:forTableColumn:row: delegate method to populate a mutable dictionary with [[cell objectValue] size] (the image's size) keyed to the represented object's unique ID. Then, in the -tableView:heightOfRow: call I'm looking up the cover from this dictionary. I need to call [tableView noteNumberOfRowsChanged] after data is loaded, otherwise the dictionary isn't filled properly for the initial screen of data.
I tried the NSTableColumn method -dataCellForRow:, which sounded perfect, except the cell returned returns an objectValue that seems to be the last row for which data was loaded.
That's because the NSTableColumn only uses a single data cell for displaying the entire column, swapping out its value as it draws.
If I were you, I would probably try implementing the – tableView:willDisplayCell:forTableColumn:row: method in your NSTableViewDelegate. Then you can intercept the cell before it's about to be drawn, get its value at that moment, and cache just its height (as an NSNumber* or CGFloat). Then you can return that value in -tableView:heightOfRow:.
Then again, it's possible that -tableView:heightOfRow: gets called before – tableView:willDisplayCell:forTableColumn:row:, so that might not work. I'm not sure. But that's where I would start.