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.
Related
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
I understand the concepts of cell re-usability for Xcode 5.0 table views. However, I have one very weird observation which I don't understand and wish anyone of you could enlighten me here. Thanks.
I have implemented a table view with a search bar utility (just on top of the table view). Under each custom cell (prototype cell), whenever a user clicks on it, it will be marked with a checkmark (UITableViewCellAccessoryCheckmark). The number of cells are more than 10.
Observation:
- Without using any search, marking and unmarking a cell is working as intended. Cells are updated instantly along with their checkmarks.
- When doing a search, from the results given, marking and unmarking a cell is also working as intended.
[Problem] Here comes the weird issue: when cancelling a search, an already marked cell (marked during search) does not refresh itself in the tableview unless scrolling up or down is performed!
And hence, I wrote [tableview reload] at the end of tableview:didSelectRowAtIndexPath: method. Obviously, it doesn't refresh the tableview for me. Without further changing any other code, merely modifying [tableview reload] to [self.tableview reload] under the same method works!
Why is the only addition of "self." able to make the table cells refreshed instantly? I have always thought the first argument, tableView, from the method (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath is as equal to self.tableview. Obviously, my interpretation in this case is wrong.
Thank you. I'm sorry for my lengthy post.
My guess is that this UISearchBar comes from a UISearchDisplayController. Is that correct?
If true, that is a common misconception, but an easy one to understand.
When filtering your UITableView entries and showing results, UISearchDisplayController actually overlays the view with its own tableView, UISearchResultsTableView.
Thus, this overlaid tableView also gets to call data source and delegate methods on your implementation, and this is when the tableView argument from tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath stops being equivalent to self.tableView.
This means that calling [tableView reloadData] during filtering actually asks UISearchResultsTableView to reload its contents, not self.tableView, a property of your viewController.
I'm having trouble understanding how this works. I've read many threads on SO about it - such as UITableView dequeueReusableCellWithIdentifier Theory and How does dequeueReusableCellWithIdentifier: work?.
However, my UITableView succesfully dequeues a cell each time (it's never nil), even when it first loads. I was under the impression that similar cells should use the same identifier, so you only have to change what's necessary.
Because
if (!cell) {
NSLog(#"New cell");
cell = [[UITableViewCell alloc] initWithStyle:someStyle reuseIdentifier:someIdentifier];
}
Never gets called, I'm not sure how I'm supposed to handle cells in the same table with different styles, because the style can only be set in the initializer.
I also tried using different cell identifiers, to make sure it wasn't reusing cells from a different table or something. I am registering these identifiers with [tableView registerClass: forCellReuseIdentifier:]
If I understand, this method should only return previously created cells that have been moved off the screen (hidden, i.e. can be reused). Then how come it returns a cell the first time it's called?
Edit: So the confusion was using [tableView dequeueReusableCellWithIdentifier: forIndexPath:] instead of [tableView dequeueReusableCellWithIdentifier:] (the first requires registering the identifier, the second will return nil if none is available - the behavior I was expecting above).
However, I noticed that when I changed my code to use [tableView dequeueReusableCellWithIdentifier:], it creates a new cell, and its contentView.frame has a width of 320 (full width). Before, when I did dequeue...forIndexPath it would give a width of 302, or the visual/"real" width of the cell. Why is this?
Also, is there a way to specify the style of the UITableViewCells regstiered for reuse?
Solution: So I found this thread UITableView cell.contentView.bounds.size.width Changes With Cell Reuse, which says when you set the autoresizingmask to UIViewAutoresizingFlexibleLeftMargin, it's fixed when you try to do relative positioning (the contentView width is initially the fully width, but when you present it it's shrunk, so if you do your calculations right it'll still show up properly).
I was positioning a UISwitch on the right - and when I set the autoresizing mask it works when it's first displayed but shifted over another ~20 pixels when I switched it. I don't know what caused that extra shift, but I ended up solving it by simply setting the UISwitch as the cell's accessoryView.
(This is partially off topic from the original question, but if someone stumbles on this maybe it'd be useful). For anyone wondering specifically about the original question, the answer is under the first edit.
When you call [tableView registerClass: forCellReuseIdentifier:], you're teaching the table view what to do when you later use the specified ReuseIdentifier. So, when you later call [tableView dequeueReusableCellWithIdentifier:] it will either:
A. Get a cell that has previously been created and isn't currently being used
OR
B. Create a new cell of the class you specified
So, when you dequeue, you will always get an instance. If you want to create new cell instances yourself with initWithStyle:reuseIdentifier: then you shouldn't register a class with the table view. Alternatively, leave the registration and add logic to specify everything that needs to be configured (and consider using multiple different cell classes and reuse identifiers).
because the first time the cell is nil that is why this gets called:
if (!cell) {
NSLog(#"New cell");
cell = [[UITableViewCell alloc] initWithStyle:someStyle reuseIdentifier:someIdentifier];
}
but then if the cell is already ready for reuse and basically its not nil - it returns the cell and it does not hit the above if statement
From the apple docs at https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006943
Call this method from your data source object when asked to provide a new cell for the table view. This method dequeues an existing cell if one is available or creates a new one using the class or nib file you previously registered. If no cell is available for reuse and you did not register a class or nib file, this method returns nil.
The dequeue method will
Return a recycled cell if one is available
Create a new cell if you registered one (you mentioned you did this)
If none of these are true, it returns nil
I'm guessing if you remove the registration (which may be hidden in a xib) then you will get the nil result.
if you see UITableView.h
Beginning in iOS 6, clients can register a nib or class for each cell.
If all reuse identifiers are registered, use the newer -dequeueReusableCellWithIdentifier:forIndexPath: to guarantee that a cell instance is returned.
Instances returned from the new dequeue method will also be properly sized when they are returned.
(void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
(void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
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.
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