Visualizador de PDF enxuto e rápido para iPhone / iPad / iOs - dicas e sugestões?

369

Recentemente, houve muitas perguntas sobre o desenho de PDFs.

Sim, você pode renderizar PDFs com muita facilidade, UIWebViewmas isso não pode fornecer o desempenho e a funcionalidade que você esperaria de um bom visualizador de PDF.

Você pode desenhar uma página PDF em um CALayer ou em um UIImage . A Apple ainda possui um código de exemplo para mostrar como desenhar um PDF grande em uma visualização de rolagem Zoomable UIS

Mas os mesmos problemas continuam surgindo.

Método UIImage:

  1. PDFs em uma UIImageescala não óptica, assim como em uma abordagem de camada.
  2. A CPU e a memória atingem a geração UIImagesde PDFcontext limites / evitam seu uso para criar uma renderização em tempo real de novos níveis de zoom.

Método CATiledLayer:

  1. Há uma sobrecarga significativa (tempo) atraindo uma página PDF completa para CALayer: blocos individuais podem ser vistos renderizando (mesmo com um ajuste tileSize)
  2. CALayers não pode ser preparado com antecedência (exibido fora da tela).

Geralmente, os visualizadores de PDF também consomem muita memória. Monitore até o uso da memória do exemplo PDF com zoom da apple.

No meu projeto atual, estou desenvolvendo um visualizador de PDF e renderizando uma UIImagede uma página em um thread separado (também problemas aqui!) E apresentando-a enquanto a escala é x1. CATiledLayera renderização entra em ação quando a escala é> 1. O iBooks adota uma abordagem semelhante, como se você rolar as páginas, poderá ver uma versão mais baixa da página por menos de um segundo antes de aparecer uma versão nítida.

Estou renderizando 2 páginas de cada lado da página em foco, para que a imagem PDF esteja pronta para mascarar a camada antes de começar a desenhar. As páginas são destruídas novamente quando estão a +2 páginas da página em foco.

Alguém tem alguma ideia, não importa quão pequena ou óbvia seja para melhorar o desempenho / manipulação de memória dos PDFs de desenho? ou quaisquer outros problemas discutidos aqui?

EDIT: Algumas dicas (Crédito: Luke Mcneice, VdesmedT, Matt Gallagher, Johann):

  • Salve qualquer mídia em disco quando puder.

  • Use tileSizes maiores se renderizar em TiledLayers

  • init matrizes freqüentemente usadas com objetos de espaço reservado, alternativamente outra abordagem de design é essa

  • Observe que as imagens renderizam mais rápido que um CGPDFPageRef

  • Use NSOperationsou GCD & Blocks para preparar as páginas antes do tempo.

  • ligue CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);antes CGContextDrawPDFPagepara reduzir o uso de memória enquanto desenha

  • iniciar o seu NSOperationscom um docRef é uma má idéia (memória), envolva o docRef em um singleton.

  • Cancelar desnecessariamente NSOperationsQuando você puder, especialmente se eles estiverem usando memória, tenha cuidado ao deixar os contextos abertos!

  • Recicle objetos de página e destrua visualizações não utilizadas

  • Feche todos os contextos abertos assim que não precisar deles

  • ao receber avisos de memória, libere e recarregue o DocRef e qualquer cache de página

Outros recursos de PDF:

Documentação

Projetos de exemplo

Luke Mcneice
fonte
comentando para garantir espreitadelas obter a notificação de edição
Luke McNeice
+1 e obrigado por adicionar todas essas informações, gostaria de tê-las quando estava desenvolvendo meu leitor;) também obrigado por adicionar minha pergunta sobre anotações em PDF (também contém as respostas com código de exemplo). alguns dias atrás eu abri isso: stackoverflow.com/questions/4097044/pdf-search-on-the-iphone você tem alguma dica?
precisa saber é o seguinte
Eu ainda não cobri isso, então não posso dizer nada além de apontar para o blog de idéias aleatórias: random-ideas.net/posts/42 Obrigado pelo post, porém, estou tentando reunir todos os problemas de PDF em um Lugar, colocar.
precisa saber é o seguinte
8
Na minha empresa usamos para Pdf renderização, notação etc. uma solução terceira parte chamada PSPDFKit, não é barato, mas vale a pena: pspdfkit.com
János
11
+1 Eu segui estas dicas úteis para o meu visualizador de PDF de código aberto Swifty PDF github.com/prcela/SwiftyPDF
Prcela

Respostas:

103

Criei esse tipo de aplicativo usando aproximadamente a mesma abordagem, exceto:

  • Coloco em cache a imagem gerada no disco e sempre gero duas a três imagens com antecedência em um thread separado.
  • Eu não sobreponho com um, UIImagemas, em vez disso, desenhe a imagem na camada quando o zoom é 1. Esses blocos serão liberados automaticamente quando avisos de memória forem emitidos.

Sempre que o usuário inicia o zoom, eu o adquiro CGPDFPagee o renderizo usando o CTM apropriado. O código em - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) contexté como:

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);
    }
}

questão é o objeto que contém o CGPDFDocumentRef. Sincronizo a parte em que acesso a pdfDocpropriedade porque a libero e a recria ao receber memoryWarnings. Parece que o CGPDFDocumentRefobjeto faz algum cache interno do qual não encontrei como me livrar.

VdesmedT
fonte
11
qual é a sua abordagem quando o usuário inicia o zoom?
Luke Mcneice
2
@Luke: Eu modificado o cargo a resposta
VdesmedT
11
Muito obrigado por isso, e a dica sobre o cache CGPDFDocumentRef.
Luke Mcneice 10/10
Apenas uma pergunta rápida: por que você está obtendo o pdfPageRef e transformando quando a escala é 1.0? porque vejo que você desenha uma baseImage (a imagem do trabalhador bg?) antes de criar a transformação.
Luke Mcneice 10/10
@ Lucas: Poste aqui qualquer melhoria de desempenho que você puder fazer por favor. Obrigado !
VdesmedT
67

Para um visualizador de PDF simples e eficaz, quando você exige apenas funcionalidade limitada, agora (iOS 4.0 ou superior) pode usar a estrutura do QuickLook:

Primeiro, você precisa vincular QuickLook.frameworke#import <QuickLook/QuickLook.h>;

Posteriormente, em um viewDidLoadou em qualquer um dos métodos de inicialização lenta:

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
fonte
Sim, conceitualmente é o mesmo que usar um UIWebView. Não passaríamos horas tentando descobrir como usar o material CGPDF *, se fosse assim tão fácil.
pt2ph8
5
Sim claro. Certamente não o apresento como uma solução completa. Mas alguns leitores desta pergunta podem não estar cientes de que, para alguns propósitos, esta é uma solução razoável. Atualizado a resposta para deixar isso claro.
Joshua J. McKinnon
6
+1 para isso. Meu principal interesse é permitir que o usuário visualize alguma documentação no aplicativo em formato PDF. Eu não ligo para pesquisar, destacar ou qualquer um dos outros sinos e assobios - apenas renderização em PDF com desempenho.
memmons
2
Minha categoria UIImage + PDF é um renderizador de PDF rápido e sem brincadeiras, com uma camada de cache embutida. github.com/mindbrix/UIImage-PDF
Mindbrix
6

Desde o iOS 11 , você pode usar a estrutura nativa chamada PDFKit para exibir e manipular PDFs.

Depois de importar o PDFKit, você deve inicializar um PDFViewcom um URL local ou remoto e exibi-lo em sua exibição.

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

Leia mais sobre o PDFKit na documentação do desenvolvedor da Apple.

the4kman
fonte
-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
fonte