I am using the same code in two of my view controllers (they are implementing the same class what changes is the url they download) and in one occassion the image is displayed correclty while in the other I do see an empty cell.
Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier=#"MyCell";
//this is the identifier of the custom cell
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
tableView.backgroundColor=[UIColor clearColor];
tableView.opaque=NO;
tableView.backgroundView=nil;
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSLog(#"Image url is:%#",[images_url objectAtIndex:indexPath.row]);
NSURL *url_image=[NSURL URLWithString:[images_url objectAtIndex:indexPath.row]];
cell.myimage.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:url_image]];
return cell;
}
As i told you I have 2 view controllers implementing the same class. In the view did load the url is set depending on the value of a flag. If I open controller A, I see no image, if I open view B i can see the image. Both of the urls are correct as I can check it with the NSLog I have inserted.
What might be the problem?
Unfortunately calling "NSData dataWithContentsOfURL" is a blocking call. Execution of your program will stop until iOS is able to fetch all the data from the server or fails trying. This may often be "fast" if you're on LTE or WiFi; but can potentially take a LONG time.
Meanwhile, you're on the "main thread" in your app - so your app will appear to freeze-up, and the system's watchdog timer may kill your app. If anyone besides you will use this ap, you absolutely need to populate your tableview cell's image with local data that's retrieved immediately or use asynchronous methods.
Just google for "lazy load UIImage". This SO question has some good tips on the subject:
lazy-load-images-in-uitableview
Additionally, you should move these lines to some setup code. You don't need to perform them every time to update a cell:
tableView.backgroundColor=[UIColor clearColor];
tableView.opaque=NO;
tableView.backgroundView=nil;
Best of luck!
Related
I have a class that defines a custom contact cell called ContactItemCell and a xib file that lays out that cell. When I create the table view it opens up and six of these ContactItemCell classes are created. When I navigate back they aren't deallocated, and when I open the tableview again another 6 are created. Here's the tableview code:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Contact* contact = [self contactAtIndexPath:indexPath];
static NSString *cellID = #"ContactItemWithTagsForBothCell";
ContactItemCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil)
{
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"ContactItemWithTagsForBothCell" owner:nil options:nil];
if (arr.count <= 0)
{
NSLog(#"couldnt find cell with ID: %#", cellID);
return nil;
}
cell = [arr firstObject];
}
[cell configureCellForContact:contact];
return cell;
}
The fileowner in the xib file is just set to be blank which I'm assuming means NSObject. I've tried looking for a strong reference cycle to see if the class is kept alive by pointers but I haven't seen anything after days of investigation. I'm really at my limit and I'm not sure what else I can do, I've been using instruments too and that's how I've figured out that they're being created 6 at a time but I can't find out what's pointing to them. Why is this happening? Am I doing something wrong with the table view? If I'm not and you think it's a strong reference cycle then how can I find every object that points to this ContactItemCell? Thanks in advance!
If anyone was curious I used the memory debugger and found that there was a strong reference cycle with a pod I was using.
I have an app with some posts placed at an UITableView. Each post have a favorite button and I need to change its image when the user clicks on it. Here are the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"identifier";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if(cell == nil){
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"myNib" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSDictionary *post = [posts objectAtIndex:indexPath.row];
[cell.likeButton addTarget:self
action:#selector(clickedOnLike:)
forControlEvents:UIControlEventTouchUpInside];
cell.likeButton.tag = indexPath.row;
And on click handler:
-(IBAction)clickedOnLike:(id)sender
{
int tag = buttonSender.tag;
NSDictionary *post = [posts objectAtIndex:tag];
if( ![self likedAlready:post] ){
//set liked on this view...
//update view
NSLog(#"button: %#",buttonSender);
[sender setImage:[UIImage imageNamed:#"newImage.png"] forState:UIControlStateNormal];
//send like to server...
}
}
At this point, everything is going alright. The problem is, after click a button, update the view and scroll to other cells, the other buttons views I never clicked are updated too. For example, when I click a button at indexPath 1, the ones at 5 and 9 change their images automatically. This is a mistery to me, since I call the action sender directly and update only it. Thanks for help.
The reusable cell do it: dequeueReusableCellWithIdentifier.
U need to deal manually with things like this.
U can invoke manually to your clickedOnLike: method from the CellForRowAtIndexPath:
Try to maintain inside your modal (from your MVC development architecture), and access there from the CellForRowAtIndexPath:
That will solve your problem :)
Good Luck!
In your cellForRowAtIndexPath, set the default image of your button, then change it if its a favorite. The rows tend to get cached so always set your data to what you expect it to be.
As others have said, the problem is because the view is recycled for everyline, and in the recycled copy the button may have had its picture changed to the new one.
you need to keep somewhere in memory the status of the post, and on the cellForRowAtIndexpath set the correct image for the button every time the row is rendered.
Alternatively, if you have a very small number of posts, just eliminate recycling by doing
static NSString *identifier = nil;
I have use xib set the interface,when using it dosen't smooth,why?
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CustomerCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
NSArray * nib = [[NSBundle mainBundle] loadNibNamed:#"MSNeedCheckCell" owner:self options:nil] ;
cell = [nib objectAtIndex:0];
}
//填充cell的内容
}
Try calling the following method from your init method for the class:
[self.tableView registerNib:[UINib nibWithNibName:#"MSNeedCheckCell" bundle:nil]
forCellReuseIdentifier:#"CustomerCell"];
And then, you can remove your entire if (!cell) { section of code since it will always return a cell. This only loads the nib once instead of every time that you need a cell so will be much faster.
Is this, quite literally, all there is to it? Or are you doing anything else (e.g. in subclassed cell, or other code that you may have omitted for the sake of brevity).
The most common source of lack of smoothness (and by smoothness, I assume you're talking about a stuttering in the UI as you scroll in your tableview) would be if you're doing anything with images in the foreground queue.
The WWDC 2012 - 211 - Building Concurrent User Interfaces offers a wonderful, practical illustration of how to use Instruments to identify the sources of performance bottlenecks. Clearly they're focusing on a very specific design consideration there, but the Instruments tutorial is very useful. It would be good to make sure that this particular line of code is really the problem before you lose too much sleep over it.
I have been testing the application on the device (iOS 5) while using Instruments and I found a couple of memory leaks.
This is the part of the code I'm being redirected to from Instruments (see the arrow for exact line):
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CeldaUltimasFotosViewCell *cell =
(CeldaUltimasFotosViewCell *) [self.tableView
dequeueReusableCellWithIdentifier:#"CeldaUltimasFotosViewCell"];
if (cell == nil) {
- - - - > NSArray *topLevelObjects =
[[NSBundle mainBundle]
loadNibNamed:#"CeldaUltimasFotosViewCell"
owner:nil options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
// Configure the cell...
[[cell titulo] setFont:fuente_titulo];
...
return cell;
}
As you can see, I have a custom cell which is loaded from a NIB file. There are three files for the cell (customCell.m, customCell.h, customCell.xib). The thing is that I don't know if I have to release something in the cell controller (which is now empty, no methods), since this is iOS 5 with ARC.
check out my answer here:
How can I recycle UITableViewCell objects created from a XIB?
you don't even need to use loadNibNamed any more on iOS5
Take a look at the Table View Programming and how to load cells from NIB (XIB) files.
https://developer.apple.com/library/ios/#documentation/userexperience/conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7-SW1
The first thing weird is that you are storing the cell in a local variable. You should be wiring the custom cell up to a property in the class and all you call in your code is:
[[NSBundle mainBundle] loadNibNamed:#"CeldaUltimasFotosViewCell" owner:self options:nil];
Follow the code from Loading Custom Table-View Cells From Nib Files and you can't go wrong.
Whenever I scroll my tableview it is very laggy. I think it has to do with how I am loading up my cells. I use UINib (5.0+) whenever I can while still providing backwards compatibility. Then I load my custom cell's labels and images with items from a NSDictionary from a NSArray which is loaded from NSUserDefaults in the ViewDidLoad.
Is there any way to improve the efficiency of this cellForRowAtIndexPath?
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = (CustomCell *)[aTableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
if ([self labelCellNib]) {
[[self labelCellNib] instantiateWithOwner:self options:nil];
} else {
[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
}
cell = [self CustomTableCell];
[self setCustomTableCell:nil];
}
NSDictionary *dictionary = [myArray objectAtIndex:indexPath.row];
NSData *data = [dictionary objectForKey:#"OCRImage"];
cell.previewPicture.image = [self roundCorneredImage:[UIImage imageWithData:data] radius:60];
cell.titleLabel.text = [dictionary objectForKey:#"Title"];
cell.titleLabel.delegate = self;
cell.dateLabel.text = [dictionary objectForKey:#"Date"];
if (indexPath.row%2) {
cell.backgroundImage.image = firstImage;
}
else {
cell.backgroundImage.image = secondImage;
}
return cell;
}
Edit:
- (UIImage*)roundCorneredImage: (UIImage*)orig radius:(CGFloat) r {
UIGraphicsBeginImageContextWithOptions(orig.size, NO, 0);
[[UIBezierPath bezierPathWithRoundedRect:(CGRect){CGPointZero, orig.size}
cornerRadius:r] addClip];
[orig drawInRect:(CGRect){CGPointZero, orig.size}];
UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Edit2: These are the lines that are causing the lag:
NSData *data = [dictionary objectForKey:#"OCRImage"];
cell.previewPicture.image = [self roundCorneredImage:[UIImage imageWithData:data] radius:60];
As #Till said in a comment, you should launch your app in Instruments (Product -> Profile in Xcode), and select the CPU -> Time Profiler instrument.
Then, scroll around over the place for a few seconds, then hit the Record toolbar icon in instruments to close your app. You will be able to see the scrolling section because CPU usage will probably be pinned at 100% (unless it's slow because of network activity problem).
Click on the timeline after the start of the high CPU activity area, and click the "start inspection range" toolbar button, then click before the end of the high CPU activity area and click the "stop inspection range" toolbar button.
You can now drill down into the call tree view at the bottom of the window to figure out exactly where all your CPU usage is. In my experience it's usually easier to find the problem if you turn off "invert call tree" option on the left.
Performance bugs can be very hard to find, and sometimes a line of code that is obviously slow actually isn't causing any problems at all. The only way to fix performance issues without wasting time is to use Instruments.
Make sure that you've set the reuse identifier for your cell to the same thing that you've specified in your code, i.e. #"Cell". If they don't match, then you won't be reusing cells properly, and probably spending a lot more time creating cells than necessary.
If you are properly recycling cells, then you should take a look at the code after the if (cell == nil) {...} block. You'll be skipping that entire block once the table has created enough cells to fill the screen (and maybe one or two more), so most of the time attributable to this method while scrolling will be due to the following code. It'd be interesting to know what myArray is, and if it's actually an array, what the objectForKey: method does. Nothing else there looks like it should take a long time, but the best way to find out where the cycles are going is to profile your code in Instruments.
Some of my notes after looking at your code:
Is roundCorneredImage:radius: caching the result? If not, executing CG calls for every cell would surely present a bottleneck. Updated: Use instruments to be sure, but it might be faster (memory allowing) to store the processed UIImage in a collection so that you can pull it out again the next time that method is called with the same parameters.
All of your UIImages could be declared elsewhere and then presented in this method. Your current code instantiates a new UIImage for each cell which can also bottleneck your scrolling. Updated: Since Image1.png and Image2.png are basically static, you could declare them in your interface or as a static ivar and then just assign them to the background image rather than instantiating UIImage each time.
It may be faster to subclass UITableViewCell and instantiate that instead of reaching into UINib. Also, you'd then be able to separate your layout/data logic from the delegate method. Here's a gist of what I did in my UITableViewCell subclass. Basically, I store the entity with the cell and the cell knows about it's labels and such. This keeps the cell layout logic out of my data source code.
It looks like you're using an NSDictionary as your data source. If you have a lot of objects in that dictionary, it may be considerable faster to use CoreData and an NSFetchedResultsController. Here's a good post on the matter. Updated: Ok, that shouldn't be an issue.
-
Edit
So if you removed all of this:
NSDictionary *dictionary = [myArray objectForKey:#"OCRImage"];
cell.previewPicture.image = [self roundCorneredImage:[UIImage imageWithData:data] radius:60];
if (indexPath.row%2) {
cell.backgroundImage.image = firstImage;
}
else {
cell.backgroundImage.image = secondImage;
}
and it still lags, let's look at your constructors...what do these lines do?
cell = [self CustomTableCell];
[self setCustomTableCell:nil];
Also, you're not using any transparent images or anything in your table cell are you? Those have been known to cause drawing lag...
-
Edit #2
If you strip down to this, what happens?
CustomCell *cell = (CustomCell *)[aTableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
if ([self labelCellNib]) {
[[self labelCellNib] instantiateWithOwner:self options:nil];
} else {
[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
}
cell = [self CustomTableCell];
[self setCustomTableCell:nil];
}
cell.titleLabel.text = [dictionary objectForKey:#"Title"];