I get a memory leak in cellForRowAtIndexPath, in a new application, with ARC enabled.
The cellForRowAtIndexPath displays just a UILabel.
Buf it I add [myUIlabel release]; I get ARC error:
"ARC forbids explicit message send of 'release'"
Leak goes away if I remove the UILabel.
I don't want to disable ARC because it makes memory mgmt. easier.
What is the solution?
HERE'S THE CODE...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = indexPath.row;
float font_size;
UITextView* PWR_RX_cover_box;
int x,y,w,h;
// Determine which channel:
int channel = tableView.tag; // tag=channel, set at init time
// Prepare to update cell:
// DOCUMENTATION: Table View Programming Guide for iOS > Adding subviews to a cell’s content view
// Give each cell a cell identifier unique to each channel tableView and unique to each row, so that each gets a unique data structure:
NSString *CellIdentifier = [NSString stringWithFormat:#"%d_%d",channel,indexPath.row];
//static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// if nil: cell(chan, row) has not been created before. <>nil: cell = data structure previously initialized
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: CellIdentifier];
}
// Erase anything previously displayed in the cell, by drawing cell-size big, white label:
font_size = 10.0;
// Top, left corner of cell:
y = 0;
x = 0;
// Entire area of cell:
h = CHANNEL_ROW_HEIGHT; // height of cell
w = channel_tableView_width; // width of cell
UILabel* index_label = [[UILabel alloc] initWithFrame: CGRectMake( x,y, w,h)];
index_label.backgroundColor = [UIColor whiteColor];
index_label.textAlignment = NSTextAlignmentLeft; // NSTextAlignmentCenter, NSTextAlignmentLeft NSTextAlignmentRight
index_label.textColor=[UIColor darkGrayColor];
index_label.numberOfLines=1;
index_label.font = [UIFont systemFontOfSize: font_size];
index_label.text = [NSString stringWithFormat: #"" ];
//index_label.text = [NSString stringWithFormat: #" *LAST %d *", ++last_ind]; // normally ""
[cell.contentView addSubview:index_label ];
[index_label release]; <<<<<<<<<<<<<<<<<<< CAUSES ARC COMPILE ERROR
return cell;
}
you are allocating and adding index_label to each cell every time.so it is increasing memory every time. you can create index_label in (cell == nil) block and assign some tag to index_label to access the label each time to update properties of index_label.
solution:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = indexPath.row;
float font_size;
UITextView* PWR_RX_cover_box;
int x,y,w,h;
// Determine which channel:
int channel = tableView.tag; // tag=channel, set at init time
// Prepare to update cell:
// DOCUMENTATION: Table View Programming Guide for iOS > Adding subviews to a cell’s content view
// Give each cell a cell identifier unique to each channel tableView and unique to each row, so that each gets a unique data structure:
NSString *CellIdentifier = [NSString stringWithFormat:#"%d_%d",channel,indexPath.row];
//static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// if nil: cell(chan, row) has not been created before. <>nil: cell = data structure previously initialized
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: CellIdentifier];
UILabel* index_label = [[UILabel alloc] initWithFrame: CGRectZero];
index_label.backgroundColor = [UIColor whiteColor];
index_label.textAlignment = NSTextAlignmentLeft; // NSTextAlignmentCenter, NSTextAlignmentLeft NSTextAlignmentRight
index_label.textColor=[UIColor darkGrayColor];
index_label.numberOfLines=1;
index_label.font = [UIFont systemFontOfSize: font_size];
[cell.contentView addSubview:index_label ];
index_label.tag=TAG_VALUE;
}
// Erase anything previously displayed in the cell, by drawing cell-size big, white label:
font_size = 10.0;
// Top, left corner of cell:
y = 0;
x = 0;
// Entire area of cell:
h = CHANNEL_ROW_HEIGHT; // height of cell
w = channel_tableView_width; // width of cell
UILabel* index_label=[cell.contentView viewWithTag:TAG_VALUE];
index_label.text = [NSString stringWithFormat: #"" ];
index_label.frame=CGRectMake( x,y, w,h);
return cell;
}
You are adding the index_label subview to each cell EVERY TIME you dequeue a cell. You will end up adding the label multiple times and increasing your memory usage; however, this is not a memory leak but a problem in your logic. The memory will be reclaimed when the cell is destroyed.
The solution is simple: Create your UILabel in your cell XIB, Prototype Cell or inside the cell == nil code section. Which one of these options is appropriate depends on how you've written your app; personally I use storyboards with prototype cells.
Related
I've got a UICollectionViewController which displays blocks of telephone numbers (see image). When the view loads they all appear fine however when i either begin scrolling, changing rotation, or execute a search function which alters the (mutable) array in which the data is sourced, i see these malformed labels. I did think it might be the iOS simulator however from looking at it, it appears to be an issue with the positioning of UICollectionViewCells.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor grayColor];
cell.layer.cornerRadius = 5;
[cell setClipsToBounds: YES];
CGRect cellBound = CGRectMake(25, 12.5, 150, 12.5); // x, y, w, h
UILabel *title = [[UILabel alloc] initWithFrame:cellBound];
NSString *number = [[searchNumbers objectAtIndex:indexPath.row] valueForKey:#"number"];
number = [number stringByReplacingOccurrencesOfString:#"+44" withString: #"0"];
title.text = number;
[cell addSubview:title];
return cell;
}
It should be noted that i am using UICollectionViewFlowLayout
As #Woodstock mentioned, this is due to "over-adding" UILabel objects to your cell.
Rather than his solution, which still adds the UILabel to the cell in -collectionView:cellForRowAtIndexPath:, the better MVC solution is this:
// A UICollectionViewCell subclass
// Make sure to pick the correct "init" function for your use case
- (instancetype)init... {
self = [super init...];
if (self != nil) {
[self setupCell];
}
return self;
}
- (void)setupCell {
self.backgroundColor = [UIColor grayColor];
self.clipsToBounds = YES;
self.layer.cornerRadius = 5;
CGRect cellBound = CGRectMake(25, 12.5, 150, 12.5); // x, y, w, h
// Assumes you've set up a UILabel property
self.titleLabel = [[UILabel alloc] initWithFrame:cellBound];
[cell addSubview:self.titleLabel];
}
- (void)configureWithNumber:(NSString *)number {
number = [number stringByReplacingOccurrencesOfString:#"+44" withString: #"0"];
self.titleLabel.text = number;
}
// In your UICollectionViewDataSource/Delegate implementation
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
NSString *number = [[searchNumbers objectAtIndex:indexPath.row] valueForKey:#"number"];
[cell configureWithNumber:number];
return cell;
}
Basically, you want to set up and add views only when setting up the cell initially. After that, you should pass in a data value/object and configure the cell. If you have cells that need different controls (2 labels vs. 1, etc.), then make multiple subclasses. This way, you encapsulate your classes for cleaner code and better reuse.
I believe this is happening because you are adding more and more UILabel subviews to your cell (over and over again as cellForItemAtIndexPath is called). You need to add a check and only add a label subview if the cell doesn't already have one. The dequeued cells already have the label subview if they're being reused, if this label already exists you simply need to set it's text from your datasource.
Pseudocode:
for subview in subviews {
if subview.isKindOfClass(UILabel) {
// assign the new text label.
}
else
{
// create and add the UILabel subView.
}
}
This is an easy mistake to make as dequeueReusableCellWithReuseIdentifier can either give you a previously used cell OR as you've seen give you a fresh one initially. Which is why the app works correctly when you start, but gets messy as you scroll.
If I have a long text I have to increase the cell size to fit the text.
When I assign: cell.textLabel.text = #"my string" , if the string is long it gets truncated.
How can I display the text in two or more rows for this case? I am using UITableViewCell only and not subclassing it anywhere. Is there some code to display long texts using cell.textLabel directly? I am not talking about adding a seperate view to cell.
cell.textLabel will not allow you to line break a string into two lines. What you will have to do is customize it add your own UILabel to UITableViewCell and define its parameters.
Here's a working code that you can add to your TableView.
//define labelValue1 in your .h file
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSLog(#"Inside cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
labelValue1 = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 200, 100)]; //adjust label size and position as needed
labelValue1.font = [UIFont fontWithName:#"BradleyHandITCTT-Bold" size: 23.0];
labelValue1.textColor = [UIColor whiteColor];
labelValue1.textAlignment = NSTextAlignmentCenter;
labelValue1.numberOfLines = 2; //note: I said number of lines need to be 2
labelValue1.backgroundColor = [UIColor clearColor];
labelValue1.adjustsFontSizeToFitWidth = YES;
labelValue1.tag = 100;
[cell.contentView addSubview:labelValue1];
}
else
{
labelValue1 = (UILabel *) [cell viewWithTag:100];
}
// Set up the cell.
NSString *str1 = [arryData3 objectAtIndex:indexPath.row];
labelValue1.text = str1;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
Multiple lines can be shown using
cell.textLabel.LineBreakMode = NSLineBreakByWordWrapping
This is for Swift.
cell.textLabel?.numberOfLines = 10 /// or the number you like.
STEP 1 - this is working fine
I have UITableView that loads custom UITableViewCells
STEP 2 - this is kinda works
I change the UITableViewCell height so that all the data contained in the cell within a UITextView is visible
I manage to get the UItextView data and resize it by using the following code:
UITextView *dummy = [[UITextView alloc] init];
dummy.font = [UIFont systemFontOfSize:14];
dummy.text = cell.textView.text;
CGSize newSize = [dummy sizeThatFits:CGSizeMake(270.0f, 500.0f)];
//get the textView frame and change it's size
CGRect newFrame = cell.textView.frame;
newFrame.size = CGSizeMake(270.0f, fmaxf(newSize.height, 60));
cell.textView.frame = newFrame;
//resize the cell view I add +95 because my cell has borders and other stuff...
CGRect cellFrame = CGRectMake(0, 0, 320, newFrame.size.height+95);
cell.cellView.frame = cellFrame;
I then manage to set the UITableViewCell height using the delegate function heightForRowAtIndexPath:
Now when I run the code and scroll up and down the table cells the behavious isn't always as expected... i.e the cell size isn't always the right size, but if I scroll up and down it sometimes loads up the right size again.
I am thinking that perhaps the dequeueReusableCellWithIdentifier is the issue
cellIdentifier = #"tableCell";
ctTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
Since I am recycling cells of different size with the same identifier...
Can anybody give me some guidelines on how best to solve this issue?
Thanks!!!
I would start by adding UITextView dynamically inside cellForRowAtIndexPath method. assumptions(data array contains the content to be displayed inside cell)
// Defines
#define CELL_TEXTVIEW_WIDTH = 320
#define CELL_TEXTVIEW_HEIGHT = 9999
#define PADDING = 5
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"tableCell";
ctTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if(cell == nil){
cell = [[tableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *_data = [data objectAtIndex:indexPath.row];
CGSize _data_contentsize = [_data sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(CELL_TEXTVIEW_WIDTH, CELL_TEXTVIEW_HEIGHT) lineBreakMode:NSLineBreakModeWordWrap];
UITextView *dummy=[[UITextView alloc] initWithFrame:CGRectMake(5, 5, 290, _data_contentsize +(PADDING * 2))];
// add Font and text color for your textview here
[cell.contentView addSubview:dummy];
return cell;
}
After this calculate the height of the cell under heightForRowAtIndexPath method.
have you implemented following method ?
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
you should set height for each row , if they are different from each other
I've added a tableView and dragged a table view cell into it.
in the utilities panel, I changed the style to subtitle.
I've also tried changing it in code:
- (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];
}
cell.textLabel.textAlignment = UITextAlignmentCenter;
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
cell.textLabel.text = [myArray objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [myArray2 objectAtIndex:indexPath.row];
return cell;
The center alignment doesn't work!
I've tried adding a label object to the cell to have a workaround. But I don't know how to access it. even though I assigned an outlet to it, this wouldn't work:
cell.labelForCell....
What should I do?
any suggestions on how I make it work the usual way, without adding a label to the cell or something?
For the UITableViewCellStyleSubtitle text alignment cannot be changed
You will have to put a label and add your alignment to it,
To do that you could use this code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *myLabel;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
myLabel = [[UILabel alloc] initWithFrame:Your_Frame];
//Add a tag to it in order to find it later
myLabel.tag = 111;
//Align it
myLabel.textAlignment= UITextAlignmentCenter;
[cell.contentView addSubview:myLabel];
}
myLabel = (UILabel*)[cell.contentView viewWithTag:111];
//Add text to it
myLabel.text = [myArray objectAtIndex:indexPath.row];
return cell;
}
The problem with the subtitle style is that it does a [self.textLabel sizeToFit] when it lays out. When you center in a container that is the perfect size of the contents, nothing changes.
Try this. In a subclass of UITableViewCell, set your textAlignment, and use this as your layoutSubviews code:
- (void)layoutSubviews
{
[super layoutSubviews];
{
CGRect frame = self.textLabel.frame;
frame.size.width = CGRectGetWidth(self.frame);
frame.origin.x = 0.0;
self.textLabel.frame = frame;
}
{
CGRect frame = self.detailTextLabel.frame;
frame.size.width = CGRectGetWidth(self.frame);
frame.origin.x = 0.0;
self.detailTextLabel.frame = frame;
}
}
This makes the textLabel's frame full width, and thus allows the centering effect to be noticeable.
Note: since this overrides layoutSubviews, there is a performance cost as it will be called often.
I think the reason is that: the textLabel's width depends on the text length, if the text is too long to show all in a signal line, and you have already set the line break mode and set the number of lines to 0, you will find that the text alignment will be work.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (nil == cell)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier] autorelease];
cell.textLabel.textAlignment = UITextAlignmentLeft;
cell.textLabel.textColor = [UIColor redColor];
cell.detailTextLabel.textColor = [UIColor greenColor];
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
cell.textLabel.lineBreakMode = UILineBreakModeTailTruncation;
cell.textLabel.numberOfLines = 0;
}
cell.textLabel.text = [NSString stringWithFormat:#"You can initialize a very long string for the textLabel, or you can set the font to be a large number to make sure that the text cann't be shown in a singal line totally:%d", indexPath.row];
return cell;
}
I think adjusting the frame will work. I had style of UITableViewCellStyleValue2 but If i have a bit lengthy text in textLabel, it getting truncated at tail and textAlignment does not work here, so thought of increase the width textLabel.
-(void)layoutSubviews{
[super layoutSubviews];
if ([self.reuseIdentifier isEqualToString:#"FooterCell"]) {
CGRect aTframe = self.textLabel.frame;
aTframe.size.width += 40;
self.textLabel.frame = aTframe;
CGRect adTframe = self.detailTextLabel.frame;
adTframe.origin.x += 70;
self.detailTextLabel.frame = adTframe;
}
}
It is impossible to change frame for textLabel and detailTextLabel in UITableViewCell
right in cellForRowAtIndexPath: method.
If you don't want to subclass your cell you can implement a small hack using
performSelector:withObject:afterDelay:
with zero delay for changing geometry right after default layoutSubviews:
[self performSelector:#selector(alignText:) withObject:cell afterDelay:0.0];
See details at here
Hi please add the following instead of your code,
cell.textLabel.textAlignment = UITextAlignmentCenter;
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
change to
cell.textLabel.textAlignment = NSTextAlignmentCenter;
cell.detailTextLabel.textAlignment = NSTextAlignmentCenter;
it will work fine.
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.