My tvOS app is set up with the main capability being drawing PDFs of music sheets that are already embedded into the app itself. There are multiple options here, as it can possibly only display certain verses of the song. The code I use for this is:
- (void)drawDocument:(CGPDFDocumentRef)pdfDocument
{ NSLog(@"draw document");
// Get the total number of pages for the whole PDF document
int totalPages= (int)CGPDFDocumentGetNumberOfPages(pdfDocument);
if ([[self.arrayOfVerses firstObject] isEqualToString:@"allverses"]) {
//This is if all verses have been picked and it will do the entire PDF
NSLog(@"draw document all verses %d", totalPages);
self.pages = totalPages;
self.subtractingPages = totalPages - 1;
NSMutableArray *pageImages = [[NSMutableArray alloc] init];
// Iterate through the pages and add each page image to an array
for (int i = 1; i <= totalPages; i++) {
// Get the first page of the PDF document
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, i);
CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
// Begin the image context with the page size
// Also get the grapgics context that we will draw to
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate the page, so it displays correctly
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, pageRect, 0, true));
// Draw to the graphics context
CGContextDrawPDFPage(context, page);
// Get an image of the graphics context
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[pageImages addObject:image];
}
// Set the image of the PDF to the current view
[self addImagesToScrollView:pageImages];
}
else {
//Only a certain number of verses
self.pages = [self.arrayOfVerses count];
self.subtractingPages = self.pages - 1;
NSLog (@"total pages %ld", (long)self.pages);
NSMutableArray *pageImages = [[NSMutableArray alloc] init];
[pageImages removeAllObjects];
//Getting slide numbers to be used
NSString *text = self.selectedCountry;
NSString *substring = nil;
NSRange parenRng = [text rangeOfString: @"(?<=\\().*?(?=\\))" options: NSRegularExpressionSearch];
if ( parenRng.location != NSNotFound ) {
substring = [text substringWithRange:parenRng];
NSLog(@"Substring %@", substring);
}
// Iterate through the pages and add each page image to an array
for (int i = 1; i <= totalPages; i++) {
// Get the first page of the PDF document
NSPredicate *valuePredicate=[NSPredicate predicateWithFormat:@"self.intValue == %d",i];
if ([[self.arrayOfVerses filteredArrayUsingPredicate:valuePredicate] count]!=0) {
// FOUND
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, i);
CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
// Begin the image context with the page size
// Also get the grapgics context that we will draw to
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate the page, so it displays correctly
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, pageRect, 0, true));
// Draw to the graphics context
CGContextDrawPDFPage(context, page);
// Get an image of the graphics context
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[pageImages addObject:image];
}
else {
//NOT FOUND
}
}
// Set the image of the PDF to the current view
[self addImagesToScrollView:pageImages];
}
}
-(void)addImagesToScrollView:(NSMutableArray*)imageArray {
int heigth = 0;
for (UIImage *image in imageArray) {
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
imgView.contentMode = UIViewContentModeScaleAspectFit;
imgView.frame=CGRectMake(0, heigth-60, 1920, 1080);
[_scrollView addSubview:imgView];
heigth += imgView.frame.size.height;
}
}
-(void)viewDidLayoutSubviews {
_scrollView.contentSize = CGSizeMake(1920, 1080*self.pages);
}
There is however, one section where a PDF needs to be downloaded, and then drawn. I am using Back4app as the host of this, and the average PDF for this is 420kb. I currently have it do this:
- (void)sermonTime {
if (theImageView.hidden == NO) {
theImageView.hidden = YES;
}
if ([self.entry[@"SermonPresIncluded"] isEqualToString:@"NO"]) {
//there are some instances where there is no PDF to download, following method just displays black screen
[self blankTime];
}
else {
NSLog(@"SermonTime");
PFFileObject *thumbnail = self.entry[@"SermonPDF"];
NSString *tempDir = NSTemporaryDirectory();
NSString *pdfPath = [[tempDir stringByAppendingPathComponent:[self.entry valueForKey:@"DateOfService"]] stringByAppendingString:@".pdf"];
[thumbnail getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (error) {
NSLog(@"Error downloading PDF: %@", error);
[self blankTime];
} else {
[imageData writeToFile:pdfPath atomically:YES];
// Use completion block to signal that the PDF is ready to display
sermonPDFURL = [NSURL fileURLWithPath:pdfPath];
[self performSelector:@selector(doitnowpdf) withObject:NULL afterDelay:5];
self.view.backgroundColor = [UIColor blackColor];
}
}];
}
}
-(void)doitnowpdf {
self.arrayOfVerses = @[@"allverses"];
CGPDFDocumentRef pdfDocument = [self openPDF:sermonPDFURL];
[self drawDocument:pdfDocument];
}
I had run into issues where it seemed the problem may be that it was trying to draw the PDF before it was finished downloading, which is why I put a 5 second delay on there. However, it still will mess up, even though the Logs show that it knows there's only 1 page in the PDF at the start of drawing it, so I know it's fully downloaded. When this happens, the previous song's PDF stays on the screen. If I try to execute the code to go to the next PDF, it goes to the one AFTER what's supposed to be downloaded, and if I go back, it then draws it properly. I'm desperate for any way to get this done right.
It sounds like the issue you are facing is related to an asynch programming one.
The getDataInBackgroundWithBlock method that downloads the PDF is asynch, meaning that it runs in background and is not blocking the main thread. This is okay as it prevents the freezing effect in the app, however, this means that the code that renders the PDF is executed prior the PDF itself is ready, which could be the issue here.
The 5-secs is a workaround but the condition of download in 5 secs is not a warranty that this is enough to have it downloaded always as depends on the network conditions.
I would recommend a completion block that is executed when the PDF is ready. You can do the next:
- (void)sermonTime {
if (theImageView.hidden == NO) {
theImageView.hidden = YES;
}
if ([self.entry[@"SermonPresIncluded"] isEqualToString:@"NO"]) {
//there are some instances where there is no PDF to download, following method just displays black screen
[self blankTime];
}
else {
NSLog(@"SermonTime");
PFFileObject *thumbnail = self.entry[@"SermonPDF"];
NSString *tempDir = NSTemporaryDirectory();
NSString *pdfPath = [[tempDir stringByAppendingPathComponent:[self.entry valueForKey:@"DateOfService"]] stringByAppendingString:@".pdf"];
[thumbnail getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (error) {
NSLog(@"Error downloading PDF: %@", error);
[self blankTime];
} else {
[imageData writeToFile:pdfPath atomically:YES];
NSURL *pdfURL = [NSURL fileURLWithPath:pdfPath];
[self drawPDFWithURL:pdfURL completion:^{
self.view.backgroundColor = [UIColor blackColor];
}];
}
}];
}
}
and drawPDFWithURL is like this
- (void)drawPDFWithURL:(NSURL *)url completion:(void (^)(void))completionBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL((__bridge CFURLRef)url);
size_t pageCount = CGPDFDocumentGetNumberOfPages(pdfDocument);
if (pageCount == 0) {
NSLog(@"Error: PDF has no pages");
return;
}
// Draw the first page of the PDF
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdfDocument, 1);
CGRect pageRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);
UIGraphicsBeginImageContextWithOptions(pageRect.size, YES, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, pageRect);
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawPDFPage(context, pdfPage);
UIImage *pdfImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdfDocument);
// Display the PDF image on the screen
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = pdfImage;
if (completionBlock) {
completionBlock();
}
});
});
}
The drawPDFWithURL:completion: method takes the URL and a completion block
as parameters. The PDF is rendered on the background thread using the GCD (Grand Central Dispatch). Once the PDF is ready, the completion block is executed on the main thread.
Remember to initialize correctly your image view for drawing the PDF, add this in your drawDocument method:
// Configure image view to display PDF content
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.backgroundColor = [UIColor blackColor];
imageView.clipsToBounds = YES;
imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
imageView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
imageView.userInteractionEnabled = YES;
Also, ensure that the image view is added to the view hierarchy and is properly positioned in the view. You can add the following line to add the image view to the view hierarchy:
[self.view addSubview:imageView];
If you are still having issues with the image view not displaying PDF content, you can try creating a new image view and adding it to the view hierarchy instead of using the existing one.