Reputation: 3391
I'm using coredata
. I successfully added the image in my coredata
but the problem when I want to display the list. I get this lagging problem when I scroll down the UITableView
. I read this post Loading image from CoreData at cellForRowAtIndexPath slows down scrolling. I still get the lag and now I'm trying to use the LazyLoadImage
but the problem is, they are not using viewdidload()
. I don't know this LazyLoadImage
. I want to learn more about Grand Central Dispatch and how this things work.
Here's my code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = @"imageCell";
NSManagedObject *coreData = [self.devices objectAtIndex:indexPath.row];
ImageCell *cell = (ImageCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[ImageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *imagex = [cache objectForKey:[NSNumber numberWithUnsignedInteger:indexPath.row]];
UIImage * rowImage = [UIImage imageWithData:[coreData valueForKey:@"image"]];
UIImageView * myImage = [[UIImageView alloc] initWithImage:rowImage];
myImage.contentMode = UIViewContentModeScaleAspectFill;
myImage.frame = CGRectMake(0, 0, 79, 56);
cell.name.text = [coreData valueForKey:@"name"];
if (imagex == nil) {
[cache setObject:myImage.image forKey:[NSNumber numberWithUnsignedInteger:indexPath.row]];
imagex = [cache objectForKey:[NSNumber numberWithUnsignedInteger:indexPath.row]];
}
if(imagex){
dispatch_async(dispatch_get_main_queue(), ^{
if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) {
//check that the relevant data is still required
UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath];
//get the correct cell (it might have changed)
[[correctCell imageView] setImage:imagex];
[correctCell setNeedsLayout];
}
});
}
});
return cell;
}
Does performance matter if i put my list in inside the controller? I'm not using xib here.
The problem here is just the first load. When it scroll down. Some code is familiar. I'm trying to solve it in my own, but no luck now. I already read the important of GCD in our app. Please help me to learn more about this.
UPDATE The images came from the camera I captured. I think I need to resize it.
Upvotes: 2
Views: 865
Reputation: 1367
If you are storing photos, videos or audio in your database (so, any potencially big file), it is HIGHLY recommended to use references to them and use the file system for managing the content specially if you are planning to have hundreds, maybe thousands of records or more.
Note: Don't forget to load the files asynchronously as well.
Something to remember: When you fetch the data everything takes memory...
..
Try this workaround:
That will do the trick :)
Upvotes: 1
Reputation: 1126
I will suggest to store the images in the Mobile directory than in the core data , As core data is not efficient to store images in it.
You can store the images the example code 1. Store All images in array called imageArray
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat: @"/%@",self.projectName ]];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error];
for (int i=0; i< self.imageArray.count; i++) {
NSString *savedImagePath = [dataPath stringByAppendingPathComponent:[NSString stringWithFormat: @"%i.png",i ]];
UIImage *image = [self.imageArray objectAtIndex:i];
NSData *imageData = UIImagePNGRepresentation(image);
if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath])
{
[[NSFileManager defaultManager] removeItemAtPath:savedImagePath error:&error];
}
[imageData writeToFile:savedImagePath atomically:NO];
//
}
3. Then you can store the address of the image in core data and easily retrieve from the the directory using
static NSString *simpleTableIdentifier = @"imageCell";
NSManagedObject *coreData = [self.devices objectAtIndex:indexPath.row];
ImageCell *cell = (ImageCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[ImageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
UIImage *imagex = [cache objectForKey:[NSNumber numberWithUnsignedInteger:indexPath.row]];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat: @"/%@",self.projectName ]];
NSString * imgPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat: @"/image%@",indexPath.row]];
NSData *imgData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imgPath]];
UIImage * rowImage = [[UIImage alloc] initWithData:imgData];
UIImageView * myImage = [[UIImageView alloc] initWithImage:rowImage];
myImage.contentMode = UIViewContentModeScaleAspectFill;
myImage.frame = CGRectMake(0, 0, 79, 56);
cell.name.text = [coreData valueForKey:@"name"];
if (imagex == nil) {
[cache setObject:myImage.image forKey:[NSNumber numberWithUnsignedInteger:indexPath.row]];
imagex = [cache objectForKey:[NSNumber numberWithUnsignedInteger:indexPath.row]];
}
Upvotes: 1
Reputation: 2746
By threading the creation of your image, you are not guaranteed that the image is returned to the cell faster than the UITableView
scrolls.
Your cells are reused, so I'm not sure you need this code
if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) {
//check that the relevant data is still required
UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath];
This can cause problems because if a cell is partially or was partially on the screen, you'll get a cell back that isn't visible to a user or one that is partially visible.
Your problem is the first load. The OS caches the image when you use setImage
so that there's no lag after the first load so a lot of your caching code has no effect since the first setting of the image requires initializing the object, then the OS caches it.
Your images are also huge, I'd downscale unless you need fullscreen on the iPad.
Upvotes: 1
Reputation: 5554
If all you have is 50 entries, you can load them all - in a background queue - and populate self.devices with the images. You can initiate that population in viewDidLoad, or whenever you need to refresh
Within cellForRowAtIndexPath(), you will need to check that the data has been populated - first time through it might not be for the top of the table - and display a 'waiting' image or message
Within your data load, you should call reloadData() every 10 records or so, and once at the end to make sure that you update the table view when data is available.
With this approach, you don't see the time taken to load any more than the first few entries in the table, and performance looks good to the user
Upvotes: 1
Reputation: 491
Why do you setNeedsLayout after setImage ? I do not think it is necessary.
Upvotes: 2