I am trying to create a UITableViewCell that contains a UIScrollView that is able to scroll horizontally for each cell in the UITableView.
Everything shows correctly and works well. However, when I scroll constantly up and down on the UITableView, memory usage goes up and up and up and up..... which I think means that I am constantly adding the custom elements over each over when the UITableViewCell is being reused. I would like to know how I can stop this from happening.
Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Configure the cell...
NSDictionary *cellDictionary = [xmlMArray objectAtIndex:indexPath.row];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// code
codeString = [[UILabel alloc] initWithFrame:CGRectMake(15.0, 0.5, 70.0, 40.0)];
codeString.text = [cellDictionary objectForKey:#"Code"];
codeString.backgroundColor = [UIColor clearColor];
// series
addressString = [[UILabel alloc] initWithFrame:CGRectMake(220.0, 10.5, addressString.frame.size.width, 50.0)];
addressString.text = [NSString stringWithFormat:#"PC %#: %#",[cellDictionary objectForKey:#"Number"] ,[cellDictionary objectForKey:#"Street"]];
[addressString sizeToFit]; // Dynamic UILabel width
addressString.backgroundColor = [UIColor clearColor];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
UIScrollView *scrollCell = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0, 0.0, cell.frame.size.width, cell.frame.size.height)];
[scrollCell setContentSize:(CGSizeMake((220.0 + addressString.frame.size.width)+15, cell.frame.size.height))];
[scrollCell addSubview:codeString];
[scrollCell addSubview:addressString];
[cell addSubview:scrollCell];
return cell;
}
There seems to be two problems here. I'll try to explain what's actually happening, but you should follow mbm29414's answer on what to do.
In the first part of this method, you are asking for a UITableViewCell using the identifier:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
The next part is to check if you received a cell with this call:
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
This means that if your first line of code DID NOT return a cell-object, then you are instantiating a new one.
Later on in your method, you are instantiating yet another cell:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
UIScrollView *scrollCell (...)
This way you might not actually be reusing the cells, I'm not sure. At the very least, it should not be there. You potentionally allocate double the space each time.
Remove the last instantiation, and that should probably help a little.
The other problem, I think, is that you're adding scrollView and UILabels to your cells' subviews. Your circle of life:
Create cell
Create 2 UILabels
Create ScrollView
Add labels to ScrollView's subview
Add scrollView to Cell's subview
Send cell on it's merry way
When your cell is brought back from the dead during [tableView dequeueReusable..], they still contain the UIScrollView with the UILabel. Your code does not take advantage of that, but rather ignores it. This means that you are adding an ADDITIONAL scrollView with labels into your cell. If you scroll up and down a lot, this means that one single cell can possibly contain 50 different scrollViews, all of them taking the same amount of processing.
This is what happens next:
Get cell from the queue
(this cell already contains UIScollView and UILabels)
Add new scrollView with labels anyway
Send it on it's merry way (now with 2 scrollViews and 4 labels)
To solve this, you should do as mbm29414 suggested, to make your own subclass of UITableViewCell. That way, you can say cell.codeString.text=#"blah";
While you are re-creating a new UITableViewCell each time, you also appear to be endlessly adding UIScrollView and UILabel objects.
First, remove the second call of:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
Second, try subclassing UITableViewCell, making the UI like you like it either in an init method or in IB. Then, make a "setup" method that takes an object and configures each UI element accordingly. That way, you can not only recycle your cells, but also keep from continually creating more UIView subclass instances.
Couple of things.
Your check to see if the table view cell returned by
dequeueReusableCellWithIdentifier: is nil is not necessary, because
that method won't return nil anymore unless you've made a mistake with your identifier. It used to (I think before iOS
6).
Later in the code you're creating a new cell and assigning it to the cell variable which is eventually returned. Why? You already created one, so creating a second is fundamentally wrong.
Related
The problem is that when in my programme I have to change the datas in that UILabels inside each cells. But when I tried changing it, the datas are getting over written. I can't remove the previously created labels from the contentview. I am working in Swift.
If you are creating those labels in code not in interface builder. You need to delete previous label before creating new one.
Better way of doing this is to do it in interface builder, subclass UITableViewCell, use your custom cell, drop a label in it, connect outlet and just override label data.
Go through this link. It will shows how to add UILabel in a custom cell.
Ok, I understand your problem. You must using reusable cell technique and you are creating your label every time. I don't know the syntax of Swift so showing you in Objective-C. Hope you can convert it into Swift.
In cellForRowAtIndexPath delegate
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *myLabel = (UILable *)[[cell contentView] viewWithTag:1];
if(!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
myLabel = [[UILabel alloc] initWithFrame:yourFrame];
[myLabel setTag:1];
}
[myLabel setText:#"your text"];
Try overriding the following method in your Cell.m to solve problem.
- (void) prepareForReuse {
NSLog(#"prep for reuse");
// set your lable to nil here
}
I'm new in iOS programming that's why I'm looking for the most efficient solution to my problem.
What I want to achieve is to display in UITableViewCell with a name (some text) and under each name some filled little rectangles with a number inside, similar to badges.
My first idea is to create a UIView that will represent the badge and in a custom UITableViewCell I will add these rectangles as subviews.
The second idea is to create only one UIView that will draw all the little rectangles.
My question is, which is the better performing solution knowing that:
the number of cells will be max. 20 and the total number of rectangles no more than 50
The number of rectangles displayed in a cell is different
I want to reuse the cells, so I have to update/redraw the cell content for each row
I want to avoid the cell selection view problem that "hides" the subviews
Of course any other solution is appreciated.
Thanks in advance,
hxx
What i would suggest is to sub class the UITableViewCell and make the customization u need in it.The customized view can have a label and rectangles below it.
The rectangles can be small custom buttons with background images (if you have any or give it a background color) and title as your number.You would have to ,however calculate their width based on the width of your table to accomodate the maximum number of rectangles.
You can disable the selection of the table in the xib or you can do it programmatically like so cell.selectionStyle = UITableViewCellSelectionStyleNone; and do not implement didSelectRowAtIndexPath
I have followed the approach of subclassing the cell for my tables to customize their look and feel and it works good.I hope this helps.
A Good tutorial to begin with subclassing can be found here
http://howtomakeiphoneapps.com/how-to-design-a-custom-uitableviewcell-from-scratch/1292/
Why you are not creating cell in -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath Here you can defines your custom type cell which will also reuse and whenever you want you can add the different thing to cell like this.
static NSString *CellIdentifier = #"Cell";
UILabel *RequestSentTo;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
RequestSentTo = [[UILabel alloc] initWithFrame:CGRectMake(11, 2, 286, 40)];
RequestSentTo.backgroundColor = [UIColor clearColor];
RequestSentTo.tag = 200;
RequestSentTo.numberOfLines = 3;
RequestSentTo.font=[UIFont boldSystemFontOfSize:15.0];
RequestSentTo.textColor = [UIColor blackColor];
RequestSentTo.lineBreakMode=UILineBreakModeWordWrap;
[cell.contentView addSubview:RequestSentTo];
} else {
RequestSentTo=(UILabel*)[cell.contentView viewWithTag:200];
}
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:#"Shift Request for "];
[string appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%# by ",dateStr] attributes:nil]];
[string appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"Dr. %#",notificationsObj.doctorName] attributes:purpleTextAttributes]];//purpl
RequestSentTo.attributedText=string;
RequestSentTo.lineBreakMode=UILineBreakModeWordWrap;
RequestSentTo.numberOfLines = 3;
Whenever you want you can add the things you want with reusing cell. Hope this helps
2 methods come into my mind.
You can put the components as subview inside UITableViewCell(Through XIB or programatically subclassing UITableViewCell) and use it in UITableView.
You can subclass UITableViewCell, and override the -(void)drawRect method and draw all the components that you wish to be displayed on cell.
See if can help.
You can create a new class extends to UITableViewCell, which means to rewrite UITableViewCell as your own cell named as MyTestCell.
And in this Cell you call create your properties, like labels and views, and add those to your new cell.
like add this to MyTestCell.h
#property (nonatomic, retain) UILable *myLable1;
#property (nonatomic, retain) UIView *mySubview1;
MyTestCell.m
_myLable1 = .....
_mySubview = .....
[self addSubview: _myLbale1];
[self addSubview: _mySubview1];
And when use, u can work like this
static NSString *CellIdentifier = #"MyCell";
MyTableViewCell *cell = [tableview dequeReuseID:CellIdentifier];
if (cell == nil) {
cell = [MyTableViewCell alloc] init.........
}
//And you can sign your property here in your cell
cell.myLable1 = ....
cell.myView1 = .....
return cell;
}
If your strings add to the lable is different,make the lable.height is different. you can use code like this
CGSize labelSize = [str sizeWithFont:[UIFont boldSystemFontOfSize:17.0f]
constrainedToSize:CGSizeMake(280, 100)
lineBreakMode:UILineBreakModeCharacterWrap]; //check your lableSize
UILabel *patternLabel = [[UILabel alloc] initWithFrame:CGRectMake(35, 157, labelSize.width, labelSize.height)];
patternLabel.text = str;
patternLabel.backgroundColor = [UIColor clearColor];
patternLabel.font = [UIFont boldSystemFontOfSize:17.0f];
patternLabel.numberOfLines = 0;// must have
patternLabel.lineBreakMode = UILineBreakModeCharacterWrap;// must have
add this to your cell, and make it dynamically resize your lable as well as your cell! And also you have to dynamically set high for your tableView Row height.(Do know what is dynamically resize?)
See this:
rewrite the method setMyLable1 in MyTableViewCell.m
-(void)setMyLable1:(UILable*)aLable
{
//in here when never your sign your alabel to your cell (like this : cell.myLable1) this method will be call and u can get the size of your string and set this label's height
//get string size StringSzie
[_myLable1 setFrame:CGRectMake(10,10,stringSize.width,stringSize.height)];
//And resize your cell as well
[self setFrame:CGRectMake(0,0,_myLable1.frame.size.width+20,_myLable1.frame.size.height+20)];
//done!!!
}
OK you get a automactically reszie cell for yourself and you have to dynamically reset height for your row in tableView too!!!!!
What do you need is called custom cell
Here is good tutorial for it
customize table view cells for uitableview
Being working with StoryBoard I have this situation, with the code below:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.textLabel.text=[[cityArray objectAtIndex:indexPath.row] valueForKey:#"CELL_TEXT"];
cell.detailTextLabel.text=[[cityArray objectAtIndex:indexPath.row] valueForKey:#"DETAIL_TEXT"];
My tests show me that regardless of the fact that I use UITableViewCellStyleDefault, or UITableViewCellStyleSubtitle when performing the alloc, the two last lines of code above will work.
Those two last lines are only depending on my settings in the StoryBoard (cell style to Subtitle or not).
Now here is my question: How can I programmaticaly control the style of the cells, going from UITableViewCellStyleDefault to UITableViewCellStyleSubtitle and vice versa?
Obviously, changing the alloc only, does not work; and I did not find any property that I could set either.
If you're using a Storyboard with a prototype cell, the cell will not be nil. It will always be created. Either don't use a prototype cell in a Storyboard or allocate a different protoype cell from the Storyboard.
In the following code, if we do [cell addSubview: someLabel] vs [cell.contentView addSubview: someLabel], they seem to work the same. Is there any difference doing one or the other? (the custom cell in the real code is adding UIImageView and UILabel) (UIView, on the other hand, doesn't have contentView, so we don't need to add subview to its contentView. UITableViewCell is a subclass of UIView by the way)
-(UITableViewCell *) tableView:(UITableView *) tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
if ([tableView isEqual:self.songsTableView]){
static NSString *TableViewCellIdentifier = #"MyCells";
cell = [tableView dequeueReusableCellWithIdentifier:TableViewCellIdentifier];
if (cell == nil){
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:TableViewCellIdentifier];
}
// ... some code to create a UILabel (not shown here)
[cell addSubview: someLabel]; // vs using [cell.contentView addSubView: ...]
I believe If I am not wrong, the contentView is a subview of UITableViewCell.
If you look at this page here, you can see there are actually 3 subviews in a UITableViewCell
I think by default, the Editing Control is hidden until you enter edit mode for a table in which case, the Editing Control appears (the minus button left of each row) and your contentView gets resized and pushed to the right. This is probably what gives the "proper animation" effect mentioned by the other answer.
To test the difference, try adding a subview such as UILabel with text, to the cell rather than the cell.contentView. When you add it to cell rather than cell.contentView and you enter edit mode for your table, I believe your UILabel will not resize, you will see the edit button ontop/below the minus sign button.
Placing your views in the contentView affects proper animation in and out of edit mode. Place all of your subviews in contentView when you're not subclassing, which should be all of the time unless you know what you're doing.
When I push a UIViewController onto my UINavigation controller like:
[(UINavigationController *)self.parentViewController pushViewController:[[[Fonts alloc] initWithNibName:#"Fonts" bundle:nil] autorelease] animated:YES];
Where Fonts.xib is a UIView with only UITableView controlled by a Fonts object that is a subclass of UIViewController and acts as the UITableView's dataSource and delegate.
In the Fonts object I create a UITableViewCell like:
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: #"BlahTableViewCell"];
if (!cell) {
cell = [[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: #"BlahTableViewCell"];
[cell autorelease]; // Delete for ARC
}
return cell;
}
And then I change the font of the cell here:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
[cell.textLabel setFont:[(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontWithSize:cell.textLabel.font.pointSize]];
cell.textLabel.text = [(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontName];
}
listOfFonts is an NSArray of UIFont objects.
When the view appears it looks like UITableView without changed fonts
If I call reloadData on the UITableView or if I drag the UITableViewCells off screen with my finger and let them bounce back they are redrawn and the view the cells display with the labels having their fonts changed.
It seems like the issue is the UITableViewCells are being drawn too early. If I delay the drawing of them everything looks correct but I want the UITableView to be displaying correctly when the UINavigationController slides my view into place.
Any idea what I am doing wrong?
EDIT: I uploaded a simple and straightforward example of my issue to Dropbox. http://dl.dropbox.com/u/5535847/UITableViewIssue.zip
SOLVED IT!
Ok so I was having exactly the same issues as the original poster and this was the problem.
The line that's causing issues is:
[cell.textLabel setFont:[(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontWithSize:cell.textLabel.font.pointSize]];
Specifically, your issue is because you're trying to feed the cell's textLabel its own pointSize, but pointSize doesn't exist yet so strange bugs occur instead. For me, I noticed that a "transform" was failing due to a singular matrix being non-invertible. As soon as I hardcoded a standard value as my pointSize I saw all my labels draw with the proper font instantly. Note: this makes sense as to why a redraw worked, because then your textLabel does indeed have a pointSize.
In any case, you need to explicitly set your pointSize here, no using what the textLabel "already has" because it doesn't have anything until you're "reloading" a cell.
Set the label font inside -tableView:cellForRowAtIndexPath:.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
// do it here if your font doesn't change ....
}
// otherwise here with your font ...
cell.textLabel.font = [UIFont boldSystemFontOfSize:12];
return cell;
}
I'm not sure that table cells are designed to be customisable in this way. The table cell may assume that you won't customise the font, and so not draw itself in a way that's compatible with what you are trying to do.
You'd be better off creating a custom table cell, or appending a UILabel as a subview to the table cell when you create it, and them setting the font of that label instead.
It may seem like overkill for such a small customisation, but it's flexible and it's guaranteed to work.