Most of the views in my app are UITableVlews inside a UIViewController. My App feels like it's lagging when trying to scroll through the tables. I was wondering (1.) if it is better to create the cell objects in the table view, or create them at runtime and add them to the cells subview?
examples:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
case 3:
{
NSNumber *tetherState = [[mobIntDict objectForKey:#"State"] intValue];
NSNumber *currValState = [[NSNumber numberWithInt:1023] intValue];
tetherSw = [[UISwitch alloc] initWithFrame:CGRectMake(197, 8, 94, 27)];
tetherSw.tag = kDefaultSwTag;
if(tetherState == currValState){
tetherSw.on = YES;
}else{
tetherSw.on = NO;
}
[tetherSw addTarget:self action:#selector(tetherSwAction:) forControlEvents:UIControlEventValueChanged];
[cell.contentView addSubview:tetherSw];
cell.textLabel.text = #"Tether";
[tetherSw release];
}
break;
}
-OR-
-(void)viewDidLoad{
tetherSw = [[[UISwitch alloc] initWithFrame:CGRectMake(197, 8, 94, 27)] autorelease];
tetherSw.tag = kDefaultSwTag;
[tetherSw addTarget:self action:#selector(tetherSwAction:) forControlEvents:UIControlEventValueChanged];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
case 3:
{
[cell addSubView:tetherSw];
}
}
It doesn't matter. Your cell is complex and therefore your view lags.
If you want performance, avoid the UISwitch. Toggle the cell's checkmark instead. In fact, just avoid any fancy table view cell subclasses or custom backgrounds, to reduce the size of the view hierarchy.
Are you properly de-queuing and reusing cells?
It would optimise things a lot if you reused a cell, say a #"SwitchCell", that would speed up scrolling a lot. Currently a lot of time will be spent adding the switch to the cell's content view (laying out of views etc,) and performing other tasks that only need to happen once in a cells lifetime, instead of every time a new cell appears while scrolling.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Create cell
static NSString *CellIdentifier = #"SwitchCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
UISwitch *tetherSw = [[UISwitch alloc] initWithFrame:CGRectMake(197, 8, 94, 27)];
tetherSw.tag = kDefaultSwTag;
[tetherSw addTarget:self action:#selector(tetherSwAction:) forControlEvents:UIControlEventValueChanged];
[cell.contentView addSubview:tetherSw];
[tetherSw release];
}
// Setup for each cell
cell.textLabel.text = #"Tether";
NSNumber *tetherState = [[mobIntDict objectForKey:#"State"] intValue];
NSNumber *currValState = [[NSNumber numberWithInt:1023] intValue];
UISwitch *tetherSw = (UISwitch *)[cell.contentView viewWithTag: kDefaultSwTag];
tetherSw.on = (tetherState == currValState);
// Return
return cell;
}
See the docs for dequeueReusableCellWithIdentifier for more information on dequeuing.
Also, make sure that there is no transparency in any of your cell's subviews. This causes a lot of lagging. Make sure any labels or anything else you add has opaque = YES and a background color set.
Actually, my tables are setup just fine. A solid restore did the trick, and my app runs without the afore-mentioned "LAG"
Related
I didn't have this problem with iOS6, but am currently having it with iOS7. I have a UITableView and you can see 8 cells at the time the view is loaded. Each populated with different names from and array. If you scroll down, the next two cells look good, but everything past that gets text laid on top of it; That text being the contents of what was in the previous cells. So the 10th cell will have what was in the first cell, as well as what is supposed to be in the 10th cell laid on top of it.
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
//Create Label for Name inside cell
UILabel *name = [[UILabel alloc] initWithFrame:CGRectMake( 7.0, 5.0, 300.0, 30.0 )];
[name setText:[self.entriesArray objectAtIndex:indexPath.row]];
//Check to see if current person is a Parent or Child
NSString *class = [self.database getCellDataWithMembership:[self.MembershipArray objectAtIndex:indexPath.row] andColIndex:4];
if([class isEqualToString:#"CHILD"])
{
name.textColor = [UIColor colorWithRed:25.0f/255.0f green:111.0f/255.0f blue:45.0f/255.0f alpha:1.0f];
name.font = [UIFont fontWithName:#"Helvetica-Bold" size:17.0];
}
[cell.contentView addSubview:name];
return cell;
}
My skill with Table views is makeshift at best. I've been reading lots of documentation and researching solutions, but was not able to come up with a solution. I just find it odd that it works perfect for iOS6, but not for iOS7.
So it fetches a person's name from an array and I want to populate the cells with those names. I was able to originally accomplish this using:
cell.textLabel.text = [self.entriesArray objectAtIndex:indexPath.row];
if([class isEqualToString:#"CHILD"])
{
cell.textLabel.textColor = [UIColor colorWithRed:25.0f/255.0f green:111.0f/255.0f blue:45.0f/255.0f alpha:1.0f];
cell.textLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:17.0];
If I use that instead of the "name" UILabel actions from the first code block, then it displays the names perfectly with no text overlay, but what becomes an issue is the text color. If they are labeled as a CHILD then they should be green text and bold. However, after scrolling down, every person becomes green when they shouldn't be.
Sorry for the lengthy question. I've been working on this and racking my brain around it and I just can't seem to figure it out. Any help would be greatly appreciated.
I'm dynamically adding UI objects to cells. As "caglar" notes, adding and then removing is probs not the best-practice. But, I am doing this also. The way I get around adding loads of UI objects to the cell, each time it's displayed, is by removing all my subviews first. The willDisplayCell delegate then adds them back. Clearly, if you want to remove only certain views, you'll have to tag the view and be more selective with your removal. But you can remove all with the following.
-(void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath {
[[cell.contentView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
Just ensure you're adding / removing your labels to the cell's contentView. In iOS7, you'll remove that too, if you're not careful.
Hope it helps you out.
In your code, labels are added to cell whenever cellForRowAtIndexPath: method is called. I mean you add labels many times. You can remove the label which was added before.
//If label was added with tag = 500, remove it
for (UIView *sv in cell.contentView.subviews)
{
if (sv.tag == 500)
{
[sv removeFromSuperview];
}
}
//Create Label for Name inside cell
UILabel *name = [[UILabel alloc] initWithFrame:CGRectMake( 7.0, 5.0, 300.0, 30.0 )];
[name setText:[self.entriesArray objectAtIndex:indexPath.row]];
name.tag = 500;
However, this solution is not a good solution. I think creating a custom UITableViewCell is the right thing to do.
You can try this method, when the cell is nil, you need to creare the UILabel* name, then you set the name label with a tag name.tag = 1000, then you can access this label with this method UILabel* name = (UILabel*)[cell.contentView viewWithTag:1000];.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//Create Label for Name inside cell
UILabel *name = [[UILabel alloc] initWithFrame:CGRectMake( 7.0, 5.0, 300.0, 30.0 )];
name.tag = 1000;
[cell.contentView addSubview:name];
}
UILabel* name = (UILabel*)[cell.contentView viewWithTag:1000];
[name setText:[self.entriesArray objectAtIndex:indexPath.row]];
//Check to see if current person is a Parent or Child
NSString *class = [self.database getCellDataWithMembership:[self.MembershipArray objectAtIndex:indexPath.row] andColIndex:4];
if([class isEqualToString:#"CHILD"])
{
name.textColor = [UIColor colorWithRed:25.0f/255.0f green:111.0f/255.0f blue:45.0f/255.0f alpha:1.0f];
name.font = [UIFont fontWithName:#"Helvetica-Bold" size:17.0];
}
return cell;
}
You can set uilabels by using ios6/7 delta shown is size inspector .firstly set your total view for ios7 by changing values x,y,width,height and then change value in ios6/7 delta to make it for ios6 .Hope you get
you may change the this code.
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; -> UITableViewCell *cell = nil;
I have a grouped tableView of 4 rows. I did a pretty big overhaul on how I am doing things between two views and now reloadData is acting weird. It will only refresh the data in one row. (the last one) and none of the others.
I have checked my values in my viewDidAppear method (where I call reloadData) and all my values are updated.
The code..
- (void)viewDidAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// reload the table data
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Set up the cell...
static NSString *CellWithIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellWithIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellWithIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [_tableGroup.options objectAtIndex:rowcount];
cell.tag = rowcount;
rowcount++;
//label for currently selected/saved setting
_currentSetting = [[BaseLabel alloc] initWithFrame:CGRectMake(160, 8, 115, 25)];
[_currentSetting setFont:[UIFont systemFontOfSize:14]];
_currentSetting.backgroundColor = [UIColor clearColor];
_currentSetting.textColor = [UIColor blueColor];
_currentSetting.textAlignment = NSTextAlignmentRight;
}
if (cell.tag == 0) {
_currentSetting.text = [NSString stringWithFormat:#"%# mi",[settings.val1 stringValue]];
}
else if(cell.tag == 1)
{
_currentSetting.text = [NSString stringWithFormat:#"%# items",[settings.val2 stringValue]];
}
else if(cell.tag == 2)
{
_currentSetting.text = [NSString stringWithFormat:#"%# items",[settings.val3 stringValue]];
}
[cell.contentView addSubview:_currentSetting];
return cell;
}
I've gone through with NSLog and everything is getting called as it should on reloadData, but the cells aren't changing their labels. Why?
The major problem I can see here is this :
cell.tag = rowcount;
rowcount++;
cell.tag must not be present inside if (cell == nil), you should rather take it out. Instead you shouldn't be checking cell.tag to set currentSettingText at all, you should be using indexPath.row
Also in each viewDidAppear,when you reload The Table, cellForRowAtIndexPath is called, which is incrementing rowCount and hence cell.tag everytime, where are you resetting it?
Problem 1: cellForRowAtIndexPath needs to return a cell, either by getting an existing cell, or by creating one and adding a subview (only when it creates a cell). When we have an existing cell, we can assume it has the subview added, so we just go looking for it. So it will work like this...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellWithIdentifier = #"Cell";
BaseLabel *_currentSetting;
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellWithIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellWithIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// new cell, so add a label
_currentSetting = [[BaseLabel alloc] initWithFrame:CGRectMake(160, 8, 115, 25)];
[_currentSetting setFont:[UIFont systemFontOfSize:14]];
_currentSetting.backgroundColor = [UIColor clearColor];
_currentSetting.textColor = [UIColor blueColor];
_currentSetting.textAlignment = NSTextAlignmentRight;
_currentSetting.tag = 128;
[cell.contentView addSubview:_currentSetting];
} else {
// existing cell so find the label
_currentSetting = (BaseLabel *)[cell viewWithTag:128];
}
Problem 2: Now we have a cell ready to go and a handle to a label subview, how should it be configured? The only sensible way is to look into our model based on the indexPath.
Here's where I can't help much without understanding the model, but the generic idea is this:
// say my model is an array of N arrays, one for each section, I would do this
NSArray *sectionModel = [self.mainModel objectAtIndex:indexPath.section];
id *modelElement = [sectionModel objectAtIndex:indexPath.row];
// now configure the cell based on modelElement
_currentSetting.text = [modelElement someStringAttributeOfMyModel];
return cell;
}
I can overlook the viewWillAppear call in viewDidAppear which is very strange but what is interesting is that you don't use the indexPath parameter in you method at all.
Since this parameter is the only way how to check which cell you are generating, you can get practically any result and the cells can be ordered randomly. A reloadData will just shuffle them again.
As the rowcount only increases, very soon none of your cell.tag == comparisons will evaluate to true.
By the way, the code is adding subviews to already existing cells - that means that after several reloads or some scrolling your cells will have many labels. Why you save the last label added to a cell into an instance variable, change it text and then move it to a different cell... that's a mystery.
Your code doesn't have any sense!
What makes scrolling so choppy on the UITableView? In my mind following code is a culprit. I am having very hard time to replace this logic with something other.
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview]; }
This is what I am doing.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
BGMArticleAbstract *articleAbstract = [self.section.articleAbstracts objectAtIndex:indexPath.row];
[cell.contentView addSubview:[self getHedlineFromArticleAbstract:articleAbstract]];
[cell.contentView addSubview:[self getThumbnailImageFromArticleAbstract:articleAbstract]];
[cell.contentView addSubview:[self getAbstractParaFromArticleAbstract:articleAbstract]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell; }
I am doing, addSubview to contentview because I am creating a dynamic cell height. Is there any way I can make this scroll view work smooth ? Thank you for your help.
You should design your cell as you need it. Add labels and whatever you need to the cell, and then change the content of these already available subviews.
If you need to display an image, add once an UIImageView to the cell and only change the image property of it. Same for text fields and so on.
The way you do it makes the built-in cache useless, because you regenerate all subviews again and again..
To boost the performance even more, you can do the drawing of the cell by yourself.
Apple has a quite nice example project:
http://developer.apple.com/library/ios/#samplecode/AdvancedTableViewCells/Introduction/Intro.html
You are right that the problem is caused by how you return cells. The correct pattern is as follows...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// see if cell contains our image views. A reused cell will, but a new cell won't
UIImageView *imageViewA = (UIImageView *)[cell viewWithTag:32];
UIImageView *imageViewB = (UIImageView *)[cell viewWithTag:33];
UIImageView *imageViewC = (UIImageView *)[cell viewWithTag:34];
if (!imageViewA) {
// the cell must be new, so create it's image views
// you should be able to borrow most of this code from your getHeadline/thumbnail/etc methods.
// the good news is that this relatively expensive code runs only for new
// cells and there are only a few of those - only enough to fill the visible frame
imageViewA = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewA];
imageViewA.tag = 32;
imageViewB = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewB];
imageViewB.tag = 33;
imageViewC = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewC;
imageViewC.tag = 34;
// this too, need only be done upon creation
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// now, whether it's a new cell or a reused cell, we have image views
BGMArticleAbstract *articleAbstract = [self.section.articleAbstracts objectAtIndex:indexPath.row];
// change your methods getHeadline... getThumbnail... etc to answer UIImages
// not UIImageViews, which are setup only for new cells
imageViewA.image = [self getHedlineFromArticleAbstract:articleAbstract]];
imageViewB.image = [self getThumbnailImageFromArticleAbstract:articleAbstract]];
imageViewC.image = [self getAbstractParaFromArticleAbstract:articleAbstract]];
// as a side note, once you get these methods returning images (more like model objects)
// rather than image views (view objects) they might be more appropriately placed
// in the BGMArticleAbstract class rather than the view controller
return cell;
}
I have a TableView that has UISwitches in it. The switches are slightly off the screen. how do I make the display correctly.
Here is my code that displays the switches.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"POICell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if(!cell){
//set the cell text to the
cell.textLabel.text = [self.catNames objectAtIndex:[indexPath row]];
NSString *toggle = [self.toggleArray objectAtIndex:[indexPath row]];
//add switch
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UISwitch *switchView = [[UISwitch alloc] initWithFrame:CGRectZero];
cell.accessoryView = switchView;
if ([toggle isEqualToString: #"OFF"]) {
[switchView setOn:NO animated:NO];
}else{
[switchView setOn:YES animated:NO];
}
[switchView addTarget:self action:#selector(switchChanged: ) forControlEvents:UIControlEventValueChanged];
// Configure the cell...
}
return cell;
}
Rather than adding a UISwitch to the standard cell, I'd create a custom UITableViewCell that contained the text label and the switch.
But after looking at the Apple docs, it seems what you want to achieve is a valid approach.
Table View Programming Guide for iOS
And your code already looks a lot like this SO question: How to create a UITableViewCell with a UISwitch and get the data? So I am not sure why the UISwitch isn't displaying as expected.
I think the position of the default accessoryView can't be moved without a problem, as the TableViewCell will reposizion it in many situations.
So adding it as a subview might be the better idea, instead of trying to "hack" the default behavior.
In a normal situation when working with a UITableView I have the standard code for reusing old cells:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
return cell;
}
I noticed, however, that in the case when I added subviews to the cell that they weren't deleted and that a new view were added every time. I have an example below that demonstrate it perfectly:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
UILabel *label = [[UILabel alloc] init];
label.text = #"HELLO";
label.frame = CGRectMake(arc4random() % 50, -1, 286, 45);
label.backgroundColor = [UIColor clearColor];
// Add views
[cell addSubview:label];
return cell;
}
I need some code that reuses my labels again in the same way the cells are being reused. What should I do?
Thanks
You must only add the subviews if you are making a new cell. If you are dequeuing, the subview is already present and should not be re-created.
Your method should be:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellIdentifier];
UILabel *label;
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
label = [[UILabel alloc] init];
label.tag = 1;
// Add views
[cell addSubview:label];
}
else
{
// Label will already exist, get a pointer to it
label = [cell viewWithTag:1];
}
// Now set properties on the subview that are unique to each cell
label.text = #"HELLO";
label.frame = CGRectMake(arc4random() % 50, -1, 286, 45);
label.backgroundColor = [UIColor clearColor];
return cell;
}
Note how the label is only created when the cell is nil. Otherwise, it is found using the tag.
I need some code that reuses my labels again in the same way the cells
are being reused.
No, you need to understand the table view design better. It should be obvious why the views are being added multiple times – reusing a cell means that you take a previous instance of UITableViewCell that’s no longer needed (thus saving a costly allocation of a new object) and reuse this instance for the new cell. But this previous instance already has the label attached to it, so the number of labels grows.
I would subclass UITableViewCell and put the label creation inside the initialization code for this new class. (Or create a UIView subclass and set it as the cell’s contentView, as suggested in this nice table tutorial by Matt Gallagher.) That’s the proper way to encapsulate the view details and hide them from the table data source.
you can use something like in the else part for if(cell == nil)
for (UIView *sub in [cell.contentView subviews])
{
if([UILabel class] == [sub class])
NSLog(#"%#",[sub class]);
UILabel *label = (UILabel *)sub;
//do label coding ie set text etc.
}
I use lazy initialization of views within my custom table cell class.
It only needs to load views and "addSubview" once.
- (void) lazyInitTitleLabel {
if (_titleLabel != nil) {
return;
}
_titleLabel = [[UILabel alloc] initWithFrame: CGRectMake(10.0f, 10.0f, 200.0f, 30.0f)];
// Cell adds the label as a subview...
[self addSubview: _titleLabel];
}
The only thing you need to be careful about is resetting any content that views display like text in your labels and images in your image views. If you don't old content may get reused along with the recycled table cells.
Good luck!