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


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ć.


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];

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.


-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);
     } }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.