Reputation: 1043
My code downloads images asynchronously using InternetImage in the tableview: cellForRowAtIndexPath:
method by initializing a IntentImage with initWithURL and calling downloadImage. This code runs perfectly after scrolling down to a new cell UITableViewCell in the UITableView, but not before, even though the URL is correct in both cases. None of InternetImage's NSURLConnection delegate methods are called to notify me about success or failure of the connection, as they should be. Calling reloadData:
, setNeedsDisplay:
, and setNeedsLayout:
do nothing since the image fails to download.
Here is the code from my subclass of UiTableViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Object *object = (Object *)[self.array objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"] autorelease];
}
cell.textLabel.text = object.attribute;
if (object.image == nil && object != nil) {
NSString *URLString = [[MyAppDelegate imageURLFromLink:(object.link) withExtension:@".jpg"]absoluteString];
InternetImage *asynchImage = [[InternetImage alloc] initWithUrl:URLString object:object];
[asynchImage downloadImage:self];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
if (object.image != nil && object.thumbnailImage == nil) {
UIImage *image= (UIImage *) object.image;
UIImage *thumbnail = [image _imageScaledToSize:CGSizeMake(75.0, 50.0) interpolationQuality:20];
object.thumbnailImage = thumbnail;
}
cell.imageView.image = object.thumbnailImage;
return cell;
}
Upvotes: 2
Views: 5821
Reputation: 1605
well nice approach? First thing check where you allocating imageview? 2nd if your image is fixed means specific than just whole code in
if(cell == nil)
{
}
side that condition. I face something same problem and solved like this. let me know if this works or not.
Upvotes: 0
Reputation: 7530
I actually spent some time looking at the code.
In InternetImage
I noticed that there is this function.
What you are going to have to do is get this function to inform your TableView
to "refresh". You could do this by passing a delegate function to your class that is called later, but now things get real tricky. Because when you leave the view you will need to set the delegate function to "nil" for every image you are trying to download.
-(void) internetImageReady:(InternetImage*)downloadedImage
{
// The image has been downloaded. Put the image into the UIImageView
[imageView setImage:downloadedImage.Image];
}
Do post the answer here when you have got it, I am interested to see if this solved it or not.
Upvotes: 0
Reputation: 199
I don't have an answer, but I can tell you that I am seeing the same sort of problem.
I am using a slightly modified version of Mark Johnson's AsyncImageView class. The asynchronous requests for the images don't cause any delegates to be called, till I scroll the tableview. As soon as I scroll a cell out of view and back, the call fires again and gets the data properly.
I read elsewhere that NSURLConnection depends on the run loop to be in a certain mode - I don't know if that figures in a situation where a URLConnection request is started in tableView:cellForIndexPath.
How would one go about debugging NSURLConnection internals?
[UPDATE]
Okay, so I solved my particular instance of this problem by starting the connection on the main thread explicitly. I put the connection setup code in a different selector, and invoked that via performSelectorOnMainThread, and everything was good again.
You should try logging the current runloop mode in the selector that starts the connection - I discovered that is was not the default runloop mode, which NSUrlConnection needs.
Upvotes: 0
Reputation: 43452
Okay, first off, what you have posted here is not what you are using, or you have made modifications to InternetImage you have not mentioned, since InternetImage does not respond to initWithUrl:object:. You also have not included the code to the delegate method internetImageReady:. I understand that it is not being called when something goes wrong, but it is called once you start scrolling, and having the contrast of what happens in the correct case will help people diagnose your issue.
Also, it is not entirely clear from your description what happens when you scroll. Clearly subsequent calls work, but do all the previously attempted connections suddenly start at that point, or are they completely lost?
As for what is going on, well for the reasons above I don't believe anyone can do more than give you some guesses. If I had to wager guess that if what I said in the last paragraph is happening then your issue is that (for whatever reason) the main threads runloop is not processing the NSURLConnections events until something primes the pump. This could be for a number of reasons, for instance the runloop being in the wrong mode. You could try running the NSRunLoop manually, or changing to InternetImage to explicitly set runloop, or manually starting the download.
Two quick asides
Upvotes: 0
Reputation: 10125
It indeed seems that NSUrlConnections don't retrieve data as long as the table is scrolling. Once scrolling stops, it instantly continues to retrieve data. This behavior can however be seen in any iPhone application (images aren't loaded as long as the table is scrolling) and in the end, I decided to live with that in my application, too. I think, Apple did this on purpose. Maybe to prevent the device from hammering the server with tons of image requests if the user is scrolling very fast; or maybe even to keep scrolling smooth for the user at any time.
It'd however be nice if it would at least work while scrolling slowly and only block downloading while scrolling fast.
Upvotes: 1
Reputation: 2122
A solution that worked for me is to move the responsibility for downloading to Object, and moving responsibility for handling the image refresh to the table cell itself. If the Object is visible on the screen and has a corresponding table cell, the image will refresh immediately once it's downloaded.
This approach would require the following changes:
Here's a sketch of how this could work:
// in the UITableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Object *object = (Object *)[self.array objectAtIndex:indexPath.row];
ObjectTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"objectcell"];
if (nil == cell) {
cell = [[[ObjectTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"objectcell"] autorelease];
}
cell.object = object;
return cell;
}
// in Object
- (void) downloadImageIfNeeded {
if (nil == image && nil == internetImage) {
NSString *URLString = [[MyAppDelegate imageURLFromLink:(object.link) withExtension:@".jpg"]absoluteString];
internetImage = [[InternetImage alloc] initWithUrl:URLString object:object];
[internetImage downloadImage:self];
}
}
- (void) internetImageReady:(InternetImage*)downloadedImage {
self.image = downloadedImage.image;
self.thumbnailImage = [image _imageScaledToSize:CGSizeMake(75.0, 50.0) interpolationQuality:20];
// notify the delegate
[imageDelegate imageChanged: self];
}
// in ObjectTableViewCell.h
@property (nonatomic, retain) Object *object;
// in ObjectTableViewCell.m
- (Object *) object {
return object;
}
- (void) setObject: (Object *) obj {
if (obj != object) {
object.imageDelegate = nil;
[object release];
object = nil;
if (nil != object) {
object = [obj retain];
object.imageDelegate = self;
[object downloadImageIfNeeded];
self.textLabel.text = object.attribute;
self.imageView.image = object.thumbnailImage;
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
- (void) imageChanged: (Object *) o {
self.imageView.image = object.thumbnailImage;
}
Upvotes: 2
Reputation: 5100
If you are asking why your images are not downloaded in the background once your table is loaded, it is because tableView:cellForRowAtIndexPath:
is only invoked with indexPaths
of on-screen cells.
If you want all your images to be downloaded at once rather than when a cell rolls on-screen, write a method which loops through your array
property and insantiates InternetImage
objects there. A likely place for this could be in viewDidLoad
.
Then implement tableView:willDisplayCell:forRowAtIndexPath:
in your UITableView delegate and make it responsible for assigning the correct InternetImage
object to each cell's imageView
property.
Upvotes: 1