Szybka i łatwa przeglądarka plików PDF na iPhone'a / iPada / iOS - porady i wskazówki?

368

Ostatnio pojawiło się wiele pytań dotyczących rysowania plików PDF.

Tak, możesz bardzo łatwo renderować pliki PDF za pomocą, UIWebViewale to nie daje wydajności i funkcjonalności, których można oczekiwać od dobrej przeglądarki plików PDF.

Możesz narysować stronę PDF w CALayer lub UIImage . Apple ma nawet przykładowy kod, który pokazuje, jak narysować duży plik PDF w powiększalnym widoku UIScrollview

Ale wciąż pojawiają się te same problemy.

Metoda UIImage:

  1. Pliki PDF UIImagenie są skalowane optycznie, podobnie jak podejście warstwowe.
  2. Procesor i pamięć uderzyły przy generowaniu UIImagesz PDFcontext limitów / uniemożliwiają użycie go do tworzenia renderowania w czasie rzeczywistym nowych poziomów powiększenia.

Metoda CATiledLayer:

  1. Znaczny narzut (czas) przyciąga pełną stronę PDF do CALayer: poszczególne kafelki mogą być renderowane (nawet z poprawką tileSize)
  2. CALayers nie mogę być przygotowany z wyprzedzeniem (renderowane poza ekranem).

Zasadniczo przeglądarki plików PDF są również dość obciążone pamięcią. Nawet monitoruj użycie pamięci w przykładowym pliku PDF firmy Apple.

W moim obecnym projekcie tworzę przeglądarkę plików PDF i renderuję UIImagestronę w osobnym wątku (tutaj też są problemy!) I prezentuję ją, gdy skala wynosi x1. CATiledLayerrenderowanie kopnięć, gdy skala jest> 1. iBooks ma podobne podwójne podejście, ponieważ przewijając strony, możesz zobaczyć wersję strony w niższej rozdzielczości przez nieco mniej niż sekundę, zanim pojawi się wyraźna wersja.

Renderuję 2 strony z każdej strony strony, tak aby obraz PDF był gotowy do maskowania warstwy przed rozpoczęciem rysowania. Strony są ponownie niszczone, gdy znajdują się w odległości +2 stron od strony, na której ustawiono ostrość.

Czy ktoś ma jakieś spostrzeżenia, bez względu na to, jak małe lub oczywiste, aby poprawić wydajność / obsługę pamięci podczas rysowania plików PDF? lub jakieś inne kwestie tutaj omówione?

EDYCJA: Kilka wskazówek (Credit-Luke Mcneice, VdesmedT, Matt Gallagher, Johann):

  • Gdy tylko możesz, zapisz dowolny nośnik na dysku.

  • Użyj większego tileSizes, jeśli renderujesz na TiledLayers

  • init często używane tablice z obiektami zastępczymi, alternatywnie jest to inne podejście projektowe

  • Pamiętaj, że obrazy będą renderowane szybciej niż a CGPDFPageRef

  • Użyj NSOperationslub GCD i bloki, aby przygotować strony z wyprzedzeniem.

  • zadzwoń CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);wcześniej, CGContextDrawPDFPageaby zmniejszyć zużycie pamięci podczas rysowania

  • Inicjowanie twojego NSOperationsdocRef to zły pomysł (pamięć), zawiń docRef w singleton.

  • Anuluj niepotrzebne NSOperationsKiedy możesz, zwłaszcza jeśli będą one korzystały z pamięci, uważaj jednak na pozostawienie otwartych kontekstów!

  • Przetwarzaj obiekty strony i niszcz nieużywane widoki

  • Zamknij wszystkie otwarte konteksty, gdy tylko ich nie potrzebujesz

  • po otrzymaniu ostrzeżeń pamięci zwolnij i ponownie załaduj DocRef i dowolną pamięć podręczną strony

Inne funkcje PDF:

Dokumentacja

Przykładowe projekty

Luke Mcneice
źródło
komentując, aby upewnić się, że podglądacze otrzymają powiadomienie o edycji
Luke Mcneice,
+1 i dziękuję za dodanie wszystkich tych informacji, szkoda, że ​​nie miałem go podczas opracowywania mojego czytnika;) również dziękuję za dodanie mojego pytania dotyczącego adnotacji PDF (zawiera również odpowiedzi z przykładowym kodem). kilka dni temu otworzyłem to: stackoverflow.com/questions/4097044/pdf-search-on-the-iphone czy masz jakieś wskazówki?
pt2ph8,
Nie opisałem tego jeszcze sam, więc nie mogłem powiedzieć nic innego niż wskazać na blogu z przypadkowymi pomysłami: random-ideas.net/posts/42 Dziękuję za ten post, ale próbuję zebrać wszystkie problemy z PDF w jednym miejsce.
Luke Mcneice
8
W mojej firmie używaliśmy do renderowania plików PDF, notacji itp. Rozwiązanie o nazwie innej firmy PSPDFKit, nie jest tanie, ale warte: pspdfkit.com
János
1
+1 Postępowałem zgodnie z tymi przydatnymi wskazówkami dla mojej przeglądarki PDF typu
Prcela

Odpowiedzi:

103

Zbudowałem tego rodzaju aplikację, używając w przybliżeniu tego samego podejścia, z wyjątkiem:

  • Wygenerowałem buforowany obraz na dysku i zawsze generuję z wyprzedzeniem dwa do trzech obrazów w osobnym wątku.
  • Nie nakładam za pomocą, UIImageale zamiast tego rysuję obraz w warstwie, gdy powiększenie wynosi 1. Te kafelki zostaną zwolnione automatycznie po wydaniu ostrzeżeń pamięci.

Za każdym razem, gdy użytkownik zaczyna powiększać, uzyskuję CGPDFPagei renderuję go za pomocą odpowiedniego CTM. Kod w - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) contextjest jak:

CGAffineTransform currentCTM = CGContextGetCTM(context);    
if (currentCTM.a == 1.0 && baseImage) {
    //Calculate ideal scale
    CGFloat scaleForWidth = baseImage.size.width/self.bounds.size.width;
    CGFloat scaleForHeight = baseImage.size.height/self.bounds.size.height; 
    CGFloat imageScaleFactor = MAX(scaleForWidth, scaleForHeight);

    CGSize imageSize = CGSizeMake(baseImage.size.width/imageScaleFactor, baseImage.size.height/imageScaleFactor);
    CGRect imageRect = CGRectMake((self.bounds.size.width-imageSize.width)/2, (self.bounds.size.height-imageSize.height)/2, imageSize.width, imageSize.height);
    CGContextDrawImage(context, imageRect, [baseImage CGImage]);
} else {
    @synchronized(issue) { 
        CGPDFPageRef pdfPage = CGPDFDocumentGetPage(issue.pdfDoc, pageIndex+1);
        pdfToPageTransform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFMediaBox, layer.bounds, 0, true);
        CGContextConcatCTM(context, pdfToPageTransform);    
        CGContextDrawPDFPage(context, pdfPage);
    }
}

problemem jest obiekt zawierający CGPDFDocumentRef. Synchronizuję część, w której pdfDocuzyskuję dostęp do właściwości, ponieważ zwalniam ją i odtwarzam ponownie po otrzymaniu memoryWarnings. Wygląda na to, że CGPDFDocumentRefobiekt wykonuje pewne wewnętrzne buforowanie, którego nie udało mi się pozbyć.

VdesmedT
źródło
1
jakie jest twoje podejście, gdy użytkownik zaczyna powiększanie?
Luke Mcneice
2
@Luke: Zmodyfikowałem post, aby odpowiedzieć
VdesmedT
1
Bardzo dziękuję za to i wskazówkę dotyczącą buforowania CGPDFDocumentRef.
Luke Mcneice,
Krótkie pytanie: dlaczego otrzymujesz pdfPageRef i przekształcasz, gdy skala wynosi 1.0? ponieważ widzę, że rysujesz baseImage (obraz z robota BG?) przed utworzeniem transformacji.
Luke Mcneice 10.10.10
@Luke: Opublikuj tutaj wszelkie ulepszenia wydajności, które możesz wprowadzić. Dziękuję Ci !
VdesmedT,
67

Aby uzyskać prostą i skuteczną przeglądarkę plików PDF, gdy potrzebujesz tylko ograniczonej funkcjonalności, możesz teraz (iOS 4.0+) korzystać z frameworka QuickLook:

Najpierw musisz połączyć się z QuickLook.frameworki#import <QuickLook/QuickLook.h>;

Następnie w viewDidLoaddowolnej leniwej metodzie inicjalizacji:

QLPreviewController *previewController = [[QLPreviewController alloc] init];
previewController.dataSource = self;
previewController.delegate = self;
previewController.currentPreviewItemIndex = indexPath.row;
[self presentModalViewController:previewController animated:YES];
[previewController release];
Joshua J. McKinnon
źródło
Tak, jest to koncepcyjnie to samo, co użycie UIWebView. Nie spędzilibyśmy godzin zastanawiając się, jak korzystać z CGPDF *, gdyby to było takie proste.
pt2ph8
5
Tak, jasne. Z pewnością nie przedstawiam tego jako kompletnego rozwiązania. Ale niektórzy czytelnicy tego pytania mogą nie mieć świadomości, że dla niektórych celów jest to rozsądne rozwiązanie. Zaktualizowałem odpowiedź, aby to wyjaśnić.
Joshua J. McKinnon
6
+1 za to. Moim głównym celem jest umożliwienie użytkownikowi przeglądania niektórych dokumentów w aplikacji w formacie PDF. Nie dbam o wyszukiwanie, wyróżnianie ani inne dzwonki i gwizdy - po prostu wydajne renderowanie plików PDF.
memmons,
2
Moja kategoria UIImage + PDF to szybki, bezsensowny renderer PDF z wbudowaną warstwą buforowania. github.com/mindbrix/UIImage-PDF
Mindbrix
6

Od iOS 11 możesz używać natywnego frameworka o nazwie PDFKit do wyświetlania i manipulowania plikami PDF.

Po zaimportowaniu pliku PDFKit należy zainicjować PDFViewlokalny lub zdalny adres URL i wyświetlić go w widoku.

if let url = Bundle.main.url(forResource: "example", withExtension: "pdf") {
    let pdfView = PDFView(frame: view.frame)
    pdfView.document = PDFDocument(url: url)
    view.addSubview(pdfView)
}

Przeczytaj więcej o PDFKit w dokumentacji Apple Developer.

the4kman
źródło
-2
 CGAffineTransform currentCTM = CGContextGetCTM(context);     if
 (currentCTM.a == 1.0 && baseImage)  {
     //Calculate ideal scale
     CGFloat scaleForWidth = baseImage.size.width/self.bounds.size.width;
     CGFloat scaleForHeight = baseImage.size.height/self.bounds.size.height; 
     CGFloat imageScaleFactor = MAX(scaleForWidth, scaleForHeight);
     CGSize imageSize = CGSizeMake(baseImage.size.width/imageScaleFactor,
 baseImage.size.height/imageScaleFactor);
     CGRect imageRect = CGRectMake((self.bounds.size.width-imageSize.width)/2,
 (self.bounds.size.height-imageSize.height)/2, imageSize.width,
 imageSize.height);
     CGContextDrawImage(context, imageRect, [baseImage CGImage]); }  else  {
     @synchronized(issue)  { 
         CGPDFPageRef pdfPage = CGPDFDocumentGetPage(issue.pdfDoc, pageIndex+1);
         pdfToPageTransform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFMediaBox, layer.bounds, 0, true);
         CGContextConcatCTM(context, pdfToPageTransform);    
         CGContextDrawPDFPage(context, pdfPage);
     } }
Kuldeep singh
źródło