How to reload and animate just one UITableView cell/row? - objective-c

how can I reload and animate just one cell/row ?
Right now i download some files. Everytime a file finished downloading, i call it's finished delegate and call [tableview reload].
But then the whole table reloads. And how can i animate the table, so that it doesn't blink in. For example a fade effect or so.
greets Max

Use the following UITableView instance method:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
You have to specify an NSArray of NSIndexPaths that you want to reload. If you just want to reload. If you only want to reload one cell, then you can supply an NSArray that only holds one NSIndexPath. For example:
NSIndexPath* rowToReload = [NSIndexPath indexPathForRow:3 inSection:0];
NSArray* rowsToReload = [NSArray arrayWithObjects:rowToReload, nil];
[myUITableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
You can see the UITableViewRowAnimation enum for all the possible ways of animating the row refresh. If you don't want any animation then you can use the value UITableViewRowAnimationNone, as in the example.
Reloading specific rows has a greater advantage than simply getting the animation effect that you'd like. You also get a huge performance boost because only the cells that you really need to be reloaded are have their data refreshed, repositioned and redrawn. Depending on the complexity of your cells, there can be quite an overhead each time you refresh a cell, so narrowing down the amount of refreshes you make is a necessary optimization that you should use wherever possible.

Swift 4 & 5
For TableView
let index = IndexPath(row: 0, section: 0)
tableView.reloadRows(at: [index], with: .automatic)
For CollectionView
let index = IndexPath(item: 2, section: 0)
collectionView.reloadItems(at: [index])

If you only need to update the text in the tableview cell..
UITableViewCell *cell = [_myTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]];
cell.textLabel.text = [NSString stringWithFormat:#"%i", (int)_targetMaxDistanceSlider.value];

Swift
You can use
let selectedIndexPath = IndexPath(item:0 , section: 0)
self.tableView.reloadRows(at: [selectedIndexPath], with: .none)
in order to reload a specific cell.

Apple Document
done it with new syntax
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];

To reload particular cells in a tableView at given indexPaths, you need to create an array of indexPaths and then call function reloadRowsAtIndexPaths on the tableView.
Here is the example:
Swift 5:
let indexPathsToReload = [indexPath1, indexPath2, indexPath3]
tableView.reloadRows(at: indexPathsToReload, with: .none)
Objective C:
NSArray *indexPaths = #[indexPath1, indexPath2, indexPath3];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
or
NSArray *indexPaths = [NSArray arraywithobject:#"indexPath1", #"indexPath2", #"indexPath3",nil];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];

Related

Creating NSCollectionView with datasource programmatically

I am trying to create a NSCollectionView programmatically using a NSCollectionViewDataSource.
The code is very simple:
self.collectionView = [[NSCollectionView alloc] init];
// Add collection view to self.view etc.
self.collectionView.dataSource = self;
[self.collectionView registerClass:[NSCollectionViewItem class] forItemWithIdentifier:#"test"]
self.collectionView.collectionViewLayout = gridLayout;
[self.collectionView reloadData]
This leads to the following methods getting called (if I don't set the collectionViewLayout property explicitly these two don't get called either):
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView*)collectionView
- (NSInteger)collectionView:(NSCollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
However, collectionView:itemForRepresentedObjectAtIndexPath: is never called. Is there something else that I need to do in order to make sure that the last data source method is called? I have made sure that the two count calls return > 0, so that's not the problem.
So it seems that the problem was actually that I wasn't wrapping the NSCollectionView in a NSScrollView. This probably has to do with the layout being done incorrectly (so the items aren't requested from the data source) if it is not wrapped in a scroll view.
I've been working through different scenario's in the past days, and I dare say that using an NSScrollView, or not, makes practically no difference. With or without scrollView, I've ended up with the same errors and limitations.
What does make a huge difference is the choice between "old school" and the new-fangled collectionView. By "old school" I mean setting the itemPrototype and contents properties, something like this:
NSCollectionView *collectionView = [[NSCollectionView alloc] init];
collectionView.itemPrototype = [TBCollectionViewItem new];
collectionView.content = self.collectionItems;
NSInteger index = 0;
for (NSString *title in _collectionItems) {
NSIndexPath *path = [NSIndexPath indexPathForItem:index inSection:0];
TBCollectionViewItem *item = [collectionView makeItemWithIdentifier:#"Test" forIndexPath:path];
item.representedObject = title;
index++;
}
// Plays well with constraints
New school, something along these lines:
NSCollectionView *collectionView = [[NSCollectionView alloc] init];
collectionView.identifier = TBCollectionViewIdentifier;
[collectionView registerClass:[TBCollectionViewItem class] forItemWithIdentifier:TBCollectionViewItemIdentifier]; //register before makeItemWithIdentifier:forIndexPath: is called.
TBCollectionViewGridLayout *gridLayout = [TBCollectionViewGridLayout collectionViewGridLayout:NSMakeSize(250, 100)]; //getting the contentSize from the scrollView does not help
collectionView.collectionViewLayout = gridLayout;
collectionView.dataSource = self;
Now, you may have noticed the comment that registerClass: must be called before makeItemWithIdentifier:forIndexPath. In practice, that means calling registerClass: before setting .dataSource, whereas in your code you set .dataSource first. The docs state:
Although you can register new items at any time, you must not call the makeItemWithIdentifier:forIndexPath: method until after you register the corresponding item.
I wish I could say that by switching those two lines, all layout problems will be solved. Unfortunately, I've found that the .collectionViewLayout / .dataSource combination is a recipe for (auto)layout disaster. Whether that can be fixed by switching from NSCollectionViewGridLayout to flowLayout, I'm not yet certain.

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/

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).

UITableView scrollToRowAtIndexPath not working for last 4

I am in a UITableViewController and I have textfields inside cells. When the user clicks on the textfield, I implement the UITextFieldDelegate and in method textFieldDidBeganEditing I determine the index of the cell and scroll to that position. It works perfectly for all cells expect for the last 4-5 cells. What am I doing wrong? Thanks.
scrollToRowAtIndexPath: method scrolls the cell to UITableViewScrollPositionTop or UITableViewScrollPositionMiddle only if the tableView's contentSize is large enough to bring the cell to those positions. If the cell you are trying to scroll to top or middle is the last cell or among the last few cells, it can not be scrolled to middle or top. In these situations you need to calculate the contentOffset manually for those cells.
Edit - Calculating the contentOffset:
In order to calculate the contentOffset use the method as specified by #Schoob. Put the following code in your textFieldDidBeginEditing method.
CGPoint origin = textField.frame.origin;
CGPoint point = [textField.superview convertPoint:origin toView:self.tableView];
float navBarHeight = self.navigationController.navigationBar.frame.size.height;
CGPoint offset = tableView.contentOffset;
// Adjust the below value as you need
offset.y += (point.y - navBarHeight);
[tableView setContentOffset:o animated:YES];
I found the key to using scrollToRowAtIndexPath was to make sure to call this after the view has been presented. I.e. pushed on to a navigationcontroller or presented modally.
My working code goes like this
Calling code
MyViewController *cont = [[MyViewController alloc] initWithMedication:medication];
cont.sectionToShow = 1;
[self.navigationController pushViewController:cont animated:YES];
[cont release];
Viewcontroller code inside viewWillAppear
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:self.sectionToShow];
[cont.tableview scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
If I did it any other way there were always edge cases where it didn't work. I guess we want to scroll late in the presentation cycle.
This is what you should be using when trying to determine the indexPath of a view/textField added on a UITableViewCell
CGPoint point = [textField.superview convertPoint:textField.frame.origin toView:self.tableView];
Then use point with
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:point];

Slide UITableViewCell

My goal is to have a UITableViewCell slide off one side of the screen (like in Twitter) and then slide back on from the other side. I'm able to make the cell slide off the screen to the right, but I can't seem to figure out how to get it to slide back onto the screen from the left right after. Here's my code to slide it off to the right:
UITableViewCell *cell = [self cellForRowAtIndexPath:indexPath];
[cell.layer setAnchorPoint:CGPointMake(0, 0.5)];
[cell.layer setPosition:CGPointMake(cell.frame.size.width, cell.layer.position.y)];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position.x"];
[animation setRemovedOnCompletion:NO];
[animation setDelegate:self];
[animation setDuration:0.14];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[cell.layer addAnimation:animation forKey:#"reveal"];
Thanks for any help!
UITableView provides methods to insert and remove rows with animation and handles all the animation for you. Try something like this:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:myIndexPaths
withRowAnimation:UITableViewCellRowAnimationRight];
[tableView endUpdates];
And then to slide in a new cell:
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:myNewIndexPaths
withRowAnimation:UITableViewCellRowAnimationLeft];
[tableView endUpdates];
The beginUpdates/endUpdates methods cause the animations to be batched up and executed all at once.
Beware of 2 things:
With a lot of table data, the insert can take a long time (expecially if you are essentially replacing the whole table). Calling [tableView reloadData] works better in this case, but the deleteRowsAtIndexPath:withRowAnimation: can still be used for a nice visual effect.
You have to be sure that the actual data backing your table changes accordingly and correctly. That is to say, if you are removing or inserting rows form the table, then your array (or whatever) that is feeding the table must also correctly reflect how many rows are in the table at each step.
The behavior you want should be done by animating subviews of the cell, not the cell itself. The cell should stay in place in the table. Create a custom cell with the subviews you want in the contentArea and then slide those on/off as you like.
The standard UIView block based animations should work great for this as well.