Retrieving cell contents from NSOutlineView - objective-c

I have an odd issue cropping up with an NSOutlineView. The view is essentially a list of apps with associated files as children. I populate the view by hand in it's data source and all of that works fine. What I now want to do is have a button to remove an item. In order to do it I implemented a method, removeAppOrFile, like so:
- (IBAction)removeAppOrFile:(id)sender
{
NSInteger selectedRow = [myView selectedRow];
if (selectedRow == -1) //ie. nothing's selected
{
return;
}
NSTableColumn *col = [myView tableColumnWithIdentifier:#"Column 1"];
NSCell *cell = [col dataCellForRow:selectedRow];
NSString *item = [cell stringValue];
NSLog(#"The row is: %ld\nThe column is: %#\nThe cell is: %#\nThe selected item is: %#",selectedRow, col, cell, item); // For testing purposes
}
myView is an IBOutlet connected to my NSOutlineView.
If I select a different row and click the button the value for selectedRow will change properly, but the NSCell object never changes, and what should be it's value (ie. NSString item) always shows that of the last visible item (ie. if there's an item with children as the last item NSString item will be the parent if it's not expanded, or the last child if it is expanded).
The weird thing is that I use basically the same code elsewhere for a doubleAction on an NSOutlineView and it works perfectly. In that case, the code is as below:
- (void)editedAppOrFile:(id)sender
{
NSInteger rowNumber = [sender clickedRow];
NSTableColumn *col = [sender tableColumnWithIdentifier:#"Column 1"];
NSCell *cell = [col dataCellForRow:rowNumber];
NSString *item = [cell stringValue];
NSLog(#"The row is: %ld\nThe column is: %#\nThe cell is: %#\nThe selected item is: %#",selectedRow, col, cell, item); // For testing purposes
}
In this case sender is the outlineView. item & cell change with rowNumber change.
Any idea as to why it's not working in the first example?

There are a few issues with your approach.
You're getting the data cell, not the -preparedCellAtColumn:row:, so you have no guarantees about what its internal object value will be.
You can ask the outline view directly for -itemAtRow:.
If you're trying to remove (in the first case) or edit (the second case), you really only need to modify your data source and then note number of rows changed (first case) or reload data for row (second case).

Related

Are the cells in view based NSOutlineView getting released on collapse?

I've never worked before with NSOutlineView and I'm curious if the cells are getting released and deallocated when the item is collapsing?
I feel like my cells are being stacked on top of each other after every time I expand and collapse the item.
Any kind of help is highly appreciated!
HSObjectViewModel* ovm = item;
HSObjectTableCellView *oCell = [outlineView makeViewWithIdentifier:#"OutlineCell"
owner:self];
oCell.textField.stringValue = ovm.hsObject.name;
NSImage* im = [[NSImage alloc] init];
if(ovm.isVisible) {
im = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:#"on" ofType:#"png"]];
} else {
im = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:#"off" ofType:#"png"]];
}
[oCell.eyeButton setImage:im];
oCell.eyeButton.target = self;
oCell.eyeButton.action = #selector(visibilityButtonClicked:);
[[oCell.eyeButton cell] setHighlightsBy:0];
There are two types of NSOutlineView. One is view-based (the more flexible one to use) and the other is cell-based. Assuming youre using a view-based, the NSTableCellViews in the outline view will not get deallocated when you collapse an item. The cells simply get de-queued i.e. removed from the screen to be used later.
This is done for memory effeciency reasons. The logic is "why allocate lets say 2000+ cellViews if the screen is only able to display 20 at a time?" So the cells will get de-queued (to be used later) and not deallocated generally.
HOWEVER, this behavior is unpredictable. If you set up your code the standard way, then the system will be managing the cells. You cant be 100% sure when a cell will be deallocated. If your users can delete cells from the NSOutlineView, then the chance of cells being deallocated increases.
-Edit-
Based on the comments below, you need to reset the cells after dequeuing them. Assuming your code looks something like this.
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
NSTableCellView *aCellView = [outlineView makeViewWithIdentifier:[tableColumn identifier] owner:self];
// You can reset the cell like so
if (item.status == 0) { // Assuming your item has a property called status (you can make what ever property you want)
aCellView.imageView.image = [NSImage imageNamed:#"redImage"];
} else if (item.status == 1) {
aCellView.imageView.image = [NSImage imageNamed:#"blueImage"];
} else {
aCellView.imageView.image = nil;
}
}
So basically, you observe the properties of the item (properties you should declare to distinguish what item is what) and depending on the property, you reset the cell to show the correct values. In the example above, the cell has a image status 0 = a red image, 1 = blue image, anything else = no image. Had i not reset the cells, when ever i collapse, some cells will have old values of other cells since they are being reused.

Object/Item out of UICollectionView cells instead of index?

I've been trying to save the state or order of cells in a UICollectionView after they have changed locations in the UICollectionView. When I use indexPathsForVisibleItems I get indexes that are always in the same order. They do not reflect order change. If I could get the object at each indexPath I could at least make an attempt to iterate through them to get the resulting order.
I used this code. And found that it returned a refreshed state of indexPaths in [0,0]. The ordering is always the same because the it tells me what cells have something in them not what is in each cell. I just don't know enough or where to start to get the object/item in each cell.
NSArray *visible = [self.collectionView indexPathsForVisibleItems];
NSMutableArray *rowsArray = [NSMutableArray arrayWithCapacity:[visible count]];
[visible enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[rowsArray addObject:#(indexPath.item)];
I need the items or objects at each location. How do I iterate through all the cell locations to retrieve the item there. I plan on using the count of an array used to populate the UICollectionView to loop through all the locations and get the object stored there. There is only one section and cells seem to be storing objects like this <Slide: 0x7585200>
The function above returns NSLogs in this format "<NSIndexPath 0x8490390> 2 indexes [0, 0]"
I need to get what is in that location.
I'm not familiar with NSIndexPath or it's formatting. Even just the command/function would help for me to get an object out. Thanks.
(Addendum) I'm think of using something like [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; Not sure what I can store it in.
CollectionView is a collection of UIViews (call UICollectionViewCell). Each cell has an indexPath (section,row) mean the layout position in collectionView content. In the case you has 1 section, section always = 0.
visibleCells mean all cells visible on screen at that time (visible in scroll frame or collectionView frame).
indexPathsForVisibleItems = indexPath array of visibleCells
cellForItemAtIndexPath is a method to get a collectionViewCell at any indexPath you want, often use to get cell when select:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
NSLog(#"%#",cell);
}
Investigate more from Apple UICollectionView video and example:
https://developer.apple.com/library/ios/#samplecode/CollectionView-Simple/Introduction/Intro.html
https://developer.apple.com/videos/wwdc/2012/

How to get elements inside a selected row in a NSTableView?

I have an NSTableView of content type View Based with two columns.
My objects are structured as follows:
Table View
Table Column
Table Cell View
Text Field
Table Column
Table Cell View
Popup Button
For a selected row, I want to get an element inside the selected row for a specific column. Specifically, I want to get the Text Field in the first column for the selected row.
I have an outlet to the table view and I have the index of the selected row so far, but that's about it:
- (void)getSelectedTextField
{
NSInteger selected = [tableView selectedRow];
}
Any ideas on how I can go about tackling this problem?
Edit: This is what I am trying to do: I want to change the text field to be in an editing state and focus on it so the user can start editing the text field value as soon as it is selected
I seemed to have solved my own problem:
- (void)getSelectedTextField
{
NSInteger selected = [tableView selectedRow];
// Get row at specified index
NSTableCellView *selectedRow = [tableView viewAtColumn:0 row:selected makeIfNecessary:YES];
// Get row's text field
NSTextField *selectedRowTextField = [selectedRow textField];
// Focus on text field to make it auto-editable
[[self window] makeFirstResponder:selectedRowTextField];
// Set the keyboard carat to the beginning of the text field
[[selectedRowTextField currentEditor] setSelectedRange:NSMakeRange(0, 0)];
}
Use can get all element of table view cell by get its subview. Suggest show log list subview before use it
NSInteger index = [self.tableViewFiles selectedRow];
// Get row at specified index of column 0 ( We just have 1 column)
NSTableCellView *cellView = [self.tableViewFiles viewAtColumn:0 row:index makeIfNecessary:YES];
// Get row's checkBox ( at index 0, textFields is 1,2)
NSButton *checkBox = [[cellView subviews] objectAtIndex:0];
If the text field is the only subview of your table view cell, you can get a reference to it with the following code, and select its text, so it's ready for editing:
-(void)tableViewSelectionDidChange:(NSNotification *)notification {
NSInteger row = [notification.object selectedRow];
NSTextField *tf = [[[notification.object viewAtColumn:0 row:row makeIfNecessary:NO]subviews] lastObject];
[tf selectText:tf.stringValue];
}

UITableView: load all cells

Is it possible to load all cells of an UITableView when the view is loaded so that they are not loaded when I'm scrolling?
(I would show a loading screen while doing this)
Please, it's the only way at my project (sorry too complicate to explain why ^^)
EDIT:
Okay let me explain you, what I'm definite doing:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [NSString stringWithFormat:#"Identifier %i/%i", indexPath.row, indexPath.section];
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary *currentReading;
if (cell == nil)
{
cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UILabel *label;
UIView *separator;
if(indexPath.row == 0)
{
// Here I'm creating the title bar of my "table" for each section
}
else
{
int iPr = 1;
do
{
currentReading = [listData objectAtIndex:iPr-1];
iPr++;
} while (![[currentReading valueForKey:#"DeviceNo"] isEqualToString:[devicesArr objectAtIndex:indexPath.section]] ||
[readingresultsArr containsObject:[currentReading valueForKey:#"ReadingResultId"]]);
[readingresultsArr addObject:[currentReading valueForKey:#"ReadingResultId"]];
//
// ...
//
}
}
return cell;
}
My error happens in the do-while-loop:
"listData" is an array with multiple dictionaries in it.
My problem ist that when I’m scrolling my table slowly down, all is fine, but when I’m scrolling quickly to the end of the view and then I’m scrolling to the middle, I get the error that iPr is out of the array’s range. So the problem is, that the last row of the first section has already been added to the "readingresultsArr", but has not been loaded or wants to be loaded again.
That’s the reason why I want to load all cells at once.
You can cause all of the cells to be pre-allocated simply by calling:
[self tableView: self.tableView cellForRowAtIndexPath: indexPath];
for every row in your table. Put the above line in an appropriate for-loop and execute this code in viewDidAppear.
The problem however is that the tableView will not retain all of these cells. It will discard them when they are not needed.
You can get around that problem by adding an NSMutableArray to your UIViewController and then cache all the cells as they are created in cellForRowAtIndexPath. If there are dynamic updates (insertions/deletions) to your table over its lifetime, you will have to update the cache array as well.
put a uitableview on a uiscrollview
for example , you expect the height of the full list uitableview is 1000
then set the uiscrollview contentsize is 320X1000
and set the uitableview height is 1000
then all cell load their content even not visible in screen
In my case it was that I used automaticDimension for cells height and put estimatedRowHeight to small that is why tableview loaded all cells.
Some of the answers here and here suggest using automaticDimension for cells height and put mytable.estimatedRowHeight to a very low value (such as 1).
Starting with iOS 15 this approach seems not to work anymore. Hence, another way to achieve the table to "load" all cells could be by automatically scrolling to the last cell. Depending on the tables height and how many rows it can show some cells are discarded but each cell would be loaded and shown at least once.
mytable.scrollEnabled = YES;
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:cellCount - 1 inSection:0];
[mytable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
mytable.scrollEnabled = NO;
If you want to scroll up again just scroll to the top as outlined here.
Following the comment that was made by juancazalla, I found that if you have a tableView that is using automaticDimension, loading all the cells at once can be best achieved by setting estimatedRowHeight to a low value (such as 1).

How can I select a cell without touching the screen?

I have a UITableViewController and want to move down a cell in my table every time the accelerometer's X axis is greater then 0.5 (when this event occurs i increment a value named "TEST" ). How can i change the background of a cell that has it's indexPath.row equal to TEST ?
Here is how i try to access the method(from the accelerometer function) but it gives me an error :
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
if(acceleration.x>0.5)
TEST++;
if(TEST<0) TEST=0;
if(TEST>19) TEST=19;
NSIndexPath *temp = [[NSIndexPath alloc] initWithIndex:TEST];
[self tableView:[self tableView] didSelectRowAtIndexPath:temp];
}
I have 20 rows in my table (hence the clamp).
selectRowAtIndexPath:animated:scrollPosition:
Selects a row in the receiver identified by index path, optionally scrolling the row to a location in the receiver.
(void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition
Parameters
indexPath
An index path identifying a row in the receiver.
animated
YES if you want to animate the selection and any change in position, NO if the change should be immediate.
scrollPosition
A constant that identifies a relative position in the receiving table view (top, middle, bottom) for the row when scrolling concludes. See “Table View Scroll Position”
here
   NSIndexPath *temp = [[NSIndexPath alloc] initWithIndex:TEST];
[self.tableview selectRowAtIndexPath:temp animated:YES scrollPosition:UITableViewScrollPositionNone];
How about scrollToRowAtIndexPath:atScrollPosition:animated:?