I'm looking to have a single - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath method that all UITableView's will use for formatting, etc.
What is the best way to accomplish this?
Right now I'm thinking I could use a separate class or put delegate methods in my app delegate and hook the UITableView's file's owner to that. But then I'd have to provide those classes/methods access to the data that would normally otherwise be right in the ViewController subclass.
How have others accomplished this sort of thing?
EDIT: To clarify, I'm not looking for a way to return a common cell itself, but rather a way to have a single cellForRowAtIndexPath method in my entire app.
If you just want to centralize all of the formatting but have the data sources separate in each class you could create a factory class for your table view cells which provides a method
+ (UITableViewCell* ) tableViewCellForTableView:(UITableView *)tableView withType:(int)type;
By creating type costants which get passed in, you can create a set of basic table view cells that can then be returned by this class. It is important to pass in the tableView to make use of reusable cells. A cellForRowAtIndexPath could now fetch cells from there and configure them depending on the data
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [MyCellFactory tableViewCellForTableView:tableView withType:kMyTypePlain];
cell.textLabel.text = ....;
}
You may consider using an objective-c category on your ViewController which implements the cellForRowAtIndexPath method.
If you're doing something very complex in this method however, I suggest you just create a nib file which contains your cell and then load it in cellFoRowAtIndexPath. You could even create a class that inherits from UITableViewCell and stick your own methods/properties in there. I highly recommend this approach.
You can load the nib, say "MyCustomTableCell.nib" which contains your customised UITableViewCell like so:
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCustomTableCell"
owner:self
options:nil];
MyCustomTableCell* cell = [topLevelObjects objectAtIndex:0];
I think you can use the answer from #TriPhoenix for most of this.
The only additional thing I would add is that you could potentially send the data along with the call to the factory.
[MyCellFactory cellForTableView:tableView withType:kMyTypePlain data:data];
You would of course have to ensure that the data responds to a common interface to ensure that you can set things easily e.g. making sure each object responds to a similar method like
cell.textLabel.text = data.textLabelText;
You could do this using a protocol if you like. It's up to you how you structure your data.
Related
Time profiler shows the most time consuming operation in my app is loading UITableViewCells from nib files. The most expensive of which involves loading a UITableViewCell with a 4KB image.
I am loading the UITableViewCell from the nib with the following code:
[[NSBundle mainBundle] loadNibNamed:#"UITableViewCellPortrait" owner:self options:NULL];
cell = portraitCell;
self.portraitCell = nil;
Has anyone compared the difference between creating a view programmatically or loading a UITableViewCell from a nib?
EDIT:
I compared the time profile of repeated runs of loading the UITableViewCell from a nib and creating the view programmatically. My test involved alternating between two UITableViews about 10 times in the span of 3-5 seconds. In each test, loading the UITableViewCell programmatically was substantially faster, between 2x to 6x faster.
Can anyone corroborate these results?
EDIT:
I updated the nib loading code to only load the nib file once and use a cached version for subsequent calls.
if (self.UITableViewPortaitNib == nil) {
self.UITableViewPortaitNib = [UINib nibWithNibName:#"UITableViewCellPortrait" bundle:[NSBundle mainBundle]];
}
self.UITableViewPortaitNib instantiateWithOwner:self options:NULL];
cell = portraitCell;
self.portraitCell = nil;
I also used the automation instrument to create more consistent runs and the results still suggest loading UITableViewCells programmatically is faster than loading UITableViewCells for a nib. The average running time for loading UITableViewCells from a nib was around 90ms, while the average running time for creating the UITableViewCell programmatically was 50ms.
Try creating a UINib object once and then sending it instantiateWithOwner:options: each time you need to create a new cell. From the UINib Class Reference:
For example, if your table view uses a nib file to instantiate table view cells, caching the nib in a UINib object can provide a significant performance improvement.
In iOS 5 and mentioned in the WWDC 2011 videos, there is a newer method that uses UINib. You register your nib in your viewDidLoad: method and then simplify the code in the tableView:cellForRowAtIndexPath: method. This may speed things up for you (but I've never performed any comparison timings).
Example:
In your viewDidLoad: register the nib and retain a reference to it:
NSString *myIdentifier = #"ReusableCustomCell";
[self.reuseCustomCell registerNib:[UINib nibWithNibName:#"ReusableCustomCell" bundle:nil] forCellReuseIdentifier:myIdentifier];
In your tableView:cellForRowAtIndexPath: method just ask for the cell (no need to check for nil as it is guaranteed to return a cell under iOS5) and configure the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *myIdentifier = #"ReusableCustomCell";
ReusableCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:myIdentifier];
// Your configuration code goes here
cell.nameLabel.text = #"some text";
// ....
return cell;
}
Code not tested. I'd be interested if this was any faster than using UINib alone.
I load the nib cell (cellTemplate) once and duplicate it as needed, so in a sense this approach is both programmatic and nib based.
Duplicating was more complicated than I expected as mutableCopy didn't work. An NSKeyedArchiver roundtrip did, however:
NSData* cellData = [NSKeyedArchiver archivedDataWithRootObject:cellTemplate];
cell = [NSKeyedUnarchiver unarchiveObjectWithData:cellData];
In fact if you're going for raw, blazing, pedal-to-the-metal speed, even the archived template can be calculated once and cached.
But shouldn't you be measuring frame rate? In that case the UIView's complexity comes into play too.
It's possible to reuse the uitableviewcell nibs which are once loaded and then they go out of the view. Read the following:
iPhone - What are reuseIdentifiers (UITableViewCell)?
I'm trying to use the new StoryBoard feature of Xcode 4.2 and keep getting this error when using a Table View with a custom cell.
cell reuse indentifier in nib (Cell) does not match the identifier used to register the nib (ThisCell)
I've set the class of my custom cell to my custom UITableViewCell class and set the Identifier to "ThisCell" in IB.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ThisCell";
//TableViewCell *cell = (TableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"ThisCell"];
TableViewCell *cell = (TableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
return cell;
I have no idea where "Cell" is coming from. Any ideas? I tried loading a custom cell in another project, and it seems to work fine, I just can't find any documentation on this error to find out what I messed up in the current project.
Thanks
The error message states that in your nib the cell is identified as "Cell"—you need to look in the xib file that defines your custom cells for configuration of the Cell Reuse Identifier, and set that to ThisCell... Or just change ThisCell to Cell in this code.
Take a look at following 2 places in your project. And make sure they both string identifiers are same.
The problem is as #Duncan Babbage described. It happens when there is some type of conflicts in the reuse identifier for the same instance. (In my case, I defined two different reuse identifiers in both Storyboard and code and they were conflicting each other )
you can also look for "Cell" outside the .xib file, specifically in all project files.
I was under the impression that table view cells never got dealloced until the app crash because you are able to resuse them. But when I was profiling my table view, I realized that something was calling dealloc on my custom cell. What exactly dealloc's custom cells and can I stop it?
Using the common "reuse" pattern:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = #"Foo";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId]
autorelease];
}
// update the cell content
return cell;
}
The table view creates as many cells as required to fill the its frame height.
When disappearing from screen they are removed from the view and put in the reuse queue, thus not deallocated.
All the cells are dealloc'd when the tableView is dealloc'd and some cells may be dealloc'd when you number of rows changes (say you had 20 cells before and only 2 after update).
You might be tempted to get rid of cells reuse but you would lose all the magic done behind to keep a low memory footprint and to have a smooth scrolling experience.
The semantics of the dequeueReusableCellWithIdentifier: selector are opaque (as far as I know). If you want complete control of your table view cells, don't use that selector to get a cell: just construct a new one or use your own pool for reuse.
Table views create and shed cells as needed. This is to allow for very long lists to be scrolled through without storing them all in memory. A virtually infinite scrollable list is possible. A convenient way to retain your custom cells for the table view's lifespan is to add them to a mutable array upon creation. Before creating, look in the array to see if it already exists.
I have the same question problem as described here How to purge a cached UITableViewCell
But my problem can't be solved with "resetting content".
To be precise - I use a custom cell (own class). While running the application it is possible that I have to use a different "cell type".
It's the same class - but it has (a lot of) differnt attributes.
Of course I could always reset all the things at "PrepareForReuse" but that's not a good idea I guess (there are a lot things to reset).
My idea - I do all these things in the constructor of the cell.
And all the rows will use this "type of cell" later.
When the (seldom) situation comes that I have to change the look of all rows I create a new instance of this kind of cell with different settings.
And now I want to replace the queued cell with this new one.
I tried it with simply calling the constructor with the same cellidentifier (in the hope it will replace the existing one) but that doesn't work.
I also didn't find a "ClearReusableCells" or something like this.
Is there a way to clear the cache - or to remove / replace a specific item?
Manfred
Create each cell type (even if they use the same cell class) using a different identifier. So if you have 2 cell types, define 2 identifiers and keep them separate.
I'm not sure where your problem is. You have a bunch of cells, with appearance A, then the user takes some action and they need to become appearance B. If you call reloadData or one of the more granular methods, your datasource will be called again for cellForRowAtIndexPath. Just implement this method to segregate the two cell types.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString* identifier = which mode are we in
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier]; // Will return nil if we haven't got this cell
if( !cell ) {
// Create different cell type based on the identifier
}
return cell;
}
Update
This doesn't work. The memory for the cell that is returned from dequeueReusableCellWithIdentifier is never released.
If you initialize a cell with a non-nil reuseIdentifier, then that cell will NOT be freed until the tableView itself is released. This is true even if [[tableView valueForKey:#"reusableTableCells"] removeAllObjects] is called. Unfortunately, the tableView is retaining the cell in some other private member and the only way to free it is by destroying the tableView.
Short Answer
The answer to the title of the question of how to clear the tableView cell cache is that Apple does not provide the "ClearReusableCells" functionality, but it is reasonably easy to implement yourself. Simply track the time that the tableView's cache was last cleared and track the time that the cell was created. A cell is considered to be dirty if it was created before the tableView's last cache refresh time.
Alternatives
Steven Canfield's answer - This is a good answer to the specific question, but it does not scale well if there are multiple attributes that can be changed independently of each other. In that case, you can very quickly have lots of 'modes' and that can get unmanageable. Also, with respect to memory usage, it is true that Apple code is responsible for managing the memory, but it is also true that library cannot know when a particular identifier might be needed again, so it will likely end up keeping these cached cells around longer than it needs to.
Recreate the tableView - This is a brute force mechanism that is guaranteed to clear the cell cache. Unfortunately, it is usually inconvenient to recreate the tableView.
Jake's Dahl's answer from the alternate question - This is a good mechanism for clearing the cache that probably worked at the time it was written, but it relies on some implementations that Apple does not guarantee, and that have, in fact, already changed.
Implementation
There are multiple ways of tracking the tableView's cache refresh time and the cell creation time. I've shown a mechanism below that uses subclasses. Of course, for this to work, you need to make sure that the code that instantiates the table and cells instantiates the subclasses.
#interface MyTableView : UITableView
#property(nonatomic,assign) NSTimeInterval cellCacheRefreshTime ;
#end
#interface MyTableViewCell : UITableViewCell
#property(nonatomic,assign) NSTimeInterval creationTime ;
#end
#implemetation MyTableView
-(void) refreshCellCache {
self.cellCacheRefreshTime = [NSDate timeIntervalSinceReferenceDate];
}
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier {
MyTableViewCell *cell = (id)[super dequeueReusableCellWithIdentifier:identifier] ;
if( cell.creationTime < aTableView.cellCacheRefreshTime ) {
return nil ;
}
return cell ;
}
#end
#implemetation MyTableViewCell
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier] ;
self.creationTime = [NSDate timeIntervalSinceReferenceDate];
return self ;
}
#end
Hey, I'm looking for useful resources about Delegates. I understand that the delegate sits in the background and receives messages when certain things happen - e.g. a table cell is selected, or data from a connection over the web is retrieved.
What I'd like to know in particular is how to use delegates with multiple objects. As far as I know, specifying the same delegate for an object (e.g. table cell) would cause the same events to be called for both the cells at the same time. Is there anything equivalent to instantiating a delegate for a particular object?
Thanks in advance!
In Cocoa, objects almost always identify themselves when calling a delegate method. For example, UITableView passes itself as the first parameter of the delegate message when calling it:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
If you wanted the same delegate to handle multiple UITableViews, then you just need a some conditional on the tableView object passed to the method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.myFirstTableView) {
// do stuff
} else if (tableView == self.mySecondtableView) {
// do other stuff
}
}
}
If you don't want to compare the object pointers directly, you can always use the tag property to uniquely identify your views.
Usually, if you have a delegate method that might have to receive messages from many different objects, you simply have the calling object pass itself to the delegate in the message (method call).
For example, if you wanted a delegate method to extract the text from a tableviewcell's label, the method definition would look something like:
-(void) extractTextFromLabelOfTableCell:(UITableViewCell *) theCallingCell{
...
NSString *extractedText=theCallingCell.textLabel.text;
}
You would call the method from a tableviewcell thusly:
[delegate extractTextFromLabelOfTableCell:self];
Each instance of the tableviewcell would send itself to the delegate and the delegate would extract that instance's text. In this way, a single delegate object could handle an arbitrarily large number of cells.
A delegate is a way of adding behaviors to a class without subclassing or for attaching a controller to a class.
In the table view example you gave, the delegate is extending or controlling the table, not the cell. The table is designed to have a controller, the cell is not. This design choice is why you can't specify cell-specific delegates.
However, delegate methods will always announce the source object (the one to which the delegate is attached) and relevant parameters (like the cell involved) so you should always be able to handle the action fully.
In your case, if you have a cell and you would like the cell to manage itself, then the delegate method (which will probably be implemented on your UITableViewController) can simply fetch the cell from the source table using its NSIndexPath (passed as a parameter to the delegate method) and invoke a method on the cell subclass to do its work.
I always liked Chris Sells' ".NET Delegates: A Bedtime Story"