Reputation: 3543
I'm currently developing a JSON TableView with the information from my database (some products with the image and their names). Everything is fine but it's very slow when I scroll down (or up). I have made a lot of research regarding this topic but I have tried their codes and I still don't know how to store the images in the cache to speed up the TableView.
Here is my code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! BuscarCellTableViewCell
if searchController.active {
cell.nombre.text = searchResults[indexPath.row].nombre
cell.marca.text = searchResults[indexPath.row].marca
if let url = NSURL(string: searchResults[indexPath.row].imagen) {
if let data = NSData(contentsOfURL: url) {
cell.imagen.image = UIImage(data: data)
}
}
}
else {
cell.nombre.text = productos[indexPath.row].nombre
cell.marca.text = productos[indexPath.row].marca
if let url = NSURL(string: productos[indexPath.row].imagen) {
if let data = NSData(contentsOfURL: url) {
cell.imagen.image = UIImage(data: data)
}
}
}
return cell
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
// Define the initial state (Before the animation)
cell.alpha = 0.25
// Define the final state (After the animation)
UIView.animateWithDuration(1.0, animations: { cell.alpha = 1 })
}
func getLatestLoans() {
let request = NSURLRequest(URL: NSURL(string: LoadURL)!)
let urlSession = NSURLSession.sharedSession()
let task = urlSession.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
let res = response as! NSHTTPURLResponse!
var err: NSError?
if error != nil {
println(error.localizedDescription)
}
// Parse JSON data
self.productos = self.parseJsonData(data)
// Reload table view
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
})
task.resume()
}
func parseJsonData(data: NSData) -> [Producto] {
var productos = [Producto]()
var error:NSError?
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary
// Return nil if there is any error
if error != nil {
println(error?.localizedDescription)
}
// Parse JSON data
let jsonProductos = jsonResult?["lista_productos"] as! [AnyObject]
for jsonProducto in jsonProductos {
let producto = Producto()
producto.nombre = jsonProducto["nombre"] as! String
producto.imagen = jsonProducto["imagen"] as! String
productos.append(producto)
}
return productos
}
How can I speed up my TableView when scrolling down the "products" from my JSON file?
Thanks in advance,
Regards.
Upvotes: 1
Views: 1370
Reputation: 1561
Regarding image loading performance (move it to background thread, store image into memory cache, etc). Here are duplicate questions:
display downloaded images in tableview
Upvotes: 0
Reputation: 38162
cellForRowAtIndexPath:
gets called for each new/reused cell rendering. You should not make server call in this method. This is how you should do it:
fetchImages
which can trigger NSOperations in background to fetch the images.Follow this and you should see desired improvement in your application scrolling behaviour.
EDIT: On OP Request:
Step 1: Expecting you to call below fetchImages
method as soon as model data is loaded from server. If you are loading data locally then call fetchImages
in loadView
:
- (void)fetchImages {
NSMutableArray *anImageURLsList = [NSMutableArray new]; // Assuming this contains your image URL list
if ([anImageURLsList count] > 0) {
__weak MyController *aBlockSelf = self;
[[MyImageFetchController sharedImageFetchController] addURLs:anImageURLsList withDescription:[self description] andCompletionBlock:^(NSMutableArray *iFailedURLs) {
dispatch_async(dispatch_get_main_queue(), ^{[UIApplication sharedApplication].networkActivityIndicatorVisible = NO; });
if (aBlockSelf) {
[[MyImageFetchController sharedImageFetchController].objectTokens seValue:[NSString stringWithFormat:@"%d", NO] forKey:[aBlockSelf description]];
[aBlockSelf performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:YES];
}
}];
[[MyImageFetchController sharedImageFetchController].objectTokens setValue:[NSString stringWithFormat:@"%d", YES] forKey:[self description]];
[MyImageFetchController sharedImageFetchController].delegate = self;
[[MyImageFetchController sharedImageFetchController] startOperations];
dispatch_async(dispatch_get_main_queue(), ^{[UIApplication sharedApplication].networkActivityIndicatorVisible = YES; });
}
}
- (void)refresh {
[self.tableView reloadData];
}
Step 2: Now lets write NSOperation
subclass to fetch images via operation queue. Below code covers only important aspect with respect to the discussion here. Here, I've given a delegate call back for image caching implementation.
- (void)addURLs:(NSMutableArray *)iURLs withDescription:(NSString *)iObjectDescription andCompletionBlock:(ImageFetchCompletionBlock)iImageFetchCompletionBlock {
self.urls = iURLs;
[self.objectTokens removeAllObjects];
self.imageFetchCompletionBlock = iImageFetchCompletionBlock;
self.objectDescription = iObjectDescription;
if (self.failedURLs) {
[self.failedURLs removeAllObjects];
}
}
- (void)urlFailed:(NSString *)iFailedURL {
@synchronized(self) {
if (iFailedURL) {
[self.failedURLs addObject:iFailedURL];
}
}
}
- (void)startOperations {
MyImageFetchOperation *anImageFetchOperation = nil;
NSMutableArray *anOperationList = [[NSMutableArray alloc] initWithCapacity:self.urls.count];
self.queue = [NSOperationQueue new];
for (NSString *aURL in [self.urls copy]) {
anImageFetchOperation = [[MyImageFetchOperation alloc] initWithImageURL:aURL delegate:self];
[anOperationList addObject:anImageFetchOperation];
}
[self.queue setMaxConcurrentOperationCount:3];
[self.queue addOperations:anOperationList waitUntilFinished:NO];
}
- (void)operationCompletedForURL:(NSString *)iImageURL {
@synchronized(self) {
[self.urls removeObject:iImageURL];
if ([self.urls count] == 0) {
if ([[self.objectTokens valueForKey:self.objectDescription] boolValue]) {
self.imageFetchCompletionBlock([self.failedURLs mutableCopy]);
} else {
dispatch_async(dispatch_get_main_queue(), ^{[UIApplication sharedApplication].networkActivityIndicatorVisible = NO; });
}
if (self.failedURLs && [self.failedURLs count] > 0) {
[self.failedURLs removeAllObjects];
}
self.queue = nil;
}
}
}
- (NSString *)cacheDirectoryForImages {
NSString *anImageCacheDirectory = kImageCacheDirectoryKey; // Your image cache directory
if (self.delegate && [self.delegate respondsToSelector:@selector(cacheDirectoryForImages)]) {
anImageCacheDirectory = [self.delegate cacheDirectoryForImages];
}
return anImageCacheDirectory;
}
Step 3: Now, lets write cellForRowAtIndexPath
. Here, I am using a custom cell which implements an imageView and also expecting a custom loading overlay. You can put your loading overlay here.
- (UITableViewCell *)tableView:(UITableView *)iTableView cellForRowAtIndexPath:(NSIndexPath *)iIndexPath {
NSString *cellType = @"defaultCell";
MyCustomTableViewCell *cell = (MyCustomTableViewCell *)[iTableView dequeueReusableCellWithIdentifier:cellType];
if (!cell) {
cell = [[MyCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:aCellType];
}
cell.textLabel.text = @“Dummy”; // Set your cell data
NSString *anImageURL = self.productos[iIndexPath.row][@“image"];
[cell.loadingOverlay removeView];
cell.loadingOverlay = nil;
// If image is present in Cache
if (anImageURL && [anImageURL existsInCache]) {
cell.imageView.image = [UIImage imageNamed:<Image from cache>];
} else if ([[[MyImageFetchController sharedImageFetchController].objectTokens valueForKey:[self description]] boolValue]) {
cell.imageView.image = [UIImage imageNamed:defaultImage];
cell.loadingOverlay = [MyLoadingOverlay loadingOverlayInView:aCell.imageView];
} else {
cell.imageView.image = [UIImage imageNamed:defaultImage];
}
return cell;
}
Upvotes: 3
Reputation: 217
for Lazy loading you should use SDWebImage Library . It provides you the catching of images & also provides the functionality to add loader when the image is loading for each cell.
Follow this link to integrate SDWebImage in your app.
https://github.com/rs/SDWebImage
Upvotes: 0