As a user scrolls up and down in an uitableview cells get destroyed and created.
Is there a way to detect when a cell is going to be or has been destroyed?
Assuming that by "getting destroyed" you actually are referring to a cell getting reused, simply implement prepareForReuse within your UITableViewCell derived class.
prepareForReuse
Prepares a reusable cell for reuse by the table view's delegate.
- (void)prepareForReuse
Discussion
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:. For
performance reasons, you should only reset attributes of the cell that
are not related to content, for example, alpha, editing, and selection
state. The table view's delegate in tableView:cellForRowAtIndexPath:
should always reset all content when reusing a cell. If the cell
object does not have an associated reuse identifier, this method is
not called. If you override this method, you must be sure to invoke
the superclass implementation.
Availability
Available in iOS 2.0 and later.
See Also
– initWithFrame:reuseIdentifier:
#property reuseIdentifier
Declared In
UITableViewCell.h
Without going into the implications of suitability or performance, another option might be to periodically check what cells remain visible, using the visibleCells method of the UITableView class:
- (NSArray *)visibleCells
As per the documentation:
Returns an array containing UITableViewCell objects, each representing a visible cell in the receiving table view.
You can subclass UITableViewCell and override it's dealloc method.
Any good reason to do it assuming you are reusing the cells to save the resources ?
What you're attempting to intercept is part of the internal implementation of UITableView and how it manages its cells. While there are ways in which you can attempt to intercept such behavior, I would suggest that you avoid using them, as there is no guarantee that future implementations of UITableView will maintain this behavior.
It would be better in cases such as this to consider a different approach: be it design and implement your own table class, or change your code logic.
As stated above, cells aren't destroyed when the leave the screen. However there are some things you can do, to track related actions, depending on what you are trying to do.
First there is a delegate message:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
This is called before a cell enters the screen. Another possibility is the already stated prepareForReuse method of a cell.
Another approach would be: Try and override willMoveToSuperview: or any other of the related methods. I am not sure if this is fired after the cell becomes invisible, but it might work.
Best regards,
Michael
Related
I have a custom UITableViewCell that dequeueReusableCells. I have an int called selectedRow which gets the selected rows number in the method of didSelectRowAtIndexPath. I then pass selectedRow to an int called rowNumber which is in the class of my customCell.
In customCell.m, I have the method prepareForReuse. In that I made an NSLog of rowNumber.
What I want to do is: if a row is selected and that row went off screen, then perform some code. I would probably have to use prepareForReuse, but I don't know what to do in it.
I know it's a bit complicated, but if you have any questions, then I'd be happy to answer
Actually, you don't need to call prepareForReuse directly as it would be called automatically:
this method is invoked just before the object is returned from the
UITableView method dequeueReusableCellWithIdentifier:.
and as you don't know what to do in it, note:
For performance reasons, you should only reset attributes of the cell
that are not related to content, for example, alpha, editing, and
selection state
UITableViewCell Class Reference
You can use - (void)tableView:tableView didEndDisplayingCell:cell forRowAtIndexPath:indexPath; in UITableViewDelegate to know which cell is scrolled off screen.
However, this method is iOS6+ only.
You're over complicating things. You don't have to do prepareForReuse the in the custom cell.
Take a look at this.
http://www.icodeblog.com/2009/05/24/custom-uitableviewcell-using-interface-builder/
Its pretty similar for storyboards.
Scenario: I want to check selected state of specific visible UICollectionViewCells with indexPath for which I am calling cellForItemAtIndexPath on the UICollectionView reference.
Problem: Calling UICollectionView.cellForItemAtIndexPath always calls UICollectionViewDatasource.cellForItemAtIndexPath which returns a new cell without the selection state.
Question: Why UICollectionView.cellForItemAtIndexPath always calls UICollectionViewDatasource.cellForItemAtIndexPath ?
Apple documentation says the return value is "The cell object at the corresponding index path or nil if the cell is not visible or indexPath is out of range."
Am I missing something or is my implementation of cellForItemAtIndexPath in datasource incorrect ?
- (UICollectionViewCell*) collectionView: collView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
SudoCell *cell = [collView dequeueReusableCellWithReuseIdentifier:CELL_ID forIndexPath:indexPath];
[cell setValuesWithSection:indexPath.section item:indexPath.item modelObject:_model];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
As a work around currently I am setting storing the section and item values as instance values of the cell. Looping through all visible cells to find matching cells with the section and item values and checking visible state. This becomes tedious when number of cells are huge.
Please advice.
You're misunderstanding how protocols and delegates work. cellForItemAtIndexPath: is a delegate method that UICollectionView and UITableView call on its datasource in order to populate the Collection or Table View.
So lets say you have a CollectionView and you gave it a datasource. At some point when you run your application that CollectionView is going to call a method on the datasource numberOfSectionsInCollectionView: in order to get how many sections are needed for the CollectionView
Then it calls collectionView:numberOfItemsInSection in order to get the items for each section for the collection. This method is called for each individual section defined in the Collection View.
Finally it calls collectionView:cellForItemAtIndexPath: in order to get the Cells for each item of the collection. This method is called for each individual item defined in the Collection View. This would be where you could programmatically configure the cell to display the information you want, for instance, if you wanted to give the collection a Cell that had an image attached to it, this is where you would do it.
https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewDataSource_protocol/Reference/Reference.html
These are all datasource methods with the sole responsibility of providing data for the Collection View. If you want to respond to user interactions you need to use UICollectionViewDelegate Protocol and implement the methods
collectionView:didSelectItemAtIndexPath:
collectionView:didDeselectItemAtIndexPath:
As the signature implies, these methods are called in response to actions performed on the collection, along with an IndexPath describing the section and item number of the cell the action as performed on.
https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewDelegate_protocol/Reference/Reference.html
UICollectionDelegate Protocol reference above you can use to respond to different events that occur in that view
But none of that information above is any use to you if you don't have a basic understanding of protocols and delegates. I would recommend spending time reinforcing that understanding first before continuing
The design of the app I am working on, specifically the tableview part is quite complicated.
There are around 5-6 methods in which I need to get a reference to particular cell from the table view.
What I do not like is that I have to use the
-tableview:cellForRowAtIndexPath
This is a datasource method and does a lot of heavy lifting. Custom cell configuration, dequeueing, getting data for particular cell..etc.. The point is all this code is executed again,even if it does not make any sense at all. The cell object is completely loaded already.
Am I right in my observation and if yes, is there a working tested and lightweight solution?
Perhaps there could be an index of references to all cells. Asking it, I would immediately get the reference without the datasource code.
UITableView has cellForRowAtIndexPath: method.
From UITableView reference:
Returns the table cell at the specified index path.
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
Parameters
indexPath The index path locating the row in the receiver.
Return Value An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
Apple's documentation says of the indexPath parameter:
The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the table view.
But register(Class|Nib):forCellReuseIdentifier: only specifies the reuse identifier to use, not the section or a set of index paths.
I thought perhaps UITableViewCell had some way of getting its hands on the index path, so it could, say, round its corners if in the first row of a section, but I'm not seeing it. At creation time, all it gets is its style and reuse identifier (initWithStyle:reuseIdentifier:); at reuse time, all it gets told is prepareForReuse.
Seeing as the old dequeueReusableCellWithIdentifier: is still supported, what sort of index-path-based configuration could it possibly be doing, if it can't rely on having the chance to do it, anyway?
I checked the Table View Programming Guide, but it hasn't been updated since iOS 5.
The most important difference between dequeueReusableCellWithIdentifier: and dequeueReusableCellWithIdentifier:indexPath: is that they are different methods! Thus they can behave differently, and they do. This has nothing to do with the indexPath, really; we just need a way to distinguish them.
The New Way
In particular, if you call dequeueReusableCellWithIdentifier:indexPath:, this is a sign that you are using the new iOS 6 register-and-dequeue system. So, if you have failed to register this identifier, you'll get a nice crash and a log message explaining the problem. This method will never return nil; it always returns a cell, either by creating a new one or by reusing one.
The Old Way
On the other hand, plain and simple dequeueReusableCellWithIdentifier: is old and has to be backward compatible. If you haven't registered this identifier, it won't complain: it will just return nil, leaving you high and dry. You'll have to create the cell yourself, as in the bad old days.
EDIT: But see also the answer by #svena! The new way (with indexPath:) has a second advantage I didn't know about: the cell is correctly sized at the time it is returned to you.
According to WWDC 2012 Session 200 - What's New In Cocoa Touch,
If you use - dequeueReusableCellWithIdentifier:forIndexPath: to dequeue your cell, it will be the right size and you'll be able to do layout inside your cell's contentView.
That's pretty much a quote from Chris Parker, a UIKit Engineer.
Up until iOS 6, you had to subclass your UITableViewCell and override - layoutSubviews if you wanted to make layout adjustments. From encapsulation point of view, this might still be the better solution – however, sometimes you just need a tiny adjustment, and now you can do that in - tableView:cellForRowAtIndexPath: instead.
I believe it is used to call the tableView:heightForRowAtIndexPath: method, if one exists, allowing the cell to be correctly sized.
I always thought that UIKit would round the corners of the top and bottom cells in a grouped table view when the UITableViewDelegate method:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
is called. However, knowing the index path isn't much use unless the cell also knows how many rows are in the same section so it can work out if it's the last cell or not.
I assume that the addition of the index path to the method dequeueReusableCellWithIdentifier: is to improve performance perhaps as #jrturton suggested with different reuse pools or simply to determine the position of a cell in grouped sections.
As far as I can remember from the WWDC videos, there were a few additional methods added in iOS 6 to support reordering, insertion and deletion of cells so perhaps this also comes into factor here?
I'm using CustomTableCell in my project. I can see the "dequeueReusableCellWithIdentifier" is returning a valid cell in the method "cellForRowAtIndexPath" as it should eg whenever the table is reloaded.
In the CustomTableCell I have some images that I want to reuse, without downloading them again. However everytime i get a "nil" with the "dequeueReusableCellWithIdentifier" when used inside "didSelectRowAtIndexPath" delegate method.
The original table was not destroyed. The table is class object and I can see it is the same as the tableView received in the "didSelectRowAtIndexPath" parameter.
This behavior is consistent with out viewcontrollers in my project where I have used tableview.
I'm using ARC. Any idea what could be wrong ?
Thank you!
The question is a little vague, but I believe you're simply trying to access your custom cell from within "didSelectRowAtIndexPath". If this is the case, then you should utilize:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomTableCell *myCell = (CustomTableCell*)[tableView cellForRowAtIndexPath:indexPath];
}
Then you're free to access any part of your custom cell that you wish.
In case your curious:
"dequeueReusableCellWithIdentifier" is utilized to reduce the amount of object allocations & deallocations by reusing table cells that have "moved" beyond the visible range. i.e If you have a table with 1000 cells, but only 10 are visible at any given time. ~10 cells will be created and reused over and over again. Thus, when called dequeueReusableCellWithIdentifier will pull one of these cells off the stack of cells that aren't currently being utilized, or create one if the stack is empty.