Access all cells in TableView - ios

Hi I am tying to iterate through all the cells of my tableview but My tableview variable only let's me access the visible cells. so is there a way to declare the tableview without using the dequeuereusableCellWithIdentifier? or is there a way to iterate through all the cells?
Thanks,

To efficiently display a table, cells are used and reused depending on which ones are visible onscreen. In fact, this is what dequeuereusableCellWithIdentifier is suggesting - you specify different types of cells so they can be recycled later, as new ones are displayed and the components of offscreen ones are available for reuse.
You should define what needs to be changed or retrieve cells using table view cellForRowAtIndexPath.

You Are Already Accessing All The Cells In The TableView.
Note: I lied, two or more cells may be kept for quick reuse as well by tableView, but are not shown immediately.
At a time, a tableView only shows limited amount to cells to maintain performance and memory usage. So, for this purpose dequeuereusableCellWithIdentifier() method is utilised to let iOS handle the reuse of cells when necessary.
This way, no matter how large the dataSource, from 100,1000 to 1M, for the tableView, it will show the data in its cell smoothly and without any hiccups. That is why, you have limited cells visible and only those are the total cell used and reused by the tableView again and again.
By this definition, the total cell in use are the total visibleCells. So, when you are accessing the visibleCells, you are already accessing all the cells tableView has in use.
If you want to access all the data used by the cell, then please access the dataSource of tableView, not the visibleCells only.
Kind Regards,
Suman Adhikari

Related

Getting loaded vs visible cells on a UITableView or UICollectionView

With the introduction of iOS 10, it seems like we're going to have prefetching enabled by default on UITableView and UICollectionViews. This means that cells that aren't displayed on the screen are going to be fetched before the user actually sees them.
Here are some relevant methods:
UITableView:
cellForRowAtIndexPath:: returns "nil if the cell is not visible."
visibleCells: each item represents "a visible cell in the table view."
indexPathsForVisibleRows: each item represents "a visible row in the table view."
UICollectionView:
visibleCells: "returns the complete list of visible cells displayed by the collection view."
indexPathsForVisibleItems: each item represents "a visible cell in the collection view."
cellForItemAtIndexPath:: returns "nil if the cell is not visible."
All these specifically mention "visible" in their descriptions. With the introduction of pre-fetching in iOS 10, how would I distinguish between a cell that was pre-fetched vs. one that is currently visible?
In other words:
How do I get all visible cells?
How do I get all loaded cells?
It does not look like there are any new APIs on either UITableView or UICollectionView that can help with this.
TL;DR
Take the visible in function names literally.
UITableView behaves just as it did in iOS 9.
You'll need to do some bookkeeping if you want to treat loaded vs. visible cells differently in UICollectionView on iOS 10.
UITableView and UICollectionView appear to behave very differently when it comes to prefetching.
First thing to notice is that there is a difference between prefetching cells and prefetching data:
Prefetching cells refers to the cellForRowAtIndexPath being called before the cell is actually displayed on screen. This enables the scenario where you have cells that are off-screen but still loaded.
Prefetching data refers to the prefetchDataSource methods which inform you about indexPaths that are going to be displayed on screen. You do not have a reference to the cell when this method is called, and you do not return a cell when this method is called. Instead, this method should do things like fire off a network request to download an image that will be displayed in the cell.
Note: In all of these scenarios, imagine there are 8 cells that can be displayed at any given time.
UITableView: (options: no prefetching, or prefetch data)
Does not prefetch cells, ever. In other words, it will never call cellForRowAtIndexPath on an indexPath that isn't displayed.
As such, there is no isPrefetchingEnabled property on a UITableView.
You can opt-in to prefetching data by using the prefetchDataSource.
Note that although the table view does seem to be less aggressive with reusing cells, it still appears to call cellForItemAtIndexPath when the reused cell comes back on screen. (Although I may need to do some more investigation as to this, especially for collection views.)
UICollectionView: (options: no prefetching, prefetch cells, or prefetch cells and data)
Prefetches cells by default. In other words, it will call cellForItemAtIndexPath for cells that aren't going to be immediately displayed.
The prefetching of cells only begins when the user scrolls up or down on the collection view. In other words, you will get exactly 8 calls to cellForItemAtIndexPath when the view is loaded. Only once the user scrolls down will it start asking for non-visible cells (e.g. if you scrolled down to show 2-10, it might ask for 11-14).
When the prefetched, non-visible cell comes on screen, it's not going to call cellForItemAtIndexPath again. It's going to assume that instantiation you did the first time is still valid.
You can opt-in to prefetching data by using the prefetchDataSource.
The prefetchDataSource turns out to be only useful for the initial load. In the same scenario above, when the first 8 cells are displayed, it may fire off a prefetching of data for cells 9-14, for example. However, once this initial method is called, it's useless thereafter. This is because cellForItemAtIndexPath is going to be called immediately after each call to prefetchItemsAt. For example, you'll get prefetchItemsAt:[14, 15] immediately followed by cellForItemAt:14, cellForItemAt:15.
You can opt-out of all prefetching behavior by setting isPrefetchingEnabled = false. This means you can't make a UICollectionView behave similarly to a UITableView with a prefetchDataSource. Or, in other words, you can not have a UICollectionView prefetch data only.
For both:
visibleCells, indexPathsForVisibleRows, and cellForItemAtIndexPath do exactly as they say: they only deal with visible cells. In our same scenario, if we have 20 cells loaded, but only 8 are visible on screen. All 3 of these methods will only report about the 8 on-screen cells.
So what does this mean?
If you're using a UITableView, you can use it as is and never have to worry about a difference between loaded vs. visible cells. They are always equivalent.
For UICollectionView, on the other hand, you're gonna need to do some book-keeping to keep track of loaded, non-visible cells vs. visible cells if you care about this difference. You can do this by looking at some of the methods on the data source and delegate methods (e.g. willDisplayCell, didEndDisplayingCell).

How to access UITableView Cell which is not yet in view?

I want to access/update UITableView cell (using reusable cells) which is not in the current view . I know the table view cells are reusable, that can be the reason I am unable to fetch them but is there any way of virtually making and updating. OR I have to drop the reusable cell technique. Suppose tableview have total 20 cells but only say 7 are visible in the current view of iPhone. How will i update other 13 cells which are out of view bounds
Accessing and modifying cells (even if you could) would be a bad pattern. UITableViewCells are designed to be created and modified solely in the tableView:cellForRow:atIndexPath datasource method, where framework automatically asks you what to do with cells that are about to be displayed.
The whole idea behind reusability is that the system takes care of the view for you, and all you need to do is take care of your datasource and instruct the system, using the model, how to render cells, in its allocated datasource method.
This paradigm would be defeated if we started accessing cells manually and modifying them.
You can't access those cells because they are not added to the UITableView but are kept in a queue until user scrolls to them, then they are added to the UITableView. Instead update your model, which will reflect changes on the cells.

How can I make the uicollectionview not dequeue the cells?

I don't want cells to have to be generated when they come on screen for the first time. I also don't want cells that have gone off screen to have to be regenerated when they come back on the screen.
How can I have all the cells generated on loading the UICollectionView and stay that way as I scroll up and down?
You could manage your own list of cells (pre-create them based on the datasource, create any new ones as the datasource updates, and return the appropriate cell for the indexPath desired instead of dequeuing a cell inside cellForItemAtIndexPath) but as a general rule, that's a bad idea. You may have some specific case where cell configuration performance is poor, but the answer is usually "improve cell configuration" and rarely "keep everything in memory".
Speculating: If you're thinking of doing this in order to preserve state or cache some information in the cell, that's the wrong place to put it. The cell is presentation; keep that info in the datasource element so that whatever needs to be represented can be applied to whatever cell is being used right now.

iOS iterate UITableView

I have a UITableView that collects data from a database. What I would like to know is if there is some way I can iterate in the UITableView collection and check the values of the cell? The reason I ask is because I would like to update each cell based on the current value that it has (change font, size, color, etc.). I've seen in another SO post regarding this topic, but since the cells are already created and their values are changed it is a bit harder for me. I was thinking of iterating through the UITableView before I call reloadData, but any other suggestions are welcome.
You should not iterate over the cells of UITableView, because some of them (in fact, most of them) may not be present until you request them. UITableView aggressively recycles its cells, so if a cell is not visible, it is very likely that you would be creating it from scratch only to put it back into recycle queue moments later.
Changing your model and calling reloadData the way your post suggests would be the right solution. iOS will ensure that it runs the update in a smallest number of CPU cycles possible, so you do not need to worry about the cells that are already created. This is also the easiest approach in terms of your coding effort.
A table view is for displaying data. The properties of your table cells should only be written to, not read from. The appropriate way of handling this situation would be to update your underlying model objects -- the objects that you use to populate the table view -- as the data changes, and then reload the affected rows.
The issue you'll encounter is that UITableView reuses table cells. Once a table cell scrolls off the screen, it's quite likely that the table view will reuse the same cell to display a different row.
This means it's fundamentally not possible to iterate over the table cells. When you need to refresh a row because its data has changed, you should call reloadRowsAtIndexPaths:withRowAnimation: (or reloadData if all rows have changed) and if the row is visible on screen, UITableView will call your data source methods and give you an opportunity to configure the cell for display.

Graphical glitches when adding cells and scrolling with UITableView

I am using a UITableView to display the results of a series of calculations. When the user hits 'calculate', I add the latest result to the screen. When I add a new cell, the UITableViewCell object is added to an array (which is indexed by tableView:cellForRowAtIndexPath:), and then I use the following code to add this new row to what is displayed on the screen:
[thisView beginUpdates];
[thisView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationFade];
[thisView endUpdates];
This results in the new cell being displayed. However, I then want to immediately scroll the screen down so that the new cell is the lowermost cell on-screen. I use the following code:
[thisView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
This almost works great. However, the first time a cell is added and scrolled to, it appears onscreen only briefly before vanishing. The view scrolls down to the correct place, but the cell is not there. Scrolling the view by hand until this invisible new cell's position is offscreen, then back again, causes the cell to appear - after which it behaves normally. This only happens the first time a cell is added; subsequent cells don't have this problem. It also happens regardless of the combination of scrollToRowAtIndexPath and insertRowsAtIndexPath animation settings.
EDIT:
I've now started inserting cells at the second-to-last position of the table, rather than the end, and the problem still occurs - when first inserted, a cell is 'invisible' until it goes offscreen and comes back on again. What could be causing this, and how can I force the cell to be drawn as soon as it is added to the table?
You're having problems because your updating the table without updating the data model backing it. Tables don't actually know how many rows they have nor what cells to display. They depend on the datasource and the delegate to tell them these things. Your design expects the table itself to track them.
insertRowsAtIndexPaths: is intended to be used for moving existing rows around a table, not for adding entirely new logical rows. When you insert an entirely new cell, the tableview looses track of how many rows it actually has.
Before you display a new row, the first thing you should do is update the values returned by:
– numberOfSectionsInTableView:
– tableView:numberOfRowsInSection:
... to reflect the addition of the new rows. This will allow the table to understand how big it is.
Then you need to update cellForRowAtIndexPath: to return the correct cell for the added row. Then you need to reload the table.
After you've done that, you should be able to scroll the tableview to the end and have the cell display properly.
The important thing to remember about tables is that they are dumb. The table itself holds no data, doesn't know how many sections and rows it has or what order the rows and sections come in. All the logic about data, sections, rows, cells and cell contents comes from the datasource and/or the delegate. When you want to change a table, you actually change the datasource and/or the delegate and then the table will reflect those changes automatically.
Edit:
Upon rereading the parent, I see that your putting the actual UITableViewCell objects in your data array and that you have one cell for each row.
This is not how tableviews are supposed to work and this will not scale beyond a few dozen rows at most.
Tableviews are intended to be an illusion that allows you display a lOGICAL table which has an arbitrary high number or rows. To that end, it only keeps enough UITableViewCell objects alive to cover the visually displayed area in the UI. With a default cell height of 44 pixels this means a tableview will never have more than 9 cell objects at a time.
Instead of eating memory holding cells that are not displayed, the tableview lets the delegate dequeue a cell that has scrolled off screen, repopulate it with the data of another LOGICAL row and then display it in a new position. This is done in cellForRowAtIndexPath:
You really need to start over here with your design. Your data needs to be kept separate from the user interface objects. You don't want to have more cells alive at anyone time than absolutely necessary because your memory use will balloon and your response time will degrade. Your current problem is the result of this unusual design.
When you've done that, you can add the result row as outlined above.
Try to scroll with some time shift after cell update via NSTimer or performSelector:withDelay:. It can help but to fix all problems I think there need to do more work.
The glitches may be caused because a UITableView considers itself the owner of any UITableViewCell instances it is displaying, and reuses them as needed. Part of that process is calling prepareForReuse on the cell. Since you are keeping the cells in an array, you do not want them reused. Try implementing an empty prepareForReuse in your UITableViewCell class. Or just create cells dynamically in tableView:cellForRowAtIndexPath: as apple recommends.
I used what Skie suggested to avoid the problem in the following way:
Immediately after adding the row:
[self performSelector:#selector(scrollToDesiredArea:) withObject:newIndexPath afterDelay:0.4f];
This called the following:
-(void)scrollToDesiredArea:(NSIndexPath *)indexPath{
UITableView *thisView = (UITableView*)self.view;
[thisView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
The delay of 0.4s seems to be sufficient to avoid the glitching; any less and it still happens. It may have to be different on varying models of iPhone hardware, though - I only tested on emulator.

Resources