I am puzzled at the following line "static NSString *MyIdentifier = #"MyIdentifier";" in the method: cellForRowAtIndexPath
What does that line do?
Is it just creating a random pointer to an NSString object and assigning it the string?
Why is it being called MyIdentifier, I have seen this in many examples.
#import "AddToFavorites.h"
#implementation AddToFavorites
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:MyIdentifier] autorelease];
}
// Configure the cell
return cell;
}
#end
Here is another example, this one has a different string, CellIdentifier.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TimeZoneCell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self tableviewCellWithReuseIdentifier:CellIdentifier];
}
[self configureCell:cell forIndexPath:indexPath];
return cell;
}
UITableViews can automatically reuse cells to save on memory. To take advantage of this, you must specify a "reuse identifier" which is used by the UITableView to be able to look up existing cells ("dequeueReusbaleCellWithIdentifier") with the same identifier as the one you will create if it can't find an existing cell.
The line creates a static variable (global in that it is shared by all code paths and only initialized once, but local in that you can only access it in this method) to hold the NSString for the identifier. My guess is that this is to ensure that the same pointer is used every time, as comparing pointers is quick and easy, while comparing the contents of strings can take a little bit longer.
For performance as mentioned but also to get help from the compiler in catching spelling errors. There is no checking of your identifier if you use a #""-string literal. The compiler will error out you if you misspell the static identifier. Also codesense will autocomplete the static identifier.
The identifier is a key or tag that allows you to have multiple separate collections of cells for different purposes.
This saves you time and RAM memory - let's find out how.
Let's suppose you had a contacts list application, with two types of contacts, businesses and friends.
If you wanted to display these differently, then you might design two types of cell - one with a picture (friend photo) and name in black font, and one with just the name of the company and no picture or icon.
When the user is using the application, it might need to display 3 friends and 4 companies with names starting with "A-M" at first, so it needs 3 friend cells and 4 company cells. You pass it these, and tag all the friend cells with the identifier "friend", and all the business ones with the identifier "business".
When later on the view changes and just wants names starting with "P-T", you might just have 7 businesses. Ideally you would re-use the cells you already created, so it requests 7 cells with identifier "business", and it turns out you already tagged 4 cells that you already created with "business", so it simply re-uses those. The remaining 3 you already created have the wrong tag, so it ignores those (or maybe deletes them?) and creates 3 new business type cells and gives them the tag "business".
By re-using cells in this way you save on Memory (only need as many cells can be displayed at once of each type), and Performance (no need to go to the effort of allocating and initialising new cells while scrolling up and down). You trade this off against the additional programmer effort of writing this selection code and giving things ids.
They could have automatically tagged cells based upon the objective-C type, but this wouldn't work if you programmatically created the contents of a cell rather than subclassing or using the Interface builder to lay out your cells. So they provide the identifier mechanism instead.
If you only have one type of cell in your table, just call it "Alice" and forget about it.
Related
I have 6 sections in a UItableView, every section displays 2 cells, normally, I want it like this:
However, here is what I have:
Every indexPath.row is duplicated in every section.
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"avenir";
Avenir *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) {
cell =[[Avenir alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.section];
cell.equipeb.text=[arrayofClubB objectAtIndex:indexPath.section ];
return cell;
}
The elements are retrieved from two NSMutableArrays, one for the first left element in cell and the other for the right element cell.
What is wrong?
Thank you for helping.
You need to calculate the correct index from both the row and the column. Since you have two pairs of rows per section, you need to multiply section by two, and add row, which will be either zero or one. The end result should look like this:
NSUinteger pos = indexPath.section*2 + indexPath.row;
cell.equipea.text=[arrayofClubA objectAtIndex:pos];
cell.equipeb.text=[arrayofClubB objectAtIndex:pos];
You're always fetching the identical text for each section, since
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.section];
always returns the same value for each section (since indexPath.section contains the section's index). Perhaps you wanted to do to the following instead?
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.row];
Also, for these kind of uses, it might be a lot more straight forward to use the free Sensible TableView framework as it automatically handles displaying your arrays.
I have a UITableView, and custom cells on it. On cell I have a UILabel, but before I set text to UILabel I did really hard work on text...like find the text in another text, highlight some words on it, and only then I set it to label. So when I scroll my list, it has delay because of this hard work. Any idea how to improve performance ? Maybe to do all hard work in another thread ??
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath {
static NSString *customCellIdentifier =
#"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
customCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomTableRow"
owner:self options:nil];
if (nib.count > 0) {
cell = self.customTableRow;
}
}
self.myLabel.text = [self giveMeTheTextThatINeed];
return cell;
}
[self giveMeTheTextThatINeed] - did a hard work on text that takes some time.
Make a new thread for every cell. this thread calls [self giveMeTheTextThatINeed:indexPath], and resets the label(s) in the cell. I'm assuming you can't get your data any faster, so you want to maintain the scrolling in the table and spin the hard work out to the thread. When the thread is finished, update the cell. You see this a lot in cells with a thumbnail image where the thumbnail only gets uploaded after a while, and is blank or has a placeholder there first.
Any way for you to precompute the values you'll need? In other words, start doing your "hard work" (in another thread) when the app starts, and store it somewhere so that, if it's ready, you can just grab it when the table view asks for it. It's hard to answer without more detail about what the hard work is and how much data we're talking about.
I don't know about just doing the hard work on another thread as you suggested, since you still have to give something to tableView:cellForRowAtIndexPath:. I suppose you could return some kind of template cell at first, and then update it with reloadRowsAtIndexPaths:withRowAnimation: when the hard work is done.
I believe the method giveMeTheTextThatINeed needs to take the current cell as one of the parameters (or another parameter dependent on the cell content), e.g.: [self giveMeTheTextThatINeed:indexPath]. Otherwise you could store the text as an instance variable and set it in all cells from that variable.
So, with that in mind, the easiest way is to store the result of the computation in an additional dictionary, where indexPath (or the other parameter) would be the key:
self.myLabel.text = [self->myDictionary objectForKey:indexPath];
Now, you could either pre-populate that dictionary before the cells are drawn (e.g. in viewWillAppear), or cache them once they are calculated so that they are not recalculated when the cells are scrolled, e.g.:
NSString* calculatedText = [self->myDictionary objectForKey:indexPath];
if(calculatedText == nil)
{
calculatedText = [self giveMeTheTextThatINeed:indexPath];
[self->myDicationary setValue:calculatedText forKey:indexPath];
}
self.myLabel.text = calculatedText;
I'm using a custom UITableViewCell. They load up great, but when they get reused instead of replacing the text in the labels, they are some how writing over the top of them. Any ideas how to stop this behaviour?
I assume I'm not reseting the view correctly before it gets reused. I'm currently empty the labels so they have just a blank #"" string. But I still get the old text plus the new (very messy).
I'm certain there's an obvious solution to this (presently i just don't reuse the cell, but this isn't best practice and is slow on old devices), so if someone can help I'd be very grateful.
Thanks
ED
As requested here is the method for amending the cell
static NSString *CellIdentifier = #"Cell";
TransactionCellView *cell = (TransactionCellView *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"TransactionCellView" owner:self options:nil];
cell = tblCell;
}
[cell resetCell]; // clears the labels
[cell setData:[dataArray objectAtIndex:[indexPath row]]; // replaces the data and updates the labels
return cell;
}
Solved this issue!!! Amazingly simple when you know what the problem is/was.
I was using IB and "Clear Graphics Context" was not selected on my UILabels. So that is why the next text was just overlaying the old.
THanks guys for trying to help.
I suspect that you are adding the label as a subview programatically in your cellForRowAtIndexPath method. You need to make sure that you are not adding that subview every time the cell is recycled. Instead create the label only when creating a new cell (not when recycling a cell) then assign a tag value to the label and then in the future, when it gets recycled, retrieve the label by tag value and change its text.
You are correct in that the cells are being reused. Your code should be set up roughly using the following pattern:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString* identifier = #"someidentifier";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
// create the cell and add all subviews to it
} else {
// update the cell and access appropriate subviews to modify what is displayed
}
return cell;
}
The cell will be created the first time the identifier is used. For all subsequent requests, the cell is pulled from the UITableView cache (via dequeueReusableCellWithIdentifier), and you can then access its subviews either by tag, index, type, or whatever mechanism you choose.
Somewhat related is that you can have multiple cell identifiers, which allows you to create multiple instances of different cells depending on the data that you have. In one of my projects, I have 4 different cells, each dependent upon the number of lines of data that they will display (anywhere between 1 and 4). This helps ensure a smooth scrolling experience regardless of how many lines the cell has since the renderer doesn't have to worry about dynamically changing the height of the cell on the fly.
Try this..
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
for (UIView* subView in cell.contentView.subviews)
{
[subView removeFromSuperview];
}
// Your Code to customize cell goes here.
}
I am both new here and in IOS programming, so please bear with my newbee questions for a while :]
Here is the situation of my application before my question:
I have a view in which i added 3 different subviews. (1-categories, 2-words and 3-rules)
Within the categories subview I have a table of categories. (reads and writes from a categories plist and array)
According to the selected category I am changing the table ingredients for words table in the words subview. This table keeps the words the user enters for a selected category. (reads and writes from an nsmutabledictionary whose keys are the category names from the categories table and whose values are arrays of strings which were pre-entered by me to the plist)
Now an example to make things clear:
my categories array has: Size, Color
my words array for Size category has 3 strings in it like so: "tiny" , "big", "huge"
my words array for Color category has 2 strings in it like so: "red", "blue"
First when i select "Size" category, i go and fetch words within that category and when i move from categories subview to words subview i see everything inside the "Size" category correctly in my uitableview. (it prints "tiny", "big" and "huge")
But when i go back to my categories subview and select "Color" this time and go back to words subview i get a crash.
Seems like it calls tableView:cellForRowAtIndexPath: method, and i get an NSRangeException as below:
[NSMutableArray objectAtIndex:]: index 2 beyond bounds
Long story short, I want to know why the cellForRowAtIndexPath method is calling an index which is not there. Here is how inside my cellForRowAtIndexPath method looks like:
[self reinitializeWordsDictionary];
[self reinitializeWordsInSelectedCategoryArray];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [wordsInSelectedCategoryArray objectAtIndex:indexPath.row];
return cell;
Thanks in advance for the answers,
tiw
If anyone reading this has NSRangeExceptions not thrown by cellForRowAtIndexPath, check to ensure your UITableView isn't wrongly set to "static cells" content.
Sounds to me that the - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
method of the UITableView's data source is delivering wrong values. I would check that by setting a break point on this method and evaluate the returned values.
What are you giving back in
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
?
I'm extremely confused by the proper behavior of UITableView cell rendering. Here's the situation:
I have a list of 250 items that are loading into a table view, each with an image. To optimize the image download, I followed along with Apple's LazyTableImages sample code... pretty much following it exactly. Really good system... for reference, here's the cell renderer within the Apple sample code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// customize the appearance of table view cells
//
static NSString *CellIdentifier = #"LazyTableCell";
static NSString *PlaceholderCellIdentifier = #"PlaceholderCell";
// add a placeholder cell while waiting on table data
int nodeCount = [self.entries count];
if (nodeCount == 0 && indexPath.row == 0)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:PlaceholderCellIdentifier] autorelease];
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.detailTextLabel.text = #"Loading…";
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Leave cells empty if there's no data yet
if (nodeCount > 0)
{
// Set up the cell...
AppRecord *appRecord = [self.entries objectAtIndex:indexPath.row];
cell.textLabel.text = appRecord.appName;
cell.detailTextLabel.text = appRecord.artist;
// Only load cached images; defer new downloads until scrolling ends
if (!appRecord.appIcon)
{
if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
{
[self startIconDownload:appRecord forIndexPath:indexPath];
}
// if a download is deferred or in progress, return a placeholder image
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
else
{
cell.imageView.image = appRecord.appIcon;
}
}
return cell;
}
So – my implementation of Apple's LazyTableImages system has one crucial flaw: it starts all downloads for all images immediately. Now, if I remove this line:
//[self startIconDownload:appRecord forIndexPath:indexPath];
Then the system behaves exactly like you would expect: new images load as their placeholders scroll into view. However, the initial view cells do not automatically load their images without that prompt in the cell renderer. So, I have a problem: with the prompt in the cell renderer, all images load at once. Without the prompt, the initial view doesn't load. Now, this works fine in Apple sample code, which got me wondering what was going on with mine. It's almost like it was building all cells up front rather than just the 8 or so that would appear within the display. So, I got looking into it, and this is indeed the case... my table is building 250 unique cells! I didn't think the UITableView worked like this, I guess I thought it only built as many items as were needed to populate the table. Is this the case, or is it correct that it would build all 250 cells up front?
Also – related question: I've tried to compare my implementation against the Apple LazyTableImages sample, but have discovered that NSLog appears to be disabled within the Apple sample code (which makes direct behavior comparisons extremely difficult). Is that just a simple publish setting somewhere, or has Apple somehow locked down their samples so that you can't log output at runtime?
Thanks!
You certainly should not have an actual UITableViewCell instance for every row in the table. You should only see a few more instances than are visible in the UI. That is where your problem is. It doesn't have anything to do with the loading of images.
The only time I've seen a large number of cells instantiated when the cells where dequeued was when a coder had altered the frame of the tableview to make it much larger than the screen. The tableview retains enough cells from being dequeued to carpet its own frame regardless of what is visible. If the frame is to big then you get a lot of cells in queue.
NSLog does work in Apple examples so if you can't get NSLog output you've got something weird going on with the dev tools themselves.
You might want to shutdown Xcode and the simulator and restart and see if that clears up the odd behavior.
Oh my... mustISignUp is absolutely correct in saying "you definitely have a deeper underlying problem". I have variable-height table rows, and I was doing all height calculation and data storage on the rows themselves rather than on the data model that populated them. As a result, ALL cells were being created and populated by my heightForRowAtIndexPath method which was reading cell height from the cell objects. SO – lesson learned.
Thanks mustISignUp, and I love your username.
NSLog definitely isn't disabled within the Apple sample code. I don't know why you can't see it but you definitely have a deeper underlying problem.
Anyway, for your comparison:- if you have 6 rows on screen -tableView: cellForRowAtIndexPath: is called 6 times with index [0, 0] - [0, 5].
So, is that what you are seeing? How many times is -cellForRowAtIndexPath being called?