I would like to create a pdfReader with a collectionview. What I want is to have a collectionview with Thumbnails of the pdf to display. So I use this in the viewDidLoad (to avoid the fact that it will generate the thumbnail in the collectionview each time we go down or up). It is generated one time, and it is without lag :
Loading the thumbnail of the pdf in viewDidLoad:
- (void)viewDidLoad
...
coverPdf = [NSMutableArray new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Load image on a non-ui-blocking thread
NSString *pdfPath = nil;
NSURL *pdfUrl = nil;
CGPDFDocumentRef pdfRef = nil;
NSMutableArray *arr = [NSMutableArray new];
for (id cover in filePathsArray)
{
pdfPath = [categoryPath stringByAppendingPathComponent:cover];
pdfUrl = [NSURL fileURLWithPath:pdfPath];
pdfRef = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
[arr addObject:[self imageFromPDFWithDocumentRef:pdfRef]];
NSLog(#"first process");
}
coverPdf = [NSMutableArray arrayWithArray:arr];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[pdfCollectionView reloadData];
});
});
...
}
Generating the thumbnail:
- (UIImage *)imageFromPDFWithDocumentRef:(CGPDFDocumentRef)documentRef
{
CGPDFPageRef pageRef = CGPDFDocumentGetPage(documentRef, 1);
CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, CGRectGetMinX(pageRect),CGRectGetMaxY(pageRect));
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, -(pageRect.origin.x), -(pageRect.origin.y));
CGContextDrawPDFPage(context, pageRef);
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
Using the thumbnail:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ListPdfCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CollectionViewCell" forIndexPath:indexPath];
cell.productLabel.text = [filePathsArray objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Load image on a non-ui-blocking thread
dispatch_sync(dispatch_get_main_queue(), ^(void) {
// Assign image back on the main thread
if ([coverPdf count] > indexPath.row)
{
cell.pdfImage.image = [coverPdf objectAtIndex:indexPath.row];
}
});
});
return cell;
}
I have two problems with that method :
- The first one is that the thumbnail is taking too much time to appear. When it is loaded, it works well.
- The second problem, is that the memory is continually increasing and even if I close the viewcontroller, and I come in it, it seems that the memory is not released. If I close the viewcontroller and come in 9 or 10 times, if crash the app.
In conclusion, how can I create a collection view by loading the thumbnails of the pdfs in advance and how to avoid a crash with the memory increasing ?
Thanks in advance.
SOLUTION :
For the fact that it takes too much time to appear, I just replaced the DISPATCH_QUEUE_PRIORITY_BACKGROUND with the DISPATCH_QUEUE_PRIORITY_HIGH. It is much better.
For the memory leak, I used the CGPDFDocumentRelease() function just at the end of the loop like this, and all works like a charm :
for (id cover in filePathsArray)
{
pdfPath = [categoryPath stringByAppendingPathComponent:cover];
pdfUrl = [NSURL fileURLWithPath:pdfPath];
pdfRef = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
[arr addObject:[self imageFromPDFWithDocumentRef:pdfRef]];
CGPDFDocumentRelease(pdfRef);//Line added
NSLog(#"first process");
}
I have bad news for you. Memory leak is not your problem but iOS 10 memory management bug
There is a memory management bug in iOS 10.0.1 and 10.0.2 in the CGContextDrawPDFPage() function.
You can find details here http://www.openradar.me/28415289
Also you can find useful this discussion https://github.com/vfr/Reader/issues/166
In few words possible workaround is not to create
CGPDFPageRef pageRef = CGPDFDocumentGetPage(documentRef, 1);
each time but use one CGPDFPageRef for all of your pdf files. Then set it NULL when you not needs it anymore.
Creating thumbnails lag
And in terms of solution for creating thumbnails. I can only suggest not to do it in this VC at all but create thumbnail for each .pdf in the app at the moment when this .pdf added to the app (or on app launch in background service if all pdfs stored in the app bundle). Then you can save this previews as .jpg or .png files with names equal to pdf names or set relationships between pdf and preview in any database or other storage if you have it in your app.
Then just reuse this previews in your collectionView.
I have an array of images that changes to a random image with an IBAction attached to a button when pressed. When run on a simulator it runs fine but on a device it seems to crash after memory warnings. It also lags when the button is pressed. I want it to run smoothly, and not crash. I believe this has something to do with my array not releasing each image. Here is my array inside my buttons code.
-(IBAction)buttonPressed:(id)sender;
{
int ptr = arc4random() % 132;
NSArray* images = [[NSArray alloc] initWithObjects:#"17",#"29",#"55",#"400",#"Alcohol",#"Arianny",#"Anderson",#"Approach",#"Arab",#"Asian",#"Attitude",#"Attraction",#"Beckinsale",#"Blueberry",#"Brain",#"Break",#"Breakups",#"Burns",#"Buttocks",#"Charity",#"Check",#"Chicago",#"Chocolate",#"Coco",#"Coffee",#"College",#"Commit",#"Common",#"Confident",#"Cook",#"Count",#"Country",#"Couples",#"Courtship",#"Criminal",#"Cruz",#"Date",#"Date14",#"Deed",#"Degree",#"Dropped",#"Dushku",#"Dworaczyk",#"Eating",#"Emotion",#"Exercise",#"Fwb",#"Fantasies",#"Fitness",#"Flings",#"Flirt",#"Foot",#"Forget",#"Friendship",#"Frowning",#"Hum",#"Impression",#"Hair",#"Happiness",#"Hazel",#"Headache",#"Instant",#"Interest",#"Internet",#"Jacobs",#"January",#"Jimena",#"Jolie",#"Kalia",#"Kardashian",#"Kiss",#"Kissing",#"Krupa",#"Larissa",#"Latino",#"Laughter",#"Lip",#"London",#"Love",#"Love2",#"Love3",#"Love4",#"Math",#"Maximus",#"Melany",#"Memory",#"Men",#"Milian",#"Miller",#"Millions",#"Mind",#"Monica",#"Muscle",#"Partner",#"Naps",#"Negativity",#"Novels",#"Oral",#"Ossa",#"Pain",#"Positions",#"Productive",#"Proximity",#"Read",#"Reputation",#"Second",#"Sensitive",#"Serious",#"Shaking",#"Sleep2",#"Smile",#"Smoke",#"Smoke2",#"Smokers",#"Sneeze",#"Socks",#"Sold",#"Spot",#"Stimuli",#"Stone",#"Survey",#"Swell",#"Tattoo",#"Teacher",#"Teeth",#"Vickers",#"Violence",#"Wallet",#"Weight",#"Windmills.png",#"White",#"Women",#"Yawn",nil];
[imageView setImage:[UIImage imageNamed:[images objectAtIndex:ptr]]];
ptr++;
NSLog(#"button pressed");
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.images=nil;
}
- (void)dealloc {
[images release];
[adView release];
[super dealloc];
}
As I see your code is not running with ARC so when you create images array it is not deleted from memory until you call release. write release after you don't need your array anymore:
int ptr = arc4random() % 132;
NSArray* images = [[NSArray alloc] initWithObjects:#"17",#"29"];
[imageView setImage:[UIImage imageNamed:[images objectAtIndex:ptr]]];
ptr++;
[images release];
First off, use ARC, if you can.
You have 2 things leaking memory: the image and the array of image names. Since the image names are constant, you only need to create this array once.
Create ivars for the image and for the image name array:
UIImage *_image;
NSArray *_imageNames; // init this in your viewDidLoad:
Then, in your button press handler:
-(IBAction)buttonPressed:(id)sender;
{
int ptr = arc4random() % 132;
[_image release];
_image = UIImage imageNamed:_images[ptr]];
[imageView setImage:_image];
ptr++;
NSLog(#"button pressed");
}
Finally, release _imageNames:
- (void)dealloc
{
[_imageNames release];
// release everything else.
}
Again, you should really consider switching to ARC. You'll be glad you did.
You actually have two problems here, both surrounding this line:
NSArray* images = [[NSArray alloc] initWithObjects: ...strings omitted... ,nil];
The first is that the NSArray* at the beginning of the line declares a new local variable, images. This is separate from the property self.images that you try to erase in -viewDidUnload and release in -dealloc. Removing the NSArray* from the line will fix this issue, storing the array into the self.images property as you seem to intend.
That gives you a line like this:
images = [[NSArray alloc] initWithObjects: ...strings omitted... ,nil];
The second problem is that you re-create the images array each time you go through this method. That means that, even if you fixed the first problem, you would still be throwing away the old array without releasing it each time you passed through the method, so you'd still be leaking these arrays. There are a bunch of ways you could fix this, but the easiest one is probably to simply test if you already have an array and only create it if you haven't:
if(!images) {
images = [[NSArray alloc] initWithObjects: ...strings omitted... ,nil];
}
(Since all instances of this class have an identical list of image names, you could instead store the array in a static variable so it'd be shared between them—perhaps initialized by calling dispatch_once—but this isn't likely to make a difference unless you have many instances of this view controller on screen at the same time.)
I have a problem with loading an image from an url to display in a table. I currently have the following code to handle the image loading in a class that extends UITableViewCell:
- (void) initWithData:(NSDictionary *) data{
NSDictionary *images = [data objectForKey:#"images"];
__block NSString *poster = [images objectForKey:#"poster"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSURL *posterURL = [[NSURL alloc] initWithString:poster];
NSData *imageData = [NSData dataWithContentsOfURL:posterURL];
if (imageData != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
// 4. Set image in cell
self.backgroundImage.image = [UIImage imageWithData:imageData];
[self setNeedsLayout];
});
}
});
self.backgroundImage.image = [UIImage imageNamed:#"static"];
}
The initWithData method is called from the ViewController in the tableView:cellForRowAtIndexPath: delegate. Everything works as expected until i scroll. From what i read, the TableView cells are recycled and because the images are being loaded async, i get rows with wrong images. Also, the images are not cached and are loaded again whenever the cell is displayed.
Eg: Scroll to the middle and immediately scroll back up. The first cell will have the image that's corresponding to the middle cell that didn't get to finish loading.
Any help or suggestions? Thank you very much :)
First of all as the comment mentioned, I would definitely recommend using an existing framework/component to do this job.
The best candidates are probably:
https://github.com/rs/SDWebImage
https://github.com/enormego/EGOImageLoading
OR if you also want a general networking library
https://github.com/AFNetworking/AFNetworking
That said, if you still want to try it on your own, you would probably want to implement caching with an NSMutableDictionary using the indexPath as the key, and the image as the value.
Assuming you have an initialized instance variable NSMutableDictionary *imageCache
In your cellForRowAtIndexPath method, before attempting to do any image loading, you would check to see if your cache already has an image for this index by doing something like this
if(! imageCache[indexPath])
{
// do your web loading here, then once complete you do
imageCache[indexPath] = // the new loaded image
}
else
{
self.backgroundImage.image = imageCache[indexPath];
}
In my split view iPad app the default detail view loads a random image from an array and does this any time the user goes back to that view. The app loads fine with that view and I can go to another view fine. The problem is that if I go back to that view, sometimes it will crash and sometimes it will crash if I select another view after going back to the default view. I do not show any leaks when I run the leaks tool and I don't show anything in the log every time a crash occurs. I did receive a "Received Memory Warning" log once so it the crashes must have something to do with a leak somewhere, I'm just not sure where. I am using ARC. Any ideas?
Here is my viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImage *agelity = [UIImage imageNamed:#"Agelity"];
UIImage *agelity2 = [UIImage imageNamed:#"Agelity2"];
UIImage *biltmore = [UIImage imageNamed:#"Biltmore"];
UIImage *biltmore2 = [UIImage imageNamed:#"Biltmore2"];
UIImage *biltmore3 = [UIImage imageNamed:#"Biltmore3"];
UIImage *choice = [UIImage imageNamed:#"Choice"];
UIImage *enterprise = [UIImage imageNamed:#"Enterprise"];
UIImage *enterprise2 = [UIImage imageNamed:#"Enterprise2"];
UIImage *grainger = [UIImage imageNamed:#"Grainger"];
UIImage *grainger2 = [UIImage imageNamed:#"Grainger2"];
UIImage *greatWolf = [UIImage imageNamed:#"Great_Wolf"];
UIImage *greatWolf2 = [UIImage imageNamed:#"Great_Wolf2"];
UIImage *officeDepot = [UIImage imageNamed:#"Office_Depot1"];
UIImage *officeDepot2 = [UIImage imageNamed:#"Office_Depot2"];
UIImage *officeDepot3 = [UIImage imageNamed:#"Office_Depot3"];
UIImage *sams = [UIImage imageNamed:#"Sams"];
UIImage *sams2 = [UIImage imageNamed:#"Sams2"];
NSMutableArray *benefitAds = [[NSMutableArray alloc]initWithObjects:agelity, agelity2, biltmore, biltmore2, biltmore3, choice, enterprise, enterprise2, grainger, grainger2, greatWolf, greatWolf2, officeDepot, officeDepot2, officeDepot3, sams, sams2, nil];
int randomIndex = arc4random() % [benefitAds count];
adImage.image = [benefitAds objectAtIndex:randomIndex];
[self configureView];
}
EDIT: I am trying to use the suggestion of using imageWithData instead of imageNamed so I'm doing this:
NSData *agelityData = [NSData dataWithContentsOfFile:#"Agelity"];
UIImage *agelity = [UIImage imageWithData:agelityData];
but now the app crashes at launch with on the line:
int randomIndex = arc4random() % [benefitAds count];
with:
Thread 1: EXC_ARITHMETIC(code=EXC_I386_DIV, subcode=0x0)
When I run it on my device instead of the simulator, I get this:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 3051310543 beyond bounds for empty array'
EDIT: I set an exception breakpoint because I'm getting an exc_bad_access code=1 error. It looks like the app randomly crashes at times when I'm changing the detail view. I guess I'll create a new question.
Thanks for all the help!
I don't know if it is exactly this that is causing the crash (high chances that it might be), but I really suggest you to don't store all images inside your array.
A better approach would be store the name of the images, and the allocate just one UIImage, with the name selected.
See this:
- (void)viewDidLoad
{
NSMutableArray *benefitAds = [[NSMutableArray alloc]initWithObjects:#"Agelity", #"Agelity2", #"Biltmore", #"Biltmore2", #"Biltmore3", #"Choice", #"Enterprise", #"Enterprise2", #"Grainger", #"Grainger2", #"Great_Wolf", #"Great_Wolf2", #"Office_Depot1", #"Office_Depot2", #"Office_Depot3", #"Sams", #"Sams2", nil];
int randomIndex = arc4random() % [benefitAds count];
if(randomIndex < [benefitAds count]) {
adImage.image = [UIImage imageNamed:[benefitAds objectAtIndex:randomIndex]];
[self configureView];
}
else
{
//error message
}
}
Please give some feedback it worked or not.
EDIT:
Try to check if the random number get is really a valid index before using it.
imageNamed: uses the internal cache which does not empty itself on memory warnings. Try imageWithData
- (void)didReceiveMemoryWarning {
if([self isViewLoaded] && self.view.window == nil) {
self.view = nil;
}
[super didReceiveMemoryWarning];
}
Any idea why this crashes? What am I doing wrong?
Thanks!
-(IBAction)animationOneStart {
NSMutableArray* arrayOfImages = [[NSMutableArray alloc]initWithCapacity:10];
for(int count = 1; count <= 22; count++)
{
NSString *path = [NSString stringWithFormat:#"testDance3_%03d.jpg", count];
UIImage* img = [[UIImage alloc]initWithContentsOfFile:path];
[arrayOfImages addObject:img];
[img release];
}
loadingImageView.animationImages = arrayOfImages;
[arrayOfImages release];
loadingImageView.animationDuration = 3;
loadingImageView.animationRepeatCount = 1; //Repeats indefinitely
[loadingImageView startAnimating];
loadingImageView.animationImages = nil;
[loadingImageView release];
}
Try initializing your NSMutableDictionary with capacity for the same quantity of object's you'll be storing in it. If that doesn't work, I'd try to comment out these two lines:
//loadingImageView.animationImages = nil;
//[loadingImageView release];
In the scope of this code, your other calls appear balanced. But we can't see what's happening inside loadingImageView, and so my guess is that either the loadingImageView itself or it's animations are being released prematurely.
I can't see enough code confirm this, but a couple things that are suspicious are here:
[loadingImageView startAnimating];
loadingImageView.animationImages = nil;
[loadingImageView release];
So, while the animation is running, you are releasing the images that are being animated? Or the view which, itself, is animating? Probably one or the other or both is the problem.
If the animation is supposed to run indefinitely, you are going to need to keep the view around indefinitely. And if it stops eventually, you should release it after it stops.
You call to addObject will fail when nil is passed. The UIImage could be nil when your system run out of memory and cannot allocate more. When that happens depends on how large your images are, but basically you can be sure that sooner of later use of loadingImageView.animationImages combined with allocating all your UIImage objects at the same time will cause your app to crash. See also playing-looping-images-sequence-in-uiview. You basically need a different approach that does not hold all the images in memory at the same time, the uncompressed images consume way too much memory.