dequeueReusableCell causing weird behavior - ios

i'am creating an iOS application which is similar to bbc application
- I have a table view which has two section
- 1st section contains cells containing scrollview wid images
- 2nd section contains expandable cells which contains scrollview did images
so the problem is that
when i use the dequereusable its showing weird behaviors like when the bottommost cell in the table is expanded the first cell in the first gets cleared etc etc
so i have just stopped using the queue and everything started working fine
but now when i added images after scrolling the cells which is not in the view gets refreshed and its
taking a lot of time to load
so could kindly guide me how to use the queue wisely in the code
described below
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier=#"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell== nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"hai"] autorelease];
///[here a different name has been used for the reuse identifier];////
if ([self tableView:tableView inSection2:indexPath.section]) {
Coffee *co =[appDelegate.coffeeArray2 objectAtIndex:indexPath.section-s1Count-1];
cell.textLabel.text=co.coffeeName;
}
if ([self tableView:tableView inSection1:indexPath.section]) {
Coffee *co =[appDelegate.coffeeArray1 objectAtIndex:indexPath.section];
cell.textLabel.text = co.coffeeName;
CGRect cellname = CGRectMake(5, 0, 290, 25);
UILabel *cellabel = [[[UILabel alloc] initWithFrame:cellname] autorelease];
cellabel.backgroundColor = [UIColor whiteColor];
cellabel.font = [UIFont italicSystemFontOfSize:20];
cellabel.textColor=[UIColor blueColor];
cellabel.highlightedTextColor = [UIColor clearColor];
cellabel.text=co.coffeeName;
[cell.contentView addSubview:cellabel];
}
// Configure the cell...
if ([self tableView:tableView canCollapseSection:indexPath.section])
{
if (!indexPath.row)
{
// first row
// only top row showing
if ([expandedSections containsIndex:indexPath.section])
{
cell.accessoryView = [myuicontroller accessoryWithColor:[UIColor grayColor] type:DTCustomColoredAccessoryTypeUp];
}
else
{
cell.accessoryView = [myuicontroller accessoryWithColor:[UIColor grayColor] type:DTCustomColoredAccessoryTypeDown];
}
}
else
{
// all other rows
cell.accessoryView = nil;
cell.accessoryType =UITableViewCellAccessoryDisclosureIndicator;
CGRect cellname = CGRectMake(5, 0, 290, 25);
UILabel *cellabel = [[[UILabel alloc] initWithFrame:cellname] autorelease];
cellabel.backgroundColor = [UIColor whiteColor];
cellabel.font = [UIFont italicSystemFontOfSize:13];
cellabel.textColor=[UIColor blueColor];
cellabel.highlightedTextColor = [UIColor clearColor];
// cellabel.text =[NSString stringWithFormat:#"category"];
[cell.contentView addSubview:cellabel];
myscrollView *svb;
svb=[[myscrollView alloc]initwitharray:appDelegate.newscat1];
}else{
myscrollView *s;
NSLog(#"inside the textlabel ext%#",cell.textLabel.text);
NSLog(#"count of array %d",[appDelegate.newscat1 count]);
NSString *cat=cell.textLabel.text;
[cell.contentView addSubview:s];
}
}
return cell;
}

You need to set the alternative reuse identifier before you dequeue the cell. At the moment you are dequeuing a cell with identifier "cell" regardless of the section you are in, so you will often be returning a section 0 cell for a section 1 part of the table.
So, branch your code so that you do different things depending on the value of indexPath.section:
if (indexPath.section == 0)
cellIdentifier = #"thisCell";
else
cellIdentifier = #"otherCell";
Then dequeue your cell, if it is nil, create with the same cell identifier variable above.
You should only be adding subviews inside your (cell = nil) code - otherwise you will end up with cells with lots of overlapping subviews and will be wasting memory. If a cell has been dequeued, you just configure the existing subviews, you don't make new ones. You can assign tags to your subviews as you add them to assist with this.

Related

Same Cells repeat after Scrolling

I am using table view using custom coding with tag method to save memory.
I was successful to show data in the view but the problem is if 10 cells are showing and then if I scroll down like for one cell then it should show 2-11 cell data but it switches to 1-10 again.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
UILabel *cellNAMElabl = nil;
UILabel *cellDetaillabl = nil;
UIImageView *imgView = nil;
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
cellNAMElabl = [[UILabel alloc] initWithFrame:CGRectMake(88, 10, 150, 20)];
[cellNAMElabl setTag:1];
cellNAMElabl.text = [name5 objectAtIndex:indexPath.row];
UIFont *myFont1 = [ UIFont fontWithName: #"Arial" size: 20.0 ];
cellNAMElabl.font = myFont1;
[cell.contentView addSubview:cellNAMElabl];
cellDetaillabl = [[UILabel alloc] initWithFrame:CGRectMake(88, 28, 150, 20)];
[cellDetaillabl setTag:2];
cellDetaillabl.text = [email5 objectAtIndex:indexPath.row];
UIFont *myFont = [ UIFont fontWithName: #"Arial" size: 13.0 ];
cellDetaillabl.font = myFont;
[cell.contentView addSubview:cellDetaillabl];
imgView=[[UIImageView alloc] initWithFrame:CGRectMake(25, 5, 52, 50)];
[imgView setTag:3];
imgView.image = [imagepath5 objectAtIndex:indexPath.row];
[cell.contentView addSubview:imgView];
}
cellNAMElabl = (UILabel *)[cell viewWithTag:1];
cellDetaillabl = (UILabel*)[cell viewWithTag:2];
imgView = (UIImageView*)[cell viewWithTag:3];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
You are not assigning new content to subviews, if they have been already created. After the case if(cell == nil), these you have just got references.
cellNAMElabl = (UILabel *)[cell viewWithTag:1];
cellDetaillabl = (UILabel*)[cell viewWithTag:2];
imgView = (UIImageView*)[cell viewWithTag:3];
Here when cell is not nil, you are just getting references to labels and imageview, but you are not setting new text and image from data source. Add following lines and remove them from the if (cell == nil) part:
cellNAMElabl.text = [name5 objectAtIndex:indexPath.row];
cellDetaillabl.text = [email5 objectAtIndex:indexPath.row];
imgView.image = [imagepath5 objectAtIndex:indexPath.row];
[cell.contentView addSubview:imgView];
The way this dequeueReusableCellWithIdentifier works: If iOS detects that a cell is not displayed anymore, then dequeueReusableCellWithIdentifier will return that cell. If there is no unused cell, it returns nil. So what you need to do:
If dequeueReusableCellWithIdentifier returns nil, then you create a new cell, and you do all the setup that is required for all cells with the same identifier. For example, add view tags like you did, set fonts, colors etc.
Then, whether you use a cell returned by dequeueReusableCellWithIdentifier or one that you just created yourself, you add all the information that is used for the specific section/row that you want to display. So if row 1, row2, and so on display different text, then you set the text here. That's what you didn't do, so when a cell was reused, you didn't set the new text for it.
So the idea is that all the work that is the same for all rows is only done once when a cell is created, and only as many cells are created as is needed to display them on the screen. The work that is different from row to row is done for each row, as it is needed.
If you set a breakpoint in the if cell == nil block its probably only being hit for the first set if your reuseID is correct. Thats why its never getting a chance to set any new data into the cell.
You should not look for a nil cell, rather use a correct reuseID and a prototype cell in IB that is set to a custom UITableViewCell subclass you create.
Its also good practice to implement prepareForReuse on custom cells, where you clear any cell data e.g. label.text = nil, imageview.image = nil
This way you dont get invalid data from previously dequeued cells. It might not solve the question directly, but it would have wiped the fixed data set in your if cell == nil block to help debug.
What you want to do is..
add/setup the tableViewCell UI if the cell is nil..
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
UILabel *cellNAMElabl = [[UILabel alloc] initWithFrame:CGRectMake(88, 10, 150, 20)];
cellNAMElabl.tag = 1;
cellNAMElabl.font = [UIFont fontWithName: #"Arial" size: 20.0 ];
[cell.contentView addSubview:cellNAMElabl];
UILabel *cellDetaillabl = [[UILabel alloc] initWithFrame:CGRectMake(88, 28, 150, 20)];
cellDetaillabl.tag = 2;
cellDetaillabl.font = [UIFont fontWithName: #"Arial" size: 13.0 ];
[cell.contentView addSubview:cellDetaillabl];
UIImageView *imgView=[[UIImageView alloc] initWithFrame:CGRectMake(25, 5, 52, 50)];
imgView.tag = 3;
[cell.contentView addSubview:imgView];
}
//and just update your data if the cell is currently exist and not nil..
//you already called the view using tag so, you dont need those:
// UILabel *cellNAMElabl = nil;
// UILabel *cellDetaillabl = nil;
// UIImageView *imgView = nil;
((UILabel *)[cell viewWithTag:1]).text = [name5 objectAtIndex:indexPath.row]; // cellNAMElabl
((UILabel*)[cell viewWithTag:2]).text = [email5 objectAtIndex:indexPath.row]; // cellDetaillabl
((UIImageView*)[cell viewWithTag:3]).image = [imagepath5 objectAtIndex:indexPath.row]; // imgView
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
hope this have help you, happy coding cheers!

iOS7: UILabel constantly redraw itself on UITableViewCell

CoreData returns BOOL value and according to the value I draw a UILabel on UITableViewCell accessoryView. The problem is that UILabel repeats itself also on the cells it shouldn't appear at all.
CGRect lblRect = CGRectMake(230, 7, 20, 20);
UILabel *lblEnabled = [[UILabel alloc] initWithFrame:lblRect];
lblEnabled.textColor=[UIColor whiteColor];
lblEnabled.textAlignment=NSTextAlignmentCenter;
[lblEnabled setFont:[UIFont fontWithName:#"Verdana" size:10.0]];
[lblEnabled setText:#"40"];
lblEnabled.backgroundColor= [UIColor colorWithPatternImage:[UIImage imageNamed:#"greenBg"]];
lblEnabled.layer.cornerRadius = 9.0;
lblEnabled.layer.masksToBounds = YES;
lblEnabled.tag = indexPath.row;
cell.accessoryView = lblEnabled;
[cell.contentView addSubview:lblEnabled];
So it appears sometimes on the cell where the BOOL value = NO; Your help is strongly appreciated.
EDIT: I draw these labels in cellForRowForIndexPath.
EDIT: I use storyboards, so I don't check if cell is nil.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
/*
if(cell==nil)
{
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
tableView.rowHeight=57.0;
}*/
Coin *coin=[[self frcFromTV:tableView ] objectAtIndexPath:indexPath];
cell.textLabel.text=coin.coinNominal;
if(coin.comSubject.length>0)
{
cell.detailTextLabel.text=[NSString stringWithFormat:#"%#%# (%#) | %#",[self returnCatalogDefinition:coin.catalogIndex],coin.kmRef, coin.dates, coin.comSubject];
}
else
{
cell.detailTextLabel.text=[NSString stringWithFormat:#"%#%# | %#",[self returnCatalogDefinition:coin.catalogIndex],coin.kmRef,coin.dates];
}
if(coin.isCommemorative.boolValue)
{
// implement label
}
if(coin.listed.boolValue)
{
CGRect lblRect = CGRectMake(230, 7, 20, 20);
UILabel *lblEnabled = [[UILabel alloc] initWithFrame:lblRect];
lblEnabled.textColor=[UIColor whiteColor];
lblEnabled.textAlignment=NSTextAlignmentCenter;
[lblEnabled setFont:[UIFont fontWithName:#"Verdana" size:10.0]];
[lblEnabled setText:#"40"];
lblEnabled.backgroundColor= [UIColor colorWithPatternImage:[UIImage imageNamed:#"greenBg"]];
lblEnabled.layer.cornerRadius = 9.0;
lblEnabled.layer.masksToBounds = YES;
lblEnabled.tag = indexPath.row;
cell.accessoryView = lblEnabled;
[cell.contentView addSubview:lblEnabled];
}
return cell;
}
In code below you probably add your label but because those cells are reusable you should handle else statement (hiding label or whatever is appropriate)
if(coin.isCommemorative.boolValue)
{
// implement label
//remove from that part of the statement this line:
//[cell.contentView addSubview:lblEnabled];
} else {
cell.accessoryView = nil;
//hiding or modifying label for other cases
}
if you will not deal with that else statement the change you made in if will be applicable to more than one cell because of the reusing mechanism
As a "side advice" I would recommend you to subclass UITableViewCell and add the property you want (label) to encapsulate that and make only public method for showing or hiding that accessor.
EDIT:
if your flag for a change is not specifying to which cell it has to indicate (for example using indexPath) then the result is as your one.
This is quite global state if(coin.isCommemorative.boolValue) not indicating to which cell it counts try for example (for just learning purpose) add if(coin.isCommemorative.boolValue && indexPath.row%2==0) and see the result.
Are you reusing cells? If so, then you need to remove that cell from contentView inside prepareForReuse method.

UITableViewCell view after being dequeued from UITableView not empty

I have a strange problem using the dequeueReusableCellWithIdentifier: method of UITableView. Not sure if I don't understand the method well enough or is it just plain weird. Here goes:
Im using a UITableView which presents some data to users, and inside my
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath I use the dequeue method like so:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
Afterwards I add some subviews to the contentView property of the cell. When scrolling a bit further down on my table I see those previously added subviews i.e. the cell is not empty but filled with "old" data. If I don't dequeue, and just alloc-init a new one each time, the cells are empty but I do see a bit more memory consumption which is precisely what Im trying to bring down a little. I'm using ARC if that means anything here.
What or how should I tackle the problem? I have tried running a for loop through the subviews of the content view and [view removeFromSuperview] which does remove the previous views and brings down memory consumption a little. But is that really necessary? Or is there a better way?
EDIT here is some more code how I add subviews
cell.backgroundView = [[UIView alloc] initWithFrame:cell.frame];
cell.backgroundColor = kClearColor; //defined to [UIColor clearColor]
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (indexPath.row == 0)
{
UIImageView *shine = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
shine.image = [UIImage imageNamed:#"top_shine_1"];
[cell.backgroundView addSubview:shine]; //its a gradient thats why its added to background
UILabel *appLabel = [[UILabel alloc] initWithFrame:CGRectMake(55, winSize.height * 0.027, 250, 33)];
appLabel.backgroundColor = kClearColor; //defined to clear color
appLabel.textColor = kWhiteColor; //defined to white color
appLabel.text = [viewOrder objectAtIndex:tableView.tag]; //just an array from where I get the required text
appLabel.font = kStandardFontOfSize(30); //defined to a specific font
[cell.contentView addSubview:appLabel];
UIButton *settingsButton = [UIButton buttonWithType:UIButtonTypeCustom];
settingsButton.frame = CGRectMake(10, winSize.height * 0.0377, 31, 21);
[settingsButton setImage:[UIImage imageNamed:#"settings_button"] forState:UIControlStateNormal];
[settingsButton addTarget:self action:#selector(settings:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:settingsButton];
return cell; //here I just return it since this is all the config the first cell needs
}
NSString *app = [viewOrder objectAtIndex:tableView.tag];
NSArray *boxes = [[plist secondObjectForKey:#"order" parent:app] componentsSeparatedByString:#";"];
//Add necessary shines or create the last logotype cell - just some details and stuff, all are just images
if (indexPath.row == 1)
{
UIImageView *shine = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 282.5)];
shine.image = [UIImage imageNamed:#"top_shine_2"];
[cell.backgroundView addSubview:shine];
}
else if (indexPath.row == 2)
{
UIImageView *shine = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, winSize.width, 150)];
shine.image = [UIImage imageNamed:#"main_shine"];
[cell.backgroundView addSubview:shine];
}
else if (indexPath.row == boxes.count + 1)
{
UIImageView *logo = [[UIImageView alloc] initWithFrame:CGRectMake(111.5, 25, 97, 20)];
logo.image = [UIImage imageNamed:#"cell_logo"];
[cell.backgroundView addSubview:logo];
return cell;
}
NSString *databox = [boxes objectAtIndex:indexPath.row - 1];
UIView *view; //Main subview to be added to the cell
/*
here I have a class that creates a view with a bunch of subviews added to that view, the view is then assigned to 'view'; kinda like
view = [someAssembler assembleViewWith:options.....]. all are basically UILabels or ImageViews added to the main view
*/
[cell.contentView addSubview:view]; //and here this 'main view' is added as a subview, this view is still visible after the cell has been dequeued and the shines are as well
return cell;
Before you start criticising why im not using a single UIColor for background and text color let me remind you that this is still in testing stage, it will be taken care of later.
[cell.backgroundView addSubview:shine]; these lines of code are the problem in your case.
You should create a complete reusable cell within the if (!cell) block and repopulate them each time cellForRow is being called. For every unique cell a unique reuse identifier should be used. For example, if you have multiple cells with differently laid out subviews, you should use different identifiers for them.
In your specific example cells must be created in the if (indexPath.row == 1) blocks.
static NSString *cellIdentifier = #"cell";
UITableViewCell *cell = nil;
if (indexPath.row == 0) {
cellIdentifier = #"topCell";
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
if (!cell) {
// create the cell and add the necessary subviews for indexPath row 0
}
return cell;
}
else if (indexPath.row == 1) {
}
//etc.
}
You'll have to create the "main subview" for each cell in the !cell block with this approach though, so you should probably look into subclassing a cell.

Grouped UITableView - Replicate the Settings app table view

I want to create a view like this in my iPhone App:
I do not know exactly what is this control in iOS, that maybe I can set an icon and text in the left side and that small sign in the right side.
I have implemented a TableView, there I was able to set these stuff, like this:
[[cell textLabel] setText:customer.name];
[[cell textLabel] setTextColor:[UIColor blackColor]];
[[cell imageView] setImage:[UIImage imageNamed:#"icon.png"]];
//[[cell detailTextLabel] setText:#"Awsome weather idag"];
cell.accessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
But how can I make it works like that view in the picture?
It is pretty simple, follow the steps below and in case of doubts check out the UITableView documentation:
1. Create a grouped table view:
Programmatically:
CGRect tableFrame = CGRectMake(0, 0, 200, 200);
UITableView *tableView = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewGroupedStyle];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
Allocating a UITableViewController subclass (common case):
MyTableViewController *controller [[MyTableViewController alloc] initWithStyle: UITableViewGroupedStyle];
[self.navigationController pushViewController:controller animated:NO];
Through Interface Build:
Expand the Utilities Menu (top right corner icon);
Select your table view (click on it);
Click on the attributes inspector (top right corner fourth icon);
Under Table View, click on the style dropdown and select grouped.
2. Implement the UITableViewDataSource protocol:
Basically add this three functions to your controller.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in a given section.
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Configure the cells.
return cell;
}
3. Configuring the cells:
The default style of a UITableViewCell has an image view (UIImageView), a title label (UILabel) and an accessory view (UIView). All you need to replicate the table view in the image you provided.
So, you're looking for something like this in tableView:cellForRowAtIndexPath::
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * const cellIdentifierDefault = #"default";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierAccount];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifierAccount];
}
if (indexPath.section == 0) {
cell.imageView.image = [UIImage imageName:#"bluetooth_icon"];
cell.textLabel.text = #"Bluetooth";
// Additional setup explained later.
}else{
if (indexPath.row == 0) {
cell.imageView.image = [UIImage imageName:#"general_icon"];
cell.textLabel.text = #"General";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}else{
cell.imageView.image = [UIImage imageName:#"privacy_icon"];
cell.textLabel.text = #"Privacy";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
return cell;
}
The property accessoryType defines what is going to appear on the right side of a cell, a list of accessory types can be found here.
In the first cell (bluetooth), you'll need to create a custom accessory view and assign it to the cell's accessoryView property. A very naive example of how to achieve this is given below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * const cellIdentifierDefault = #"default";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierAccount];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifierAccount];
label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 44)];
label.textAlignment = NSTextAlignmentRight;
cell.accessoryView = label;
}else{
label = (UILabel *) cell.accessoryView;
}
cell.imageView.image = [UIImage imageName:#"bluetooth_icon"];
cell.textLabel.text = #"Bluetooth";
label.text = #"Off";
return cell;
}
Hope this helps, Mateus
For fast output, you can use some library like
https://github.com/escoz/QuickDialog
protip, in my experience, solutions like this leaves you more tangled when Changes come in.
Some times you only want to change one specific label on once specific view, thats not gona be easy in any ready-made solution.
Look into UITableView Sections. That is what separates the groups apart.

Grouped UITableView rows with varying heights causes content shuffle

Tried my hardest to find the answer here first, but I'm stuck. I have a UITableView set with UITableViewStyleGrouped with 4 sections, each with one or two rows. In two of the sections I needed a row to be a larger height to hold the content I'm sticking in there.
Looks nice except when I scroll up and down, textLablels, accessories and extra subviews start to shift into different rowss and I can't figure out why.
This screenshot shows the table view when it loads and then after I scroll up and down a few times. Each time I scroll different row content shuffles.
I thought I read something about this being an issue with the grouped style. Sure enough, I don't see this issue if I change the table style to default. Am I not allowed to dynamically set the height for some rows when using the grouped style?
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == kSection_Info && indexPath.row == kSectionRow_InfoPhoto)
{
return 84.0;
}
else if (indexPath.section == kSection_Level && indexPath.row == kSectionRow_LevelLevel)
{
return 70.0;
}
return 44.0;
}
I'm setting up each row manually in celForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RecipientEntryCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
switch (indexPath.section)
{
case kSection_Info:
{
switch (indexPath.row)
{
case kSectionRow_InfoName:
{
cell.textLabel.text = #"Name";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(74, 8, 195, 25)];
self.nameLabel.textAlignment = UITextAlignmentRight;
self.nameLabel.font = [UIFont systemFontOfSize:16];
self.nameLabel.textColor = [UIColor blueColor];
self.nameLabel.text = self.currentRecipient.fullName;
[cell.contentView addSubview:self.nameLabel];
break;
}
case kSectionRow_InfoPhoto:
{
cell.textLabel.text = #"Photo";
self.imageButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.imageButton.frame = CGRectMake(10, 14, 64, 64);
[self.imageButton addTarget:self action:#selector(onImageButtonTouch:) forControlEvents:UIControlEventTouchUpInside];
NSString *imageName = #"add_image.png";
UIImage *thumb = [UIImage imageNamed:imageName];
[self.imageButton setImage:thumb forState:UIControlStateNormal];
cell.accessoryView = self.imageButton;
break;
}
default:
{
break;
}
}
break;
}
case kSection_List:
{
switch (indexPath.row)
{
case kSectionRow_ListHasList:
{
cell.textLabel.text = #"Is Listed";
self.listSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
cell.accessoryView = self.listSwitch;
break;
}
case kSectionRow_ListBudget:
{
cell.textLabel.text = #"List Amount";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
self.budgetLabel = [[UILabel alloc] initWithFrame:CGRectMake(124, 8, 145, 25)];
self.budgetLabel.textAlignment = UITextAlignmentRight;
self.budgetLabel.font = [UIFont systemFontOfSize:16];
self.budgetLabel.textColor = [UIColor blueColor];
self.budgetLabel.text = [#"$" stringByAppendingFormat:#"%0.2f", [self.currentRecipient.budget floatValue]];
[cell.contentView addSubview:self.budgetLabel];
break;
}
default:
{
break;
}
}
break;
}
case kSection_Level:
{
switch (indexPath.row)
{
case kSectionRow_LevelLevel:
{
self.levelSlider = [[UISlider alloc] initWithFrame:CGRectMake(8, 2, 284, 40)];
self.levelSlider.minimumValue = 0.0;
self.levelSlider.maximumValue = 100.0;
self.levelSlider.continuous = YES;
UIImage *meterImage = [UIImage imageNamed:#"meter_labels.png"];
UIImageView *meterView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 32, 284, 24)];
[meterView setImage:meterImage];
[cell.contentView addSubview:self.levelSlider];
[cell.contentView addSubview:meterView];
[meterImage release];
break;
}
case kSectionRow_LevelHasLevel:
{
cell.textLabel.text = #"Show Level";
self.levelSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
cell.accessoryView = self.levelSwitch;
break;
}
default:
{
break;
}
}
break;
}
case kSection_RecipientDelete:
{
cell.textLabel.text = #"Delete Recipient";
cell.textLabel.textAlignment = UITextAlignmentCenter;
cell.textLabel.textColor = [UIColor blueColor];
break;
}
default:
{
break;
}
}
}
return cell;
}
The "content shuffling" you see is most likely due to improper handling of cell re-use.
There is no issue specifically with the grouped style. The problem is more likely to manifest itself in that style because fewer cells fit in the screen which requires more scrolling and requires more cell re-use.
You are setting up the cell contents only when creating cells (when cell == nil). When a cell scrolls off the screen, it goes into the re-use queue. The row at the other end that is now visible re-uses the cell view that is in the re-use queue. The re-used cell contains the contents of some other row.
When all the cells are alike (at least in regard to the UI controls and not the data), this isn't a problem. When all or some of the cells are different, you get controls appearing where you don't expect them.
Because you only have a small number of rows and each one is layed out differently, the quick (and perhaps dirty) solution is to use a different re-use identifier for each cell like this:
NSString *CellIdentifier =
[NSString stringWithFormat:#"RecipientEntryCell-%d-%d",
indexPath.section, indexPath.row];
This is not at all recommended if the table view is going to have lots of different cells since every cell for every row in the table will be in memory at the same time (instead of just the few on the screen). Do not use a unique identifier as a way to solve any and all cell re-use problems.
The Table View Programming Guide shows an alternate way to design table views like this where you have a few cells with different layouts. See "The Technique for Static Row Content" on this page.
Ultimately, it's better if you understand that the table view re-uses cells for good reasons and not try to workaround it all the time. Generally, the cellForRowAtIndexPath should look like this:
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCell...
if (cell == nil) {
//Create cell...
//Set UI content that applies to all rows (if any)...
}
else {
//Cell is being re-used.
//Remove UI content that doesn't apply to this row...
}
//Add UI content that applies only to this row...
//Copy values from data source to cell UI controls...
return cell;
If you construct the cells as shown above, do not maintain class-level references to UI controls inside cells (like nameLabel, imageButton, etc). Instead, the control values should be set in cellForRowAtIndexPath from a backing data variable (model) and the value should be read or saved back to the backing data variable as soon as the UI control changes.
For example, instead of storing a reference to a UISlider in a cell, store the current slider value as a float ivar. Initialize the float where appropriate (eg. viewDidLoad). In cellForRowAtIndexPath, create the UISlider, set its value using the float ivar, and have the slider call a method when its value changes. In that method, copy the slider's value to the float ivar.
Hope this helps.

Resources