Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

youPhone

macrumors member
Sep 8, 2008
41
0
Also, you need to set up all of the instance variables in ImageLoader in the
loadImageFromURL:withCallbackTarget:withCallbackSelector: method, because imageURL, callbackTarget, and callbackSelector are all undefined when sendImageBack: is being called
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
I made all the modifications you suggested. but i receive an error. "terminating due to uncaught exception"

in my custom table cell class i have this function which sets the custom data to the cell:

Code:
-(void) setData:(NSDictionary *) dict
{
	self.titleLabel.text = [ dict objectForKey:@"title" ];
	self.dateLabel.text = [ dict objectForKey:@"pubDate" ];
	self.URL = [ dict objectForKey:@"link" ];
	NSMutableString *temp =  [[NSMutableString alloc] initWithString:@"http://www.wai.de/emailsJPG/114x120/"];
	[temp appendString:[dict objectForKey:@"description"]];
	self.imgSrc = temp;

	NSURL *imgUrl = [[NSURL alloc] init];
	imgUrl = [NSURL URLWithString:@"http://www.wai.de/emailsJPG/114x120/372373.jpg"];
	
	imageLoader = [[ImageLoader alloc] init];
	[imageLoader loadImageFromURL:imgUrl withCallbackTarget:self.img withCallbackSelector:@selector(setupImage:)];
	
}

this is the place where i need the image loader ..also in this class(after reading your other posts) i added the setupImage method:

Code:
- (void) setupImage:(UIImage *) anImage
{
	[img setImage:anImage];
}

my identifierCounter in the ImageLoader class i initialized it with the int version of the url like in your previous post...

identifierCounter = [self urlToHashString: anImageURL];
is it ok?

thank you for all your help
 

youPhone

macrumors member
Sep 8, 2008
41
0
The identifier isn't a big deal. I don't even really use it other than to log how many different operations I've made at this point.

I need more information on that error you're receiving. Does it say anything else useful? Could you post the error?

You may need to start using some breakpoints or NSLog() to find out where your program is getting to and what is causing the exception if the error doesn't show anything useful.
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
this is what appears in the debugger:

Code:
*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0
2008-10-17 18:00:25.257 WAIRSS1.2[4783:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0'
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
Also, you need to set up all of the instance variables in ImageLoader in the
loadImageFromURL:withCallbackTarget:withCallbackSelector: method, because imageURL, callbackTarget, and callbackSelector are all undefined when sendImageBack: is being called

this is my current ImageLoader class:

Code:
@implementation ImageLoader
@synthesize callbackTarget;
@synthesize callbackSelector;
@synthesize imageURL;

static NSOperationQueue *imageLoadingQueue;
static NSMutableDictionary *imageCache;
static NSInteger  identifierCounter;

- (void)loadImageFromURL:(NSURL *)anImageURL withCallbackTarget:(id)target withCallbackSelector:(SEL) selector
{
	callbackTarget = target;
	callbackSelector = selector;
	imageURL = anImageURL;

	NSLog(@"ImageLoader.m anImageURL = %@", imageURL);
	
	identifierCounter = [self urlToHashString: imageURL];
	DataLoaderOperation *op = [DataLoaderOperation queueDataLoadWithURL:imageURL withIdentifier:identifierCounter withCallbackTarget:self withCallbackSelector:@selector(sendImageBack:)];
	[imageLoadingQueue addOperation:op];
}

+ (NSString*) urlToHashString:(NSURL*)aURL
{
	return [NSString stringWithFormat:@"%U",[[aURL absoluteString] hash]];
}

- (NSData*) dataForURL:(NSURL*)aURL
{
	NSLog(@"ImageLoader.m dataForURL %@", aURL);
	return [imageCache valueForKey:[ImageLoader urlToHashString:aURL]];
}

- (void) addImageDataToCache:(NSData*)aDatum forURL:(NSURL*)aURL
{
	NSLog(@"ImageLoader.m am adaugat in cache imaginea pentru url= %@", aURL);
	[imageCache setValue:aDatum forKey:[ImageLoader urlToHashString:aURL]];
}

- (void) sendImageBack:(NSData*)data
{
	if (!data) {
		return;
	}
	
	[data retain];
	[self addImageDataToCache:data forURL:imageURL];
	
	UIImage *thisImage = [UIImage imageWithData:data];
	if ([callbackTarget respondsToSelector:callbackSelector]) {
		[callbackTarget performSelector:callbackSelector withObject:thisImage];
	}
}
 

youPhone

macrumors member
Sep 8, 2008
41
0
this is what appears in the debugger:

Code:
*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0
2008-10-17 18:00:25.257 WAIRSS1.2[4783:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0'

Have you added the urlToHashString function to your .h file?
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
this is my ImageLoader.h

Code:
@interface ImageLoader: NSObject
{
	id         callbackTarget;
	SEL        callbackSelector;
	NSURL     *imageURL;
}

@property (assign)            id         callbackTarget;
@property (assign)            SEL        callbackSelector;
@property (nonatomic, retain) NSURL     *imageURL;

- (void)loadImageFromURL:(NSURL *)anImageURL withCallbackTarget:(id)target withCallbackSelector:(SEL) selector;
- (void) sendImageBack:(NSData *) data;
- (void) addImageDataToCache:(NSData *)aData forURL:(NSURL *) aImageURL;
+ (NSString*) urlToHashString:(NSURL*)aURL;
- (NSData*) dataForURL:(NSURL*)aURL;
 

youPhone

macrumors member
Sep 8, 2008
41
0
change the line:
identifierCounter = [self urlToHashString: imageURL];

You can start with just making it
identifierCounter = 0;

If that doesn't work, you need to identify the line that is throwing that error by putting NSLog()'s before/after all of them or use breakpoints.
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
change the line:
identifierCounter = [self urlToHashString: imageURL];

You can start with just making it
identifierCounter = 0;

If that doesn't work, you need to identify the line that is throwing that error by putting NSLog()'s before/after all of them or use breakpoints.

after i made identifierCounter = 0; no error appeared but i added NSLog(@"send image back"); to test if the function sendImageBack is called
and the line doesn't appear in the console...

Code:
- (void) sendImageBack:(NSData*)data
{
	if (!data) {
		return;
	}
	NSLog(@"send image back"); //added this to test if the function is called
	[data retain];
	[self addImageDataToCache:data forURL:imageURL];
	
	UIImage *thisImage = [UIImage imageWithData:data];
	if ([callbackTarget respondsToSelector:callbackSelector]) {
		[callbackTarget performSelector:callbackSelector withObject:thisImage];
	}
}
 

youPhone

macrumors member
Sep 8, 2008
41
0
So put NSLog()'s in all of your functions and see what's getting hung up. Seems like standard debugging at this point I would say.
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
So put NSLog()'s in all of your functions and see what's getting hung up. Seems like standard debugging at this point I would say.

I've done that..the only NSLog() that appears is the one that you put originally in the queueDataLoadWithURL:(NSURL*)anImageURL withIdentifier:(NSInteger)anIdentifier withCallbackTarget:(id)aTarget withCallbackSelector:(SEL)aSelector more exactly NSLog(@"Made an NSOperation withId:(%d)", anIdentifier);

the rest of them do not appear..and this is really wierd..
 

youPhone

macrumors member
Sep 8, 2008
41
0
Alright, I may see the problem.

Here's a tip when debugging, you can do
Code:
if (someObject == nil) {
NSLog(@"someObject exists");
}

You can also do:
Code:
NSLog(@"someObject's memory address: <%U>", someObject);

Your imageCache object has not been initialized anywhere. So if you do:
Code:
NSLog(@"About to add an op to imageLoadingQueue <%U>", imageLoadingQueue);
[imageLoadingQueue addOperation:op];

You'll see in the log that imageLoadingQueue will show up as nil (NULL or 0)


So now add an init function in ImageLoader:
Code:
- (id) init
{
	if (self = [super init]) {
		if (imageCache == nil) {
			identifierCounter = 0;
			
			imageLoadingQueue = [[NSOperationQueue alloc] init];
			[imageLoadingQueue setMaxConcurrentOperationCount:2];
		}
	}
	return self;
}

And when you run it again, your imageLoadingCache will have an address in the log line.
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
Alright, I may see the problem.

Here's a tip when debugging, you can do
Code:
if (someObject == nil) {
NSLog(@"someObject exists");
}

You can also do:
Code:
NSLog(@"someObject's memory address: <%U>", someObject);

Your imageCache object has not been initialized anywhere. So if you do:
Code:
NSLog(@"About to add an op to imageLoadingQueue <%U>", imageLoadingQueue);
[imageLoadingQueue addOperation:op];

You'll see in the log that imageLoadingQueue will show up as nil (NULL or 0)


So now add an init function in ImageLoader:
Code:
- (id) init
{
	if (self = [super init]) {
		if (imageCache == nil) {
			identifierCounter = 0;
			
			imageLoadingQueue = [[NSOperationQueue alloc] init];
			[imageLoadingQueue setMaxConcurrentOperationCount:2];
		}
	}
	return self;
}

And when you run it again, your imageLoadingCache will have an address in the log line.

Hello Iphone,
i made what you said..now the NSLogs that i put in all the functions in ImageLoader, DataLoaderOperation and in my FeedTableViewCell classes appear..but the images aren't changed...:confused:

i don't know what tot do next... :(
 

youPhone

macrumors member
Sep 8, 2008
41
0
Is what is being sent back an image? Are you sending the image to a UIImageView and the setImage: to the method?


Wherever you're calling loadImageFromURL:withCallbackTarget:withCallbackSelector: you might want to try changing the selector to a custom function like:

Code:
- (void) testCallbackSelector:(UIImage*)image
{
NSLog(@"testCallbackSelector received image with address <%U>", image);
}

You might even use a test image in that function instead of the image you received to try and set the cell with an image.


Walk through all the steps. Make sure everything is initialized and you're getting what you want at each step. It took me quite a while to get everything working just right.
 

anim510

macrumors newbie
Oct 20, 2008
15
0
Hello guys, could you send a example source code to me that i have the same issue

Hello guys,

Could you send me all of your example codes? because i feel very confused about your talking and uncompleting codes. Your discussion is very helpful for me so I wish I could learn something here from you guys, thanks so much buddies!

;):)
My email is: anim510@gmail.com

Thanks again.
 

anim510

macrumors newbie
Oct 20, 2008
15
0
Hey guys.

I'm struggling to come up with an efficient and performant way of loading remote images (over a local wifi network) to be used as images in a UITableViewCell.

What I'm basically trying to achieve is something similar to what the Apple iTunes Remote app does - when you browse a list of albums, it retrieves the artwork from the machine running iTunes and displays it on the left. The Remote app obviously does this asynchronously as the artwork doesn't appear immediately the first time you view a row.

Here's what I have so far: a custom UITableViewCell subclass that represents an Album; it has two properties, album name and album artwork URL, and two views, a UILabel and a UIImageView. When the album name is set, the UILabel is updated. This works just fine. When the artwork URL is set, the idea is that an asynchronous HTTP connection downloads the artwork an dupdates the UIImageView when its finished.

Here is my code for a custom AsyncArtworkFetcher class which, given a URL, downloads the artwork asynchronously and invokes a method on a delegate object when finished:

Code:
@implementation AsyncArtworkFetcher

@synthesize delegate;
@synthesize url;
@synthesize userData;

- (void)fetch;
{
  NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0];
  NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
  if(connection) {
    receivedData = [[NSMutableData data] retain];
  }
}

#pragma mark NSURLConnection Delegate Methods

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
  [receivedData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
  [receivedData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
  UIImage *downloadedImage = [UIImage imageWithData:receivedData];
  SEL delegateSelector = @selector(artworkFetcher:didFinish:);
  if([delegate respondsToSelector:delegateSelector]) {
    [delegate performSelector:delegateSelector withObject:self withObject:downloadedImage];
  }
  [receivedData release];
  [connection release];
}

@end

And here's the relevant part of my custom table view cell class:

Code:
- (void)setArtworkURL:(NSURL *)newURL;
{
  if(newURL != artworkURL) {
    [artworkURL release];
    artworkURL = [newURL retain];
    AsyncArtworkFetcher *artworkFetcher = [[AsyncArtworkFetcher alloc] init];
    artworkFetcher.url = artworkURL;
    artworkFetcher.delegate = self;
    [artworkFetcher fetch];
  }
}

#pragma mark AsyncArtworkFetcher Delegate Methods

- (void)artworkFetcher:(AsyncArtworkFetcher *)fetcher didFinish:(UIImage *)artworkImage;
{
  albumArtView.image = artworkImage;
  [self setNeedsDisplay];
}

The main issue with this approach is down to the way in which UITableViewCell objects are reused. As you scroll through the list, when a cell is reused it will display the artwork for another album until its updated (its only briefly but it just looks wrong). One approach would be to have a unique cell for each album but this defeats the whole point of reusable cells.

Apart from this one issue, this works well enough in the simulator, but running on my actual iPhone it just locks the phone up, probably because of the number of asynchronous requests it's firing off at once. I guess I need to cache the downloaded image in some way (although I thought NSURLConnection dealt with caching automatically - perhaps I'm not configuring it correctly).

Any suggestions on a better way of going about this? Maybe I'm missing a really obvious technique - it must be possible, the Apple Remote app shows it can be done without any serious performance penalties.

Hey guys. How it going on ? It works now ? I want to get more information about this. I have read these posts more than one time.

I have the problem that is every time scrolling the cell lists, it will be lagging. It locks my phone up, because of it downloading the image from the internet.
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
Hello again youPhone,
i looked over my code several times and everything stopes in the ImageLoader class when it verifies if the callback target responds to the selector.

Code:
- (void) sendImageBack:(NSData*)data
{
	NSLog(@"Send image back function. Addres for data: <%U>", data);
	if (!data) {
		return;
	}
	NSLog(@"send image back"); //added this to test if the function is called
	//[data retain];
    NSLog(@"address for data = <%U>", data);
	[self addImageDataToCache:data forURL:imageURL];
	
	UIImage *thisImage = [UIImage imageWithData:data];
	//UIImage *thisImage =  [UIImage imageNamed:@"wai_news_logo_loading.png"];
	//NSLog(@"Address for thisImage = <%U>", thisImage);
	
	if ([callbackTarget respondsToSelector:callbackSelector]) {
		NSLog(@"sendImageBack inside IF");
		[callbackTarget performSelector:callbackSelector withObject:thisImage];
	}
	//NSLog(@"Address fot thisImage= <%U>", thisImage);
}

the [callbackTarget respondsToSelector:callbackSelector] in the last if always returns false so the image isn't sent back. i commented the if and i received the fallowing error:
Code:
*** -[UIImageView testCallbackSelector:]: unrecognized selector sent to instance 0x4bb0a0

the test callback selector in this case is:

Code:
-(void) testCallbackSelector: (UIImage *) image
{
	NSLog(@"testCallbackSelector received image with address <%U>", image);
}

do you have any idea why the callback target doesn't respond to the selector?

thank you in advance,
Sorin
 

youPhone

macrumors member
Sep 8, 2008
41
0
On the call that looks like this:
Code:
[imageLoader loadImageFromURL:yourURL withCallbackTarget:yourObject withCallbackSelector:@selector(yourMethod:)];
Verify that you have properly set 'yourObject' and 'yourMethod:'

Also, make sure whatever the object is that you're using (yourObject), make sure that 'yourMethod:' is defined by that object, such as adding it to the .h file.

If you're still having trouble, post the code that is making the call (it should look similar to the above code).
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
thank you for your quick answer youPhone.
My custom UITableViewCell looks like this.
Code:
#import <UIKit/UIKit.h>
#import "Feed.h"
#import "ImageLoader.h"


@interface FeedTableViewCell : UITableViewCell {
	
	UILabel *titleLabel;
	UILabel *dateLabel;
	NSMutableString *URL;
	NSMutableString *imgSrc;
	UIImageView *img;
	ImageLoader *imageLoader;
	
}

- (void) setData:(NSDictionary *) aFeed;
- (void) testCallbackSelector: (UIImage *) image;
- (void) setupImage:(UIImage *)anImage;
- (UILabel *) newLabelWithPrimaryColor:(UIColor *) primaryColor selectedColor: (UIColor *) selectedColor fontSize:(CGFloat) fontSIze bold:(BOOL) bold;


@property(nonatomic, retain) UILabel *titleLabel;
@property(nonatomic, retain) UILabel *dateLabel;
@property(nonatomic, retain) NSMutableString *URL;
@property(nonatomic, retain) NSMutableString *imgSrc;
@property(nonatomic, retain) UIImageView *img;
@property(nonatomic, retain) ImageLoader *imageLoader;

@end

in my FeedTableViewCell. i have:
Code:
#import "FeedTableViewCell.h"
#import "ImageLoader.h"


@implementation FeedTableViewCell
@synthesize titleLabel, dateLabel, URL, imgSrc, img, imageLoader;

...

-(void) setData:(NSDictionary *) dict
{
	self.titleLabel.text = [ dict objectForKey:@"title" ];
	self.dateLabel.text = [ dict objectForKey:@"pubDate" ];
	self.URL = [ dict objectForKey:@"link" ];
	NSMutableString *temp =  [[NSMutableString alloc] initWithString:@"http://www.wai.de/emailsJPG/114x120/"];
	[temp appendString:[dict objectForKey:@"description"]];
	// clean up the link - get rid of spaces, returns, and tabs...
	temp = [temp stringByReplacingOccurrencesOfString:@" " withString:@""];
	temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
	temp = [temp stringByReplacingOccurrencesOfString:@"\t" withString:@""];
	temp = [[temp componentsSeparatedByString:@"<"] objectAtIndex:0]; 
	
	NSURL *imgurl = [NSURL URLWithString: temp];
	
	imageLoader = [[ImageLoader alloc] init];
	//[imageLoader loadImageFromURL:imgurl withCallbackTarget:img withCallbackSelector:@selector(setupImage:)];
	[imageLoader loadImageFromURL:imgurl withCallbackTarget:self.img withCallbackSelector:@selector(testCallbackSelector:)];
}

- (void) setupImage:(UIImage *) anImage
{
	NSLog(@"Setup Image in table cell");
	
	UIImage *loadImage = [[UIImage alloc] init];
	loadImage = anImage;
	
	[img setImage:loadImage];
}

-(void) testCallbackSelector: (UIImage *) image
{
	NSLog(@"testCallbackSelector received image with address <%U>", image);
}

the setData function is called in the FeedTableViewController.m at - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath like this:
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"MyIdentifier";
    
	FeedTableViewCell *cell = (FeedTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
		cell = [[[FeedTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
		cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
		
    }
    
    // Set up the cell
	int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
	
	NSDictionary *itemAtIndex = (NSDictionary *)[stories objectAtIndex: storyIndex];
	[cell setData:itemAtIndex];
	return cell;
}

thank you for all the help!
Sorin
 

youPhone

macrumors member
Sep 8, 2008
41
0
I think this:
Code:
[imageLoader loadImageFromURL:imgurl withCallbackTarget:self.img withCallbackSelector:@selector(testCallbackSelector:)];

should be:
Code:
[imageLoader loadImageFromURL:imgurl withCallbackTarget:self withCallbackSelector:@selector(testCallbackSelector:)];
 

Isorinu'

macrumors newbie
Oct 16, 2008
19
0
Thank you! thank you! thank you! thank you! thank you! thank you! thank you!
you are the best! it's working perfectly!

can u please explain why that modification was necessary?

thanK you for all your time and patience :) you made me really happy!
 

youPhone

macrumors member
Sep 8, 2008
41
0
In this line:
Code:
[imageLoader loadImageFromURL:imgurl withCallbackTarget:self withCallbackSelector:@selector(testCallbackSelector:)];

When you had the self.img as the callback target, it was trying to send the selector 'testCallbackSelector' to 'self.img' which is a UIImageView object as specified in your code. As it said in the error message, 'self.img' does not have a selector 'testCallbackSelector'

The testCallbackSelector is a custom method that you've added to your custom FeedTableViewCell class, therefore you need to call testCallbackSelector on your FeedTableViewCell instance (which is 'self') rather than the UIImageView ('self.img').
 

Wunk

macrumors newbie
Nov 17, 2008
24
0
Netherlands
Great thread, this helped me a lot with making images load smoother in a tableviewcontroller.. (filling it with data through a JSON framework)

I am however running into same issue with the hashtostring function, the debugger gives:
Code:
2008-11-17 19:17:38.661 Kookjij-Menus[12588:20b] ImageLoader.m anImageURL = http://static.kookjij.nl//upload//0000/7876/00007876-60x60.jpg
2008-11-17 19:17:38.664 Kookjij-Menus[12588:20b] *** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x3824d0

Setting the identifiercounter = 0; solves it, but from what I understood it can cause issues with longer URL's ?

And what's the best way to unload or cache an image into memory ?, at the moment when a cell is re-used while scrolling, the old image displays until the new one is loaded, this is noticable when scrolling back and forward, this will temporarily display the wrong image in a cell when it pops back into view..

I'm doing a #import DataLoaderOperation.h class from the ImageLoader.m, file, which is in turn called from the ViewImageCell subclass of a TableViewController.., as with the above example it's also called from a setData function and a setupImage function:

Code:
-(void)setData:(NSDictionary *)dict {
	self.titleLabel.text = [dict objectForKey:@"naam"];
	self.urlLabel.text = [dict objectForKey:@"description"];
	self.itemID = (int)[dict objectForKey:@"id"];

	NSURL *imgUrl = [[NSURL alloc] init];
	imgUrl = [NSURL URLWithString:[dict objectForKey:@"image"]];
	
	imageLoader = [[ImageLoader alloc] init];
	[imageLoader loadImageFromURL:imgUrl withCallbackTarget:self withCallbackSelector:@selector(setupImage:)];	

}

- (void) setupImage:(UIImage *) anImage
{
	NSLog(@"Setup Image in table cell");
	UIImage *loadImage = [[UIImage alloc] init];
	loadImage = anImage;
	
	[imageView setImage:loadImage];
}
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.