2

I am creating a file like so:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];

filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory,PDFFile];

if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
     [[NSFileManager defaultManager] createFileAtPath:filePath contents:dataBytes attributes:nil];
}

_previewItemURL = [NSURL fileURLWithPath:filePath];

and I am displaying it in an UIDocumentInteractionController like so:

if (_previewItemURL) {

    UIDocumentInteractionController *documentInteractionController =[UIDocumentInteractionController interactionControllerWithURL:_previewItemURL];
    documentInteractionController.delegate = self;

    [documentInteractionController presentPreviewAnimated:YES];
}

However, sometimes the PDF file I am saving off bytes are way too big, sometimes 5.5MB, which causes UIDocumentInteractionController to some time to load the PDF. I was doing some reading here https://stackoverflow.com/a/27863508/979331 and it is suggested to create a 'mapped' file. My question is I don't understand how to create one. I have been googling like crazy for the past two days and I just don't understand it.

I think the issue is with the PDF because I tried this:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *pgnPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.pdf", @"example"]];

    //filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory,PDFFile];

    //if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {

    NSString *newFile = [[NSBundle mainBundle] pathForResource:@"example" ofType:@"pdf"];

    [[NSFileManager defaultManager] copyItemAtPath:newFile toPath:pgnPath error:&error];

    //[[NSFileManager defaultManager] createFileAtPath:filePath contents:dataBytes attributes:nil];
//}

//_previewItemURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"pdf"];

_previewItemURL = [NSURL fileURLWithPath:pgnPath];

with a PDF that was 5.5MB and everything seemed fine, could the issue be with how I getting the PDF? I am getting the bytes from a web service, here is my call:

task = [dataSource.areaData GetPDFFileTestTwo:[NSString stringWithFormat:@"%@",encodedUrlStr] completion:^(NSData *data, NSURLResponse *response, NSError *error) {

    NSError *myError;
    NSArray *tableArray = [[NSArray alloc]initWithArray:[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&myError]];

    NSData *dataBytes;

    for (NSDictionary *dict in tableArray) {
        NSString *base64 = dict[@"data"];
        dataBytes = [[NSData alloc] initWithBase64EncodedString:base64 options:0];
    }

    if (dataBytes) {

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

        filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory,PDFFile];

        if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
            [[NSFileManager defaultManager] createFileAtPath:filePath contents:dataBytes attributes:nil];
        }

        _previewItemURL = [NSURL fileURLWithPath:filePath];

        if (_previewItemURL) {

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            UIDocumentInteractionController *documentInteractionController =[UIDocumentInteractionController interactionControllerWithURL:_previewItemURL];

            documentInteractionController.delegate = self;

            dispatch_async(dispatch_get_main_queue(), ^{

            [documentInteractionController presentPreviewAnimated:YES];

        });


    });
}


            }

        }];

And here is GetPDFFileTestTwo

-(NSURLSessionDataTask *)GetPDFFileTestTwo:(NSString *)PDFFile completion:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler{

    NSString *FileBrowserRequestString = [NSString stringWithFormat:@"%@?PDFFile=%@",kIP,PDFFile];
    NSURL *JSONURL = [NSURL URLWithString:FileBrowserRequestString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:JSONURL];
    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

        if(completionHandler)
        {
            completionHandler(data, response, error);
        }

    }];

    [dataTask resume];

    return dataTask;

}

kIP is a string and that is the web service URL

Meloman
  • 3,558
  • 3
  • 41
  • 51
user979331
  • 11,039
  • 73
  • 223
  • 418
  • Why are you attempting to present the `UIDocumentInteractionController` on a background queue. You must never do that. All UI updates must be done on the main queue. – rmaddy Mar 01 '18 at 18:05
  • Oops, sorry about that, I was trying something there...I updated my code – user979331 Mar 01 '18 at 18:07
  • 1
    Your use of the background queue here is pointless. If your goal is to let the large PDF be presented quicker, simply creating the document controller in the background isn't going to help any. – rmaddy Mar 01 '18 at 18:12
  • Okay, I was trying it that way, but if its pointless ill remove it. – user979331 Mar 01 '18 at 18:20
  • I removed the background queue, now lets focus on the goal of let the large PDF be presented quicker. – user979331 Mar 01 '18 at 18:22
  • any ideas @rmaddy ? – user979331 Mar 01 '18 at 19:46
  • It's unlikely that a mapped file, even if it were possible in this case (which doubt very much), would make things faster. The point of a mapped file is to reduce memory usage, not improve read/parse performance. It seems unlikely that you're going to find a magic bullet that will make a very large and complex PDF fast (if just mapping the file were a straightforward and dramatic improvement, then I expect that UIKit and Foundation would already be doing this internally). Start by splitting up the problem to see were you bottleneck is. Is it the PDF itself or the interaction controller… – Rob Napier Mar 05 '18 at 14:52
  • Test displaying the document directly using Apple's PDFKit and the very powerful third-party framework PSPDFKit (https://pspdfkit.com). Look into how you're building the PDF. Is it more complex than it needs to be? (It is very possible to create PDFs that are unreasonably complicated internally; PDF is basically code). Cheat. Can you create a pre-rendered preview image to display while you're loading the PDF? But I don't think just adding the `.dataReadingMapped` flag somewhere is going to solve your problems. – Rob Napier Mar 05 '18 at 14:55
  • Updated my question – user979331 Mar 08 '18 at 03:44

2 Answers2

1

You're not "creating" a mapped file. You're reading it into NSData as mapped to the bytes in the file. That means, that in-memory NSData bytes are underneath mapped to bytes in the file.

Here is a way to read a file as mapped:

https://github.com/atomicbird/atomictools/blob/master/NSData%2BreallyMapped.h

If you can't pass NSData to the controller for preview, mapping makes no sense. Even if you can, you have to be sure that controller won't copy your data before it is used.

Consider using PDFKit framework, where you can initialize PDFDocument with NSData and display it in PDFView.

Eugene Dudnyk
  • 5,553
  • 1
  • 23
  • 48
0

Your questions;

  1. create a 'mapped' file. My question is I don't understand how to create one.

So let me point out that UIDocumentInteractionController has no inputs that accept NSData, so you wouldn't be able to create a memory mapped file to use with it. I also examined the header for any other clues, and didn't find any. https://developer.apple.com/documentation/uikit/uidocumentinteractioncontroller

In looking for a solution, I saw that QLPreviewController mentions 'data' but I find it didn't accept NSData either. https://developer.apple.com/documentation/quicklook/qlpreviewcontroller

I finally settled on WKWebView in WebKit, which does support using NSData, that is Memory Mapped, and loads quite quickly with a 15 MB PDF full of pictures and text I made.

I created a project with the large PDF and tried it out, it works fine. Please feel free to examine it. https://github.com/eSpecialized/PDFDocViewerMapped

How 'mapped' works;

Anything that can take NSData can use a mapped file unless it needs the entire set of data at once. A PDF with a single image vs a multipage PDF are good examples of what can't be mapped and what can be mapped.

    NSError *errors;
    NSData *docFileData = [NSData dataWithContentsOfFile:docFileWithPath options:NSDataReadingMappedAlways error:&errors];

Options for NSData mapped;

https://developer.apple.com/documentation/foundation/nsdatareadingoptions?language=objc

NSDataReadingMappedIfSafe // Hint to map the file in if possible and safe
NSDataReadingMappedAlways // Hint to map the file in if possible. This takes precedence over NSDataReadingMappedIfSafe if both are given.
  1. could the issue be with how I getting the PDF?

Fetching a PDF remotely means you must download the document. Think of what you are doing, fetching the Pdf, saving it locally, then opening it in the UIDocument Interaction controller which takes URL's and not NSData.

I hope this meets your criteria for a solution;

  • UIDocumentInteractionController limitations with requiring URL's

  • WKWebView - allows using NSData that can be memory mapped.

  • 3rd party options for PDF viewing - anything that can accept NSData is a candidate for use. Look for CocoaPods and on GitHub for PDF, iOS PDF, etc.

  • the more I work on my issue, the more its coming clear to me, it appears I don't need a mapped file. I am looking for away to load a URL from my apps documents folder to UIDocumentationController faster, I think its an hardware issue as the iPad I have to a mini and only allows me to update to iOS 9 – user979331 Mar 08 '18 at 19:57
  • I believe the way I am getting the data for the URL is fine (Web service gets the bytes, I put those bytes into NSData and then NSData to write to a URL path) I think the issue is with the way I am presenting the document and I think the issue is with UIDocumentationController, I even put in a local file and loaded it UIDocumentationController and the result was the same, slow to present (and sometimes crashes) So is my hardware just too old or am I presenting the document wrong and is there a better way to present the document? I have to use either UIDocumentInteraction or QLPreviewController – user979331 Mar 08 '18 at 20:01
  • The size of files I worked with were 100MB+ of scanned multipage documents, the larger you go the more you need to find a solution to use memory mapped files. UIDocumentInteractionController handles files internally, I need debug info to assist with anything like that. Please post such requests in a new question if you need assistance. Share the link with me as well. Also be aware that some PDF's are not compliant or created properly for the Apple iOS components to be able to read it, you may want to check that. For this question, I think we can close it. – Bill Thompson Mar 10 '18 at 00:16