UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
I use the code above and then check
if (image)
return image;
else
{
NSLog(#"no image on URL");
return nil;
}
But sometimes (very very seldom) I don't get an image from a valid url. The url is valid 100%.
Usually it takes nearly one second to load a picture, but when it can't load a picture the process takes much more time (20-200 seconds).
And then i get "no image on URL".
Is there a better way to get a picture from URL?
I'd rather get "no image on URL" in one second then waiting so long.
P.S. srry for my poor english
It is very rare that you want to use dataWithContentsOfURL:. It's a blocking call so requires a background thread. It's also inflexible and doesn't provide good error returns (which is the problem you're encountering).
See the URL Loading System Programming Guide. Generally you'll want to configure an asynchronous NSURLConnection for this kind of work. If you're doing a lot of network operations, you may want to consider a framework like MKNetworkKit or AFNetworking which handle a lot of the complexities for you.
Related
In my iOS app I am using the +imageNamed: method to load an image (many times and in many different places in the code).
In one case the user might update (download) a new image.
When I try to load the new, it will show the old, due to caching.
From the "Is there a way to clear the cache used by UIImage class?" question, I saw that I have to use the -initWithContentsOfFile: method.
But this will not take advantage of the caching speedup that the +imageNamed: enjoys. All I want is to "tell" the cache that the file has changed, so it needs to "re-cache" it. And then keep using the +imageNamed: method with the new cached image.
In other words, I use the +imageNamed: method (say) 10 times, I change the image, I "tell" the cache, then I continue use the +imageNamed: method another (say) 10 times. If I change all the +imageNamed: to -initWithContentsOfFile: then I lose the caching advantage.
Is there a way/trick to do that?
There is no API for clearing the cache. If your app is not destined for the app store you could call the private method:
[UIImage _flushSharedImageCache];
However I wouldn't want this anywhere near production code.
Instead I would create a category on UIImage and add a method for returning the desired image from a filename. This name would be stored and then updated when your new image is downloaded. You will get the benefit of caching, without any hacky workarounds.
Depending on the complexity of your project, a simple find and replace shouldn't take too long.
Although I'm now questioning how your app is working currently, imageNamed only looks for files in your app's bundle, so won't work for images downloaded by the user.
You'll probably just have to figure out your own way of caching your images.
I'd suggest using a UIImage category with a static NSMutableDictionary that can hold your cached images. Then just use your custom caching method when initialising your UIImage.
For example:
#interface UIImage (UIImageCache)
+(UIImage*) cachedImageFile:(NSString*)imageFile;
+(void) resetCacheForImageFile:(NSString*)imageFile;
#end
#implementation UIImage (UIImageCache)
static NSMutableDictionary* cachedImages;
+(UIImage*) cachedImageFile:(NSString*)imageFile {
// Optional error checking
NSAssert1([[NSFileManager defaultManager] fileExistsAtPath:imageFile], #"Warning! The image file %# doesn't exist.", imageFile);
if (!cachedImages) cachedImages = [NSMutableDictionary dictionary];
UIImage* cachedImg = [cachedImages objectForKey:imageFile];
if (cachedImg) return cachedImg; // Image is cached, return it
else { // No cached image, create one
UIImage* img = [UIImage imageWithContentsOfFile:imageFile]; // iOS won't auto-cache the image.
[cachedImages setObject:img forKey:imageFile];
return img;
}
}
+(void) resetCacheForImageFile:(NSString*)imageFile {
[cachedImages removeObjectForKey:imageFile];
}
#end
Maybe I just got late to the party...but using
+ (UIImage *)imageWithContentsOfFile:(NSString *
I got rid of the cache issue.
Hope it helps!!
I am trying to use Path's FastImageCache library to handle photos in my app. The sample they provide simply reads the images from disk. Does anyone know how I might modify it to read from a url? In the section about providing source images to the cache they have
- (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id<FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageRequestCompletionBlock)completionBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Fetch the desired source image by making a network request
NSURL *requestURL = [entity sourceImageURLWithFormatName:formatName];
UIImage *sourceImage = [self _sourceImageForURL:requestURL];
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(sourceImage);
});
});
}
Has anyone used this api before and know how to get the source from the server to pass to the cache? Another example that still uses hard disk is
- (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id<FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageRequestCompletionBlock)completionBlock {
// Images typically come from the Internet rather than from the app bundle directly, so this would be the place to fire off a network request to download the image.
// For the purposes of this demo app, we'll just access images stored locally on disk.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *sourceImage = [(FICDPhoto *)entity sourceImage];
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(sourceImage);
});
});
}
I worked on Fast Image Cache while I was at Path. The critical portion of Fast Image Cache is that it is the absolute fastest way to go from image data on disk to being rendered by Core Animation. No decoding happens, none of the image data is kept in memory by your app, and no image copies occur.
That said, the responsibility is yours to figure out how to download the images. There's nothing inherently special about downloading images. You can use NSURLConnection or one of many popular networking libraries (like AFNetworking) to actually download the image data from your server. Once you have that image data, you can call the relevant completion block for Fast Image Cache to have it optimize it for future rendering.
If you're looking for a simple way to download an image and display it when it's finished, then use something like SDWebImage. It's great for simple cases like that. If you are running into performance bottlenecks—especially with scrolling—as a result of your app needing to display tons of images quickly, then Fast Image Cache is perfect for you.
Your Approach Seems a Lot Like Lazy Loading Images from the URL, I had to do this once I had Used the following Library to do it, It dosent stores the Images in the disk, but uses cached Images..the below is its link..
https://github.com/nicklockwood/AsyncImageView
I added the networking logic to our fork > https://github.com/DZNS/FastImageCache#dezine-zync-additions-to-the-class
It utilizes NSURLSessionDownloadTasks, has a couple of configuration options (optional). All you need to do is create a new instance of DZFICNetworkController and set it as the delegate for FICImageCache's sharedCache instance object. It'll take care of downloading images with reference to the sourceImageURLWithFormatName: method on your objects conforming to <FICEntity>.
As I assume you'd use this in a UITableView or UICollectionView, calling cancelImageRetrievalForEntity:withFormatName: on the imageCache will cancel the download operation (if it's still in-flight or hasn't started).
I am banging my head about an issue I have on iOS7 development. I use the following piece of code to load an image from a webserver:
NSData* data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://someServer/someImage.jpg"]];
This works like a charme in simulator, reading exactly the 134185 bytes that the image has. Creating an UIImage from that data works as intended.
Once I test the exact same code on a device (iPad Mini, iOS 7.03), though, it just reads 14920 byte from the same URL. Needless to say that I can't create an UIImage from that data then, creation fails and returns a nil.
The read does not produce any errors (no console output, and also using the signature with the error output param returns nil here). Is there anything I missed around this rather straightforward task? Haven't found anything on the web on this…
Thanks, habitoti
So you don't have any error, and something is downloading. Maybe try to read this response and post here (I guess it is html/text body)?
You can use NSString method:
+ (instancetype)stringWithContentsOfURL:(NSURL )url encoding:(NSStringEncoding)enc error:(NSError *)error;
Can I suggest you use a library like SDWebImage to retrieve your image, it caches it and downloads the images asynchronously.
It also has a category for UIImageView so you can just call [imageView setImageWithURL:]; and it will load the image in when its ready.
I was using the services from Parse a while back, and they had implemented an amazing feature for uploading data, with a method something like this:
PFFile *objectToSave...; //An image or whatever, wrapped in a Parse-file
[objectToSave saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
//Do stuff after upload is complete
} progressBlock:^(int percentDone) {
[someLabel setText:[NSString stringWithFormat:#"%i%#", percentDone, #"%"]];
}];
Which let me keep track of the file-upload. Since Parse only let me upload max 10mb files, I chose to move to the cloud-area to explore a bit. I've been testing with Amazon's S3-service now, but the only way I can find how to upload data is by calling [s3 putObject:request];. This will occupy the main thread until it's done, unless I run it on another thread. Either way, I have no idea of letting my users know how far the upload has come. Is there seriously no way of doing this? I read that some browser-API-version of S3's service had to use Flash, or set all uploads to go through another server, and keep track on that server, but I won't do either of those. Anyone? Thanks.
My users are supposed to be uploading video with sizes up to 15mb, do I have to let them stare at a spinning wheel for an unknown amount of time? With a bad connection, they might have to wait for 15 minutes, but they would stare at the screen in hope the entire time.
Seems like I didn't quite do my homework before posting this question in the first place. I found this great tutorial doing exactly what I was asking for. I would delete my question, but I'll let it stay just in case it might help other helpless people like myself.
Basically, it had a delegate-method for this. Do something like this:
S3PutObjectRequest *por = /* your request/file */;
S3TransferManager *tm = /* your transfer manager */;
por.delegate = self;
tm.delegate = self;
[tm upload: por];
Then use this appropriately named delegate-method:
-(void)request:(AmazonServiceRequest *)request
didSendData:(long long)bytesWritten
totalBytesWritten:(long long)totalBytesWritten
totalBytesExpectedToWrite:(long long)totalBytesExpectedToWrite
{
CGFloat progress = ((CGFloat)totalBytesWritten/(CGFloat)totalBytesExpectedToWrite);
}
It will be called for every packet it uploads or something. Just be sure to set the delegates.
(Not sure if you need both delegates to be set though)
I am trying to build a nice function to access the network for images, if they are found on the web, I store them in a cache system I made.
If the image was already stored on the cache, I return it.
The function is called getImageFromCache and returns an image if it is in the cache, else, it would go to the network and fetch.
The code might look like this:
UIImageView* backgroundTiles = [[UIImageView alloc] initWithImage[self getImageFromCache:#"http://www.example.com/1.jpg"]];
Now, I am moving on to using threads because of big latencies due to network traffic. So I want images to show a temp image before I get the result from the web.
What I want to know is how can I keep track of so many images being accessed sequentially, being added to UIImageViews by this function (getImageFromCache).
Something just won't work there:
-(UIImage*)getImageFromCache:(NSString*)forURL{
__block NSError* error = nil;
__block NSData *imageData;
__block UIImage* tmpImage;
if(forURL==nil) return nil;
if(![self.imagesCache objectForKey:forURL])
{
// Setting a temporary image until we start getting results
tmpImage = [UIImage imageNamed:#"noimage.png"];
NSURL *imageURL = [NSURL URLWithString:forURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
imageData = [NSData dataWithContentsOfURL:imageURL options:NSDataReadingUncached error:&error];
if(imageData)
{
NSLog(#"Thread fetching image URL:%#",imageURL);
dispatch_async(dispatch_get_main_queue(), ^{
tmpImage = [UIImage imageWithData:imageData];
if(tmpImage)
{
[imagesCache setObject:tmpImage forKey:forURL];
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
return [imagesCache objectForKey:forURL];
return tmpImage;
}
This is not a direct answer to your question, but are you aware that there is no need to use GCD to download things asynchronously (on a background thread)? Just use NSURLConnection and its delegate methods. All your code will be on the main thread but the actual connection and downloading will happen in the background.
(And in fact I have written a class, MyDownloader, that takes care of all this for you:
http://www.apeth.com/iOSBook/ch37.html#_http_requests
Scroll down to the part about MyDownloader and its subclass MyImageDownloader, which is doing exactly the sort of thing you need done here. Moreover, note the subsequent code in that chapter showing how to use a notification when a download completes, prompting the table view that need these images to reload the row that contains the image view whose image has just arrived.)
its good your building it from scratch but if you want to save the all the work, there's a drop in Replacement SDWebImage Library with support for remote images coming from the web, and has all the functionality Like Temp Image, Asychronous Loading, Caching etc, you said you need
In your background thread, once the download has completed and you've saved the image to the cache, I'd suggest you post a notification using the NSNotificationCenter to let other parts of your app know that the cache has been updated.
This assumes that whichever part of the app manages the image views has registered its interest in those notification with the addObserverForName method. When it receives such a notification, it can then attempt to retrieve the images from the cache again and update its image views if appropriate.
Depending on the number of image views, you may want to pass through the image url in the notification in some way (e.g. in the userInfo dictionary), and then based on that decide which image views should be refreshed rather than refreshing them all.
I should add that I would also recommend getting rid of the inner dispatch_async call. There's no need for that, although you may need to add synchronisation to your cache object so it can be safely accessed from the main thread as well as the download thread.