Drawing images to PDF in a loop, the first image is being drawn each time in iOS 15 only

So we have an app that has been working for a very long time. It is generating a PDF that has a section for crew members that will list details of each crew member including a signature image.

In iOS 15, the signature of the first crew member is being drawn for all crew members. Still working fine in other version of iOS.

Here is the code looping through each crew member:

for (ShiftCrew *crew in delegate.pcr.shift.shiftcrews) {

        NSString *tempSignatureFile;
        NSString *crewMemberName;

        tempSignatureFile = [Utils getFullPathForFile:crew.signature.fileName];
        crewMemberName = [NSString stringWithFormat:@"%@, %@", crew.lastName, crew.firstName];
        nextY = [self handleCrewMember:pdfContext andCrewMember:crewMemberName andSignatureFile:tempSignatureFile andPosition:position andBaseY:nextY];

}

Code where the signatures etc are being drawn:

if ([signatureFile length] > 0) {
        
            UIImage *myUIImage;
        
            if (self.restricted) {
                myUIImage = [UIImage imageNamed:@"Restricted Signature Image.png"];
            }
            else {
                myUIImage = [EncryptionFunctions openEncryptedImage:signatureFile];
            }
        
            CGContextDrawImage (pdfContext, CGRectMake(238, nextY - 21, 114, 28), myUIImage.CGImage);

        }

Utils getFullPathForFile just appends the passed in file name to the path to the Documents folder.

When I debug, I have verified that the signatureFile string is the correct path to the individual signature file image. To troubleshoot, right before CGContextDrawImage, I have inserted the following code to output the image files to an unencrypted png file:

NSString *filePath = [Utils getFullPathForFile:[NSString stringWithFormat:@"%@.png", crewMember]];
      [UIImagePNGRepresentation(myUIImage) writeToFile:filePath atomically:YES];

The resulting files are correct and different from each other.

Administrator, admin:

account, Test:

What actually shows in the PDF:

A few things I have tried:

  1. Converting to CIImage and then to CGImage
  2. Using drawInRect on the UIImages instead of drawing from the CGImages.
  3. Hard coded the different images based on the crew member names.

It does print a different image if I hard code drawing the Restricted Signature Image.png file that is in the bundle for one of the crew members, but that's not too helpful in figuring out how to make this work so far.

I tried to create a new project that just generates a PDF that draws the two signature files. It works fine. I also in the same project, had a separate function that similarly generates a PDF with the two signature files and it also works fine. However, as this app is quite large and old, there is a lot of legacy code, so it is hard to extract and isolate the code that can reproduce this issue.

Anyone have any suggestions on troubleshooting this? Things to look into, or things to try?

What is driving me crazy is how this code:

NSString *filePath = [Utils getFullPathForFile:[NSString stringWithFormat:@"%@.png", crewMember]];
      [UIImagePNGRepresentation(myUIImage) writeToFile:filePath atomically:YES];
       
      CGContextDrawImage (pdfContext, signatureRect, myUIImage.CGImage);

Saves two different images, but draws the same image twice. And only in iOS 15.

Thanks for any help.

edit - tried adding new code but I guess comments aren't made for that.

I don't see how to edit my original question, so posting as an answer since I wanted to show better code samples.

I was able to create a new project (objc, storyboard) and put the following code in the viewcontroller and replicate the issue:


@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self createPDF];
}

-(void)drawPageNumbersOnPDFFile:(NSString *)pdfFile {
    
    NSURL *pdfUrl = [NSURL fileURLWithPath:pdfFile];
    CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
    NSMutableData* data = [NSMutableData data];
    UIGraphicsBeginPDFContextToData(data, CGRectZero, nil);
    int currentPage;
    int totalPages = 1;
    UIFont *font = [UIFont fontWithName:@"Helvetica" size:20];
    
    for (size_t page = 1; page <= totalPages; page++) {
        
        //    Get the current page and page frame
        CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdf, page);
        const CGRect pageFrame = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
        
        UIGraphicsBeginPDFPageWithInfo(pageFrame, nil);
        
        //    Draw the page (flipped)
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextSaveGState(ctx);
        CGContextScaleCTM(ctx, 1, -1);
        CGContextTranslateCTM(ctx, 0, -pageFrame.size.height);
        CGContextDrawPDFPage(ctx, pdfPage);
        CGContextRestoreGState(ctx);
        
        // draw page number here
        currentPage = page;
        NSString *temp;
        
        temp = [NSString stringWithFormat:@"Page %d of %d",currentPage, totalPages];
        UIGraphicsPushContext(ctx);
        [temp drawAtPoint:CGPointMake(0, 0) withAttributes:@{ NSFontAttributeName: font }];
        UIGraphicsPopContext();
        
    }
    
    UIGraphicsEndPDFContext();
    
    CGPDFDocumentRelease(pdf);
    pdf = nil;
    
    [data writeToFile:pdfFile atomically:YES];
    
}

- (void)createPDF {
    CGRect pageRect = CGRectMake(0, 0, 612, 792);
    NSString *filename = [self getFullPathForFile:@"Test.pdf"];
    CGContextRef pdfContext = [self getPDFContextWithPageRect:pageRect andFilename:filename];
    CGContextBeginPage (pdfContext, &pageRect);


    NSArray *imageNames = [NSArray arrayWithObjects:@"ImageX.png", @"ImageY.png", nil];
    
    for (int i = 0; i < [imageNames count]; i++) {
        NSString *imageName = [imageNames objectAtIndex:i];
        double y = i * 21;
        [self handleImage:imageName andY:y andPDFContext:pdfContext];
    }
    
    CGContextEndPage (pdfContext);
    CGContextRelease (pdfContext);
    
    [self drawPageNumbersOnPDFFile:filename];
}

-(void)handleImage:(NSString *)imageName andY:(CGFloat)y andPDFContext:(CGContextRef )pdfContext {
    UIImage *image = [UIImage imageNamed:imageName];
    CGRect signatureRect = CGRectMake(238,y, 114, 28);
    CGContextDrawImage (pdfContext, signatureRect, image.CGImage);
}

-(NSString *)getFullPathForFile:(NSString *)fileName {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
    
    return  filePath;
}

-(CGContextRef)getPDFContextWithPageRect:(CGRect)pageRect andFilename:(NSString *)filename {
    
    // This code block sets up our PDF Context so that we can draw to it
    CGContextRef pdfContext;
    CFStringRef path;
    CFURLRef url;
    CFMutableDictionaryRef myDictionary = NULL;
    // Create a CFString from the filename we provide to this method when we call it
    path = CFStringCreateWithCString (NULL, [filename UTF8String],
                                      kCFStringEncodingUTF8);
    // Create a CFURL using the CFString we just defined
    url = CFURLCreateWithFileSystemPath (NULL, path,
                                         kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    // This dictionary contains extra options mostly for 'signing' the PDF
    myDictionary = CFDictionaryCreateMutable(NULL, 0,
                                             &kCFTypeDictionaryKeyCallBacks,
                                             &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(myDictionary, kCGPDFContextTitle, CFSTR("My PDF File"));
    CFDictionarySetValue(myDictionary, kCGPDFContextCreator, CFSTR("My Name"));
    // Create our PDF Context with the CFURL, the CGRect we provide, and the above defined dictionary
    pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary);
    // Cleanup our mess
    CFRelease(myDictionary);
    CFRelease(url);
    // Done creating our PDF Context, now it's time to draw to it
    
    return pdfContext;
    
}

@end

Upon launching, it generates a file named Test.pdf in the documents folder. It should draw the two different images that I have attached which I included in the project. It should work with any images. The issue appears to be when drawPageNumbersOnPDFFile is called. Removing the call to this function generates a PDF with the two different images as expected.

The reason for that function is because it was required that we have the page numbers at the bottom with the format 'Page 1 of 10', so we do it after all pages have been created so that we know the total page count. Not sure what in that function could be causing the change in the second image.

Thanks

If you are seeing different (unexpected) behavior on iOS 15, that could be a regression. Please file a bug report on Feedback Assistant, and attach a sample repro project.

In your example, what are ImageX.png", ImageY.png (X and √) ? Or √ and X ?

To avoid the problem, could you proceed in 2 phases:

  • Create a temp pdf, with only page number (not page n / N) or a dummy N
  • Then you know the number of pages
  • Create the final pdf with the full footer.

Note: even if this worked, a bug report is needed as you say it is a regression from iOS 14.

I am experiencing this same issue.

@JTForte

If we want to still have the total page count as well then we will go with your suggestion of just recreating the final pdf after knowing the total page count.

That's probably the best solution as for now.

Note that you are not the only one to face the problem: https://developer.apple.com/forums/thread/691512

Which means problem is not with your code most likely.

You should file a bug report.

I think this is fixed in 15.2. Can someone please confirm?

Drawing images to PDF in a loop, the first image is being drawn each time in iOS 15 only
 
 
Q