Table view with image caching and "pull to update" option - ios

Almost every ios app now has something like "Feed" option.
Programming that usually includes fetching images from the web, caching them, handling pages, "pull to update option", etc. - all the standart stuff.
But looks like there is no standart solution for that?
I tried "three 20" - really big, complicated library with many modules. It really lacks good documentation! And it also had "slowdowns" while fetching images from cache.
Maybe I should use different small libraries for each small task separately? Like HJCache, EGO, etc.
Or is it better to write everything from scratch without any libraries?
Please give me advice on best practices here, i am really stuck now.

This one is very easy to drop in for pull to refresh.
For image loading, I wrote the following category for UIImageView:
// .h
#interface UIImageView (UIImageView_Load)
- (void)loadFrom:(NSURL *)url completion:(void (^)(UIImage *))completion;
#end
// .m
#import "UIImageView+Load.h"
#import <QuartzCore/QuartzCore.h>
#implementation UIImageView (UIImageView_Load)
- (void)loadFrom:(NSURL *)url completion:(void (^)(UIImage *))completion {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) {
self.image = [UIImage imageWithData:data];
if (completion) completion(self.image);
}
}];
}
#end
// To use it when building a cell
//...
MyModelObject *myModelObject = [self.myModel objectAtIndex:indexPath.row];
if (myModelObject.image) {
cell.imageView.image = myModelObject.image;
} else {
NSURL *url = [NSURL urlWithString:myModelObject.imageUrl];
[cell.imageView loadFrom:url completion:^(UIImage *image) {
// cache it in the model
myModelObject.image = image;
cell.imageView.image = image;
}];
}
// ...

I'm a fan of Leah Culver's Pull to Refresh library, or this STableViewController which handles pull-to-refresh as well as endless scrolling downward.
For image loading, try SDWebImage from the DailyMotion app.

Related

AFNewtorking loading images from Amazon S3

We were using AFNetworking only to load images like this:
[thumbnailImage setImageWithURL:_item.thumbnailUrl];
and it worked fine. But now we've pulled in the rest of the project it has stopped working. It was just showing the background color. So I tried using this and it loads the placeholder but never loads the image.
UIImage* placeholder = [UIImage imageNamed:#"placeholder"];
[thumbnailImage setImageWithURL:_item.thumbnailUrl placeholderImage:placeholder];
I thought I might be able to see what the problem was so I tried:
NSURLRequest* urlRequest = [NSURLRequest requestWithURL:_item.thumbnailUrl cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30];
[thumbnailImage setImageWithURLRequest:urlRequest placeholderImage:[UIImage imageNamed:#"placeholder"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
NSLog(#"Loaded Image");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
NSLog(#"Failed to load Image");
}];
and it seems that it's not getting the return type that it's expecting. It says Expected content type image/png, got binary/octet-stream I'm pulling the image from Amazon S3 so I don't know if I have control over the type.
I wasn't able to change the data type so I overrode the acceptableContentTypes method in AFNetworking by adding the following files to my project.
AFImageRequestOperation+OctetMIMEType.h
//
// AFImageRequestOperation+OctetMIMEType.h
// iQ.fileShare
//
// Created by Poole, Patrick on 4/25/13.
//
//
#import "AFImageRequestOperation.h"
#interface AFImageRequestOperation (OctetMIMEType)
#end
AFImageRequestOperation+OctetMIMEType.m
//
// AFImageRequestOperation+OctetMIMEType.m
// iQ.fileShare
//
// Created by Poole, Patrick on 4/25/13.
//
//
#import "AFImageRequestOperation+OctetMIMEType.h"
#implementation AFImageRequestOperation (OctetMIMEType)
+ (NSSet *)acceptableContentTypes {
return [NSSet setWithObjects:#"binary/octet-stream",#"image/tiff", #"image/jpeg", #"image/gif", #"image/png", #"image/ico", #"image/x-icon" #"image/bmp", #"image/x-bmp", #"image/x-xbitmap", #"image/x-win-bitmap", nil];
}
#end
AFNetworking provides a method to add acceptable content types in 1.3.3, I'm not sure what other versions support this
[AFImageRequestOperation addAcceptableContentTypes:[NSSet setWithObjects:#"binary/octet-stream", nil]];

iOS App - Display images in a grid view froma list of JSON URL.

I'm storing a list of URLs in a MYSQL db table and I have been able to successfully retrieve the URLS via JSON.
However, now that I have the JSON I am not sure how to display the images in a image gallery type view in my iPhone App.
What I plan on having is a huge list of URL's to image in the database and I want to display x amount at a time and then once the user scrolls to the end I want to display x amount more. Like pinterest.
Any hints?
#import "mySQLAppViewController.h"
#interface mySQLAppViewController (){
}
- (IBAction)loadJSON:(id)sender;
#end
#implementation mySQLAppViewController
- (IBAction)loadJSON:(id)sender {
NSString *dataUrl = [NSString stringWithFormat:#"http://MYDOMAIN.com/results.php"];
NSURL *url = [NSURL URLWithString:dataUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
for (int i =0 ; i < [JSON count]; i++) {
//NSString *image_url = JSON[i][#"image_url"];
//NSLog(#"%#", image_url);
//^^THIS SUCESSFULLY LOGS THE URL LINKS (I.E. HTTP://MYDOMAIN.COM/CAR.JPG)
NSURL *imageURL = [NSURL URLWithString:JSON[i][#"image_url"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Weather"
message:[NSString stringWithFormat:#"%#",error]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
}];
[operation start];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Sounds like you want to look at UICollectionView. Here's a tutorial to get you started:
Beginning UICollectionView In iOS 6.
Edit: Edited after davidm's answer to be more complete: (https://stackoverflow.com/a/16918758/412339) Mark his as answer if it (UICollectionView) is the best for your solution.
iOS 6 and above:
You should really check out UICollectionView. A really good resource for this is found here: Create an iOS 6 UICollectionView Using Storyboards
iOS 5 and lower:
One thing that you can do is create a custom ViewController that has an array of your image URLs or your JSON Array:
#property (nonatomic, retain) NSArray *imageURLs;
Then create a set of reusable UIImageViews (which is another question in itself, but basically you need an array that holds custom UIImageViews with a reuse identifier, similar to a UITableViewCell).
#property (nonatomic, retain) NSArray *imageViews;
Finally, you will need a UIScrollView and UIView (to contain the UIImageViews) that watches the position and adds the imageview on the screen using a UIScrollViewDelegate method (scrollViewDidScroll: should work).
Alternatively, you can just implement a UITableViewController and Delegate with a custom UITableViewCell and let Apple's engineers take care of most of that for you, but you won't be able to put photos side by side, only stacked on top of each other. That may be what you are wanting though.

UITableView scrolls slow after downloading cell images

I am trying to download some thumbnail from server and then display them in each cell :
// displaying image...
dictionary = [newsFeed objectAtIndex:indexPath.row];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[dictionary objectForKey:#"image"]]]];
cell.imageView.image = [image imageScaledToSize:CGSizeMake(55, 55)];
but after downloading and displaying images, the table view scrolls slowly!
The main problem is that [NSData dataWithContentsOfURL:] method is a synchronous request. It blocks the thread where is running until the request is done.
You should avoid this type of interaction if you run this on the main thread. This thread will be frozen and the user will be disappointed ;)
You have many solutions for this. A simple one is to use third library like SDWebImage or AFNetworking.
Both have categories around UIImageView class that allows to use them in a simple manner. For example, using UIImageView+AFNetworking you should do like the following:
[cell.imageView setImageWithURL:yourURL placeholderImage:yourPlaceholderImage];
I think You are trying to say this.
NSURL* url = [NSURL URLWithString:#"http://www.YourImageUrl.com"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage* image = [[UIImage alloc] initWithData:data];
// Now workout with the image
}
}];
This will make the asynchronous call, and load the images after tableview loaded i.e when the image load complete the image will show but the table will be loaded when the table view is needed to load.
Directly use like this
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[dictionary objectForKey:#"image"]]]];
It takes the main thread, so you can't scroll befor download your Image.So You add NSURLConnectionDataDelegate, NSURLConnectionDelegate add this protocol and download image by
Insert these lines of coding where you want to download Your Image
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
received_data = [[NSMutableData alloc]init];
[connection start];
These lines triggers these delegates to download data.
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[received_data setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[received_data appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Use your download image
imageView.image = [UIImage imageWithData:received_data];
}
Using AFNetworking will get you a better feel. There is a UIImageView category as part of the project that will insert a temporary image, while switching to a background thread to download the real image.
That will keep the image download from locking the main thread. Once the image is downloaded the temporary image will be swapped out for you.
Here's a nice tutorial that includes some pointers on using the UIImageView category AFNetworking crash course.
I have written an answer here:
https://stackoverflow.com/a/14624875/1375695
which explains how to use Grand Central Despatch to keep your table scrolling smoothly while your images are downloading in the background. That answer doesn't mandate the use of a 3rd party library. Although - as Andrew and Flexaddicted have mentioned - there are libraries which will provide a solution for you - it might be worth reading so that you can understand what needs to happen under the hood.
Note that in my proposed solution, you could still use your synchronous method to actually get the data from the network:
UIImage *image = [UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString:
[dictionary objectForKey:#"image"]]]];
as it would be called on an asynchronous background thread.
I highly recommend the AFNetworking framework as #flexaddicted has recommended (UIImageView+AFNetworking). Also, check out the Sensible TableView framework. It should be able to automatically fetch all data from the web service and display them in the table view, and will also automatically handle all asynch image downloading. Should save you a bunch of work.
For More convenience to download image use EGOImageLoader/EGOImageView
Download this class by this link
https://github.com/enormego/EGOImageLoading

Slow loading the images from URL in UITAbleView.

I'm loading the images from URL in UITableView. But it's very slow when loading an view. Here's an example,
UIImage *image = nil;
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://calcuttans.com/palki/wp-content/uploads/2009/02/kidscover-small.png"]]];
In Table view, UIButton i'm setting the background image.
Please Can you provide the sample.
FYI : I'm used the LazzyTable sample program but it's not much helpful. Can you suggest any other samples.
Load image asynchronously
NSURL* url = [NSURL URLWithString:#"http://calcuttans.com/palki/wp-content/uploads/2009/02/kidscover-small.png"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
NSImage* image = [[NSImage alloc] initWithData:data];
// do whatever you want with image
}
}];
There are some open source libraries available for this:
HJCache
SDWebImage
These libraries download image in a asynchronous manner and cache it for further use.
Try to implement AFNetworking. It uses async requests to download the image, you are currently blocking your view with every download.
You can then use an AFImageRequestOperation to download your image.
if you load the image all download from the internet every time , it must be very slow.
I think you shuold exist your download image to the filePath , and when you will load the image , you can check whether the image has been downloaded before , if not ,then download. if it has been downloaded , you can use imageWithContentsOfFile: method to load the image
//Make use of dispatch queue for faster processing of data. add this in viewDidLoad
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData * data=[NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
[self performSelectorOnMainThread:#selector(setImage:) withObject:data waitUntilDone:YES];
});
//once data is got set the image and reload tableview
-(void)setImage:(NSData *)responseData
{
image = [UIImage imageWithData:responseData];
[tableView reloadData];
}
maybe you can use asihttprequest to lazy load images. use ASINetworkQueues
You've to use NSOperationQueue to make your tableview efficient.
Check this icodeblog tutorial and raywenderlich tutorial
Have a look at this tutorial. It helped me a lot. When I was using it I was quite new to iOS in general and it was helpful not only with respect to loading images from the web.
http://www.markj.net/iphone-asynchronous-table-image/
With AFNetworking it is more easy.
//AFNetworking
#import "UIImageView+AFNetworking.h"
[cell.iboImageView setImageWithURL:[NSURL URLWithString:server.imagen] placeholderImage:[UIImage imageNamed:#"qhacer_logo.png"]];

What is the best way to load a remote image?

I've been researching and haven't found any answer to this question - sendAsynchronousRequest vs. dataWithContentsOfURL.
Which is more efficient? more elegant? safer? etc.
- (void)loadImageForURLString:(NSString *)imageUrl
{
self.image = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError)
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data) {
self.image = [UIImage imageWithData:data];
}
}];
}
OR
- (void)loadRemoteImage
{
self.image = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData * imageData = [NSData dataWithContentsOfURL:self.URL];
if (imageData)
self.image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.image) {
[self setupImageView];
}
});
});
}
So I've come up with an answer for my own question:
Currently there are 3 main ways to load images async.
NSURLConnection
GCD
NSOperationQueue
Choosing the best way is different for every problem.
For example, in a UITableViewController, I would use the 3rd option (NSOperationQueue) to load an image for every cell and make sure the cell is still visible before assigning the picture. If the cell is not visible anymore that operation should be cancelled, if the VC is popped out of stack then the whole queue should be cancelled.
When using NSURLConnection + GCD we have no option to cancel, therefore this should be used when there is no need for that (for example, loading a constant background image).
Another good advice is to store that image in a cache, even it's no longer displayed, and look it up in cache before launching another loading process.
sendAsynchronousRequest is better, elegant and whatever you call it. But, personally, I prefer creating separate NSURLConnection and listen to its delegate and dataDelegate methods. This way, I can: 1. Set my request timeout. 2. Set image to be cached using NSURLRequest's cache mechanism (it's not reliable, though). 2. Watch download progress. 3. Receive NSURLResponse before actual download begins (for http codes > 400). etc... And, also, it depends on cases like, image size, and some other requirements of your app. Good luck!

Resources