I have an app i am building it works fine but the image source i am using is from a website and when I switch back to my initial view it takes quite some time for it to load. My question is would there be a way to get this done and have the speed be faster.
here is the code I use to pull my image source
////Loads UIImageView from URL
todaysWallpaper.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.inkdryercreative.com/daily/archive/mondays/images/062-mondays-960x640-A.jpg"]]];
any help or a shove in the proper direction. Soon the image will change every time day as well so any help/thoughts on that would be of great appreciation.
The probleme here is that dataWithContentsOfURL: is on the main thread, so it will block your UI when the image is downloaded.
You have to download it asynchronously. For that I personally use a great piece of code i found on the internet : SDWebImage. It does exactly what you want.
wrap the UIImage creation in a block running asynchronously (code assumes ARC) which in turn calls your callback in the main thread
#implementation Foo
...
Foo* __weak weakSelf=self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:
[NSURL URLWithString:#"http://www.inkdryercreative.com/..jpg"]]];
dispatch_sync(dispatch_get_main_queue(),^ {
//run in main thread
[weakSelf handleDelayedImage:image];
});
});
-(void)handleDelayedImage:(UIImage*)image
{
todaysWallpaper.image=image;
}
the weakSelf trick ensures that your Foo class is properly cleaned up even if the URL request is still running
Related
I am relatively new to IOS programming.
I have a NSObject "Places" which is given below.
#interface Places : NSObject
#property(strong) NSString *placeName;
#property(strong) UIImage *placeImage;
#end
I am listing the array of Places objects in UITableView. On tableView:cellForRowAtIndexPath: images are fetching asynchronously from web urls. On tableView:didSelectRowAtIndexPath:, I pass the respective 'Places' object to another ViewController (detailViewController). In that detailViewController, I uses below code to display the image in a UIImageView.
self.imageView.Image = self.myPlaces.placeImage
My problem is when I pass that object, the image needn't be fetched to placeImage. Is there anyway to update the self.imageView on successive completion of image fetching to placeImage in mainViewController.
My code is given below. It is called in tableView:cellForRowAtIndexPath: of mainViewController.
NSURL *url = [NSURL URLWithString:#"http://imageurl_goes_here"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *imageData = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
if(imageData != nil)
{
place.placeImage = [UIImage imageWithData: imageData];
cell.cellImageView.image = place.placeImage;
}
});
});
Well, there are some techniques to archive that:
You can notify your second view controller every time you received image. E.g. in your inner dispatch_async. But this requires main controller to know much about second one.
You can use Key-Value Observing technique to observe every place's image updating. In this case you might also want to use some handy library that will do sanity for you (like unsubscribing when object is deallocated, otherwise you will get crash) like that one or any other.
This post of Matt Thompson may be helpful on other types of communications between instances.
For some reason when I'm downloading my image using GCD, the image will randomly start flickering.
I'll reset the content settings in simulator and it'll work once, then it'll just start flickering again.
This is the code I am using. I've got the reloads in there because if I don't reload it, the image doesn't show until I tap on the cell.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSURL *url = [NSURL URLWithString:self.entries.arrayimage];
NSData *imgData = [NSData dataWithContentsOfURL:url];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
cell.imageView.image = nil;
UIImage *img = [UIImage imageWithData:imgData];
cell.imageView.image = img;
[self.tableView reloadData];
});
});
return cell;
[self.tableView reloadData];
That's because you are using dispatch_async(dispatch_get_global_queue to async load imageData from file. Using this style load image in cellForRow will make cell image should should previous image first. Then finish async load, will call dispatch_sync(dispatch_get_main_queue(), to load the image you want to. Therefore, whenever you reloadData or any other methods to call cellForRow, the cell image will flicker.
I know you want to load image without blocking main thread, but it's not a good way.
Check out apple sample code for Lazy Image loading. And also I checked your code and found that you always downloading image from URL. Instead of that its good to download and save image in caches and then load image from next time in cellForRowAtIndexPath method from local caches if available.
Is this code in cellForRowAtIndexPath:?
If that is the case, the problem here is that all the cells are infinitely reloading the table. You should not be calling reloadData in any of datasource methods that are triggered by reloadData.
What you have is basically an infinite loop of reloading. (reloadData triggers cellForRowAtIndexPath: which once again triggers reloadData).
My suggestion is to use an external component for this as aeskreis posted in his comment.
SDWebImage is probably the best one out there and will allow you to simplify all of the code you have there into simply:
NSURL *url = [NSURL URLWithString:self.entries.arrayimage];
[cell.imageView setImageWithURL:url];
Okay Guys I found out the problem. It was constantly reloading the data which caused te flickering. instead of [self.tableView reloadData] I replaced it with this method:
[cell setNeedsLayout];
I believe this method detects if anything has been changed, and then updates it (from my memory of a couple hours ago so it's probably not 100% accurate), but that fixed my problem.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSURL *url = [NSURL URLWithString:self.entries.arrayimage];
NSData *imgData = [NSData dataWithContentsOfURL:url];
dispatch_sync(dispatch_get_main_queue(), ^{
UIImage *img = [UIImage imageWithData:imgData];
cell.imageView.image = img;
//[self.tableView reloadData];
[cell setNeedsLayout];
});
});
return cell;
How would you stall the program from executing? Doesn't sleep function do that? If it does, what's wrong with this code:
AsyncImageView *tmpImage = [imagesArray objectAtIndex:index];
int waitCounter = 0;
while (waitCounter < 10 && !tmpImage.imageIsLoaded) {
waitCounter++;
sleep(0.5);
}
NSLog(#"%i",waitCounter);
if (tmpImage.imageIsLoaded) {
return [tmpImage image];
} else {
return [UIImage imageNamed:#"img_noimg.png"];
}
The AsyncImageView class loads an image from an URL then sets imageIsLoaded property to true. This usually happens quite fast (under a second), but if I scroll though the images like a madman, then it doesn't have time to load (it loads 10 images ahead).
I've added the while to prevent that from happening, but the log displays multiple cases of waitCounter being 10 while there was no sleep time (the interface didn't freeze).
One approach to solve a problem like this is to use a completion handler. A completion handler signals the call-site when an asynchronous task is complete and passes the result of the task as a parameter to the completion handler.
Your AsyncImageView class does have indeed such a completion handler. It's a selector. Today, one would use a block instead. The principle is the same, though.
Continuing after an async task has finished is called "Continuation". With blocks, you code looks as follows:
typedef void (^completion_handler_block)(id result);
void doSomethingAsync(completion_handler_block)completionHandler;
- (void) foo {
doSomethingAsync(^(id result) {
// Continuation code
...
});
}
With blocks implementing continuation is especially easy, since it keeps the context (referenced variables defined in the call-site) within the block (closure).
Where are you doing this? in method cellForRowAtIndexPath ?
If so, you must not sleep here.
For loading an image asynchronously you must
1) pass the real UIImageView to your AsyncImageView and
2) when you load the UIImage from URL, instead of set *_imageIsLoaded = TRUE*, you can load it directly in the passed UIImageView using *_imageView.image = image*
For instance you can do something like this, taking into account that maybe you already have the image loaded, etc. Create this method in AsyncImageView like this
-(void) reloadWithImageView:(UIImageView*)imageView {
_imageView = imageView;
}
and when you later load the UIImage (probably in the connectionDidFinishLoading method)
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
connection = nil;
UIImage *image = [UIImage imageWithData:data];
_imageView.image = image;
}
Try This, I guess it will work. Instead of
sleep(0.5);
try
[NSThread sleepForTimeInterval:.5f];
In my app I just implement a dispatch_async block that will grab images from a NSDictionary and then eventually when the image is ready, set it to a UITableViewCell UIImageView. What I want to do is make a UIActivityIndicatorAppear in the middle of the UITableViewCell's UIImageView while the dispatch_async is occurring.
This is my code for the dispatch_async block:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(void) {
NSData *data = [myDictionary objectForKey:#"myKey"];
UIImage *cellImage = [UIImage imageWithData:data];
//we get the main thread because drawing must be done in the main thread always
dispatch_async(dispatch_get_main_queue(),^ {
[cell.imageView setImage:cellImage];
} );
});
Anyway is this possible? And if so, how?
Thanks!
Show the progress indicator before your dispatch_async and remove or hide it your main queue block where you set the image.
Hi I'm working through the Stanford iOS development class. I have a question regarding threading. I understand UIKit calls should be handled by the main thread. I was wondering if something like this is legal?
- (UIImage *)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation {
FlickrPhotoAnnotation *fpa = (FlickrPhotoAnnotation *) annotation;
NSURL *url = [FlickrFetcher urlForPhoto:fpa.photo format:FlickrPhotoFormatSquare];
__block UIImage *image;
dispatch_queue_t downloadQ = dispatch_queue_create("download queue", NULL);
dispatch_async(downloadQ, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
dispatch_async(dispatch_get_main_queue(), ^{
image = [UIImage imageWithData:data];
});
}
});
dispatch_release(downloadQ);
return image;
}
or I should just return NSData and handle all the threading in the calling method?
Thanks
Your code won't do what you want.
You are putting an asynchronous block into a queue, and then immediately returning from the method. You are not guaranteed that the block will actually run before you return -- in fact, the odds are that it won't.
So you'll return the current value of image. Since you didn't initialize it, it's probably garbage. Whoever calls this method will try to use a garbage pointer to an image, and (if you're lucky) crash. If you had initialized it to nil:
__block UIImage *image = nil;
that would be a little more polite.
The problem here is: your method must return a UIImage, so you must wait for the time it takes to make a fully constructed UIImage before you return. There is zero benefit to doing this on some other thread -- you're still waiting the same amount of time, and switching threads just adds overhead.
In order to load the image in a usefully asynchronous way, you need some way to asynchronously tell the caller when the image is done loading, via a callback to a delegate method or block. For example, look at NSURLConnection in Foundation. It has an older method that calls back via a delegate, and a newer method (+sendAsynchronousRequest:queue:completionHandler:) that calls back via a block.
I concur with CodaFi's comment. Actually, images can be created off the main thread. UIView creation and manipulation must be done on the main thread, but UIImage is not a UIView though. Furthermore, the runtime is likely to give you a warning or error if you try to manipulate the displaying UI on another thread (it did for me when I accidentally updated a UITableView on another thread).