I was debugging an issue with a view controller recently and noticed that each time I drag the view up or down it will repaint the entire contents of my UITableView (as it calls the cellForRowAtIndexPath method each time). Is it possible to use an in memory datasource or add another delegate to my view controller so it won't repaint each time?
I'm not modifying anything inside the cells when the user interacts with it so my data source would be static after the initial "viewDidLoad" is called.
- (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] autorelease];
}
if ([self.hats count] > 0) {
//do some complex stuff in here that hurts each time we re-draw this ...
}
return cell;
}
Thank you in advance
so // complex stuff means adding UIViews.
I make an example for an UIImageView. Since you don't show the complex stuff you have to adopt it on your own.
your code looks like this:
- (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] autorelease];
}
if ([self.hats count] > 0) {
UIImageView *imageView = [[UIImageView....];
[cell.contentView addSubView:imageView];
[imageView setImage:foo];
[imageView release];
}
return cell;
}
refactor your code that it looks like this:
- (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] autorelease];
UIImageView *imageView = [[UIImageView....];
[cell.contentView addSubView:imageView];
imageView.tag = 42;
[imageView release];
}
if ([self.hats count] > 0) {
UIImageView *imageView = [cell viewWithTag:42];
[imageView setImage:foo];
}
return cell;
}
et voila, your tableview is responsive. Because you create the subviews exactly one time for each cell. And when the cell isn't used anymore and goes into the reuse bin the subviews stay with it.
And if you need 4 imageViews in one cell and 8 in another, you add 8 imageviews when you create the cell and give them a frame of CGRectZero and of course a different tag for each view.
If you need them you show them, if you don't need them you set the image to nil and the frame to zero.
Related
As a Xcode beginner, I want to display a thumbnail in the table cell in my app. For now, I have this code which parses JSON data and posts it in the title and subtitle of the cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MainCell"];
}
cell.textLabel.text = [[news objectAtIndex:indexPath.row] objectForKey:#"receta"];
cell.detailTextLabel.text = [[news objectAtIndex:indexPath.row] objectForKey:#"koha"];
return cell;
}
How can I show a thumbnail in the right of the cell?
Thanks.
If you don't want to make a custom cell, you can create a UIImageView with your desired image and set it as the cell's accessoryView, which will show it on the right edge of the cell. You also need to ensure that the height of the cell is tall enough to fit the image's height.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100;
}
- (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];
}
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"some-image-name"]];
return cell;
}
You can return a constant (I chose 100) if the image will always be a fixed size, or you can check the UIImage's size and return something like size.height + 10 for some extra padding.
I have a NSTableViewCell which has a ImageView within the cell.
What is needed to control the position of the image within the cell using code?
If you are using subclass of NSTextFieldCell(ImageAndTextCell). you can control positon of image in
- (NSRect)imageRectForBounds:(NSRect)cellFrame method.
You can create custom cell...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ImageOnRightCell";
UIImageView *photo;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
photo = [[[UIImageView alloc] initWithFrame:CGRectMake(225.0, 0.0, 80.0, 45.0)]];
photo.tag = PHOTO_TAG;
photo.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:photo];
} else {
photo = (UIImageView *)[cell.contentView viewWithTag:PHOTO_TAG];
}
return cell;
}
This will look like it....
or u can read this link....
http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/tableview_iphone/TableViewCells/TableViewCells.html
instead of default TableViewCell, create your own custom tableViewCell and position it according to your need.
I need to display each webview in each of the tableviewcell in my Uitableview.When using the below code,when there are 2 elements,the first cell is empty but second cell is correct.hrs and hrsHtml contains all values,the problem is only the last data is displaying in their appropriate cell in tableview.Other cells are just blank.Also is total cells is 2,first we can only been able to see the 2nd cell only,but after scrolling tableview reloads and 2nd cell disappears and 1st cell gets displayed.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [brId count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil) {
cell = [[ UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell.contentView addSubview:hrs];
hrsHtml = [NSString stringWithFormat:#" <font size=\"2\" face=\"Arial\">%# </font>",[html objectAtIndex:indexPath.row]];
[hrs loadHTMLString:hrsHtml baseURL:nil];
return cell;
}
Screenshot when tableview appears,Only webview in cell 2
Screenshot when tableview scrolls,Only webview in cell 1,cell 2 disappears
Since hrs and hrsHtml are single objects, you only have one of each even though you have multiple cells. If you modify hrs, it will change for all cells since they seem to be sharing it. (Unless you have other code somewhere that changes the objects that those variables point to.)
Another odd thing is that you use a brId array to determine the number of rows and a html array to get the row content. If those ever get out of synchronization, you will have problems.
Also, you should only add a subview to a cell when you create a new one.
- (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:nil] ;
}
hrs = [[UIWebView alloc] initWithFrame:CGRectMake(10,0,320,84)];
hrs.userInteractionEnabled = YES;
hrs.backgroundColor = [UIColor clearColor];
hrsHtml = [NSString stringWithFormat:#" <font size=\"2\" face=\"Arial\">%# </font>",[html objectAtIndex:indexPath.row]];
[hrs loadHTMLString:hrsHtml baseURL:nil];
[cell.contentView addSubview:hrs];
hrsHtml = nil;
return cell;
}
Now the webview loads correctly in every tableviewcell.
i think you have problem in initializing cell try below code
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
}
Okay, I am having another UITableView problem. For some reason the indexPath.row is all jumbled up. When I comment out the if statement that sets up the cell, everything works fine. The NSLogs tell me that they are loading in order, but all the cells are out of order.
It also seems as if they repeat; I only see 8 cells, and they repeat over and over.
Here's my code:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
NSLog(#"row: %d",indexPath.row);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor clearColor];
cell.contentView.backgroundColor = [UIColor clearColor];
// Add subviews like this:
// [[cell contentView] addSubview:objectName];
// And I get the row number like this: indexPath.row when getting objects from the array
}
return cell;
}
To use your code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
NSLog(#"row: %d",indexPath.row);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor clearColor];
cell.contentView.backgroundColor = [UIColor clearColor];
// Add subviews like this:
// [[cell contentView] addSubview:objectName];
}
### Move this here ###
// And I get the row number like this: indexPath.row when getting objects from the array
return cell;
}
" I only see 8 cells, and they repeat over and over." Correct.
What your missing is that that is how it is supposed to work. That's why only if the cell is nil are you alloc & init'ing a new cell. So you alloc and init and set the colors and add subviews in the if statement. Then after the if(cell==nil) you know you have a valid cell to populate with some data according to the indexPath variable passed in.
The problem is that now you are setting up the cell when it is nil and assigning all of the displayed data according to the indexPath passed in. The problem is cell is not nil the second time it's used so the data is never changed.
To address your speed comment further, I'll use an old fallback example.
- (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];
UILabel *hugeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height)];
hugeLabel.tag = 300;
[cell addSubview:hugeLabel];
}
[(UILabel *)[cell viewWithTag:300] setText:[arrayOfStrings objectAtIndex:indexPath.row]];
return cell;
}
If you look at the sample above, you'll see that we add a UILabel to the cell setting it's tag to 300. Then after the if statement we will have either a brand new cell or a reused cell with text already in the label. No matter either way we simply change the text of the existing label to whatever it should be considering the row. In this way we avoid creating views over and over.
If you are dead-set on caching your UITableViewCells you could do so like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row < _cells.count){
return [_cells objectAtIndex:indexPath.row]; // _cells is an NSMutableArray setup in viewDidLoad
}
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#""];
cell.textLabel.text = [source objectAtIndex:indexPath.row]; // source is an NSArray of NSStrings I set up in viewDidLoad
[_cells addObject:cell];
return cell;
}
Note When running this on device don't be surprised when in the console you see Received memory warning What's efficient & what's easy are often not the same.
The way you have it set up now, cell.selectionStyle, cell.backgroundColor, and cell.contentView.backgrounColor, etc., only get set when if (cell == nil) is true. You need to move that code outside the if statement block, so that it gets called both when dequeueReusableCellWithIdentifier: produces a cell and when it has no cells in inventory and produces nothing (i.e., nil).
when I used layoutSubviews method of UItableViewcell with category, just like the code below
#implementation UITableViewCell (forimage)
- (void)layoutSubviews{
[super layoutSubviews];
self.imageView.frame = CGRectMake(0, 0, 50, 50);
}
#end
when I used the code below to draw the cells , the textLabel was disappeared ,anyone know why it be that~, and does that mean if I use layoutSubviews,I must write all the subviews what I need in the method?
- (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] autorelease];
}
RadioInfo *radioinfo = [radios objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#",radioinfo._name];
if(!radioinfo._logo){
if(self.tableView.dragging == NO && self.tableView.decelerating == NO){
[self startPicDownload:radioinfo forIndexPath:indexPath];
}
cell.imageView.image = [UIImage imageNamed:#"1.jpg"];
}
else {
cell.imageView.image = radioinfo._logo;
}
return cell;
}
What you want to do is add to the behaviour of UITableViewCell's layoutSubviews method, not replace it.
To properly add to the behaviour, subclass the cell and perform your own layout, as you have above but add a [super layoutSubviews] right at the top of your method to ensure that the cell's own basic layout is performed first.