Быстрый и Lean PDF Viewer для iPhone / iPad / iOs - советы и подсказки?

В последнее время появилось много вопросов о рисовании PDF.

Да, вы можете рендерить PDF очень легко с UIWebView но это не может дать производительность и функциональность, которую вы ожидаете от хорошего средства просмотра PDF.

Вы можете нарисовать страницу PDF в CALayer или в UIImage. У Apple даже есть пример кода, чтобы показать, как нарисовать большой PDF в Zoomable UIScrollview

Но одни и те же проблемы продолжают возникать.

UIImage Метод:

  1. PDF в UIImage не оптически масштабировать так же, как подход Layer.
  2. Процессор и память ударили при создании UIImages из PDFcontextограничивает / запрещает использовать его для создания в реальном времени новых уровней масштабирования.

Метод CATiledLayer:

  1. Theres значительные накладные расходы (время) рисования полной страницы PDF в CALayer: можно увидеть отдельные плитки (даже с настройкой tileSize)
  2. CALayers не может быть подготовлен заранее (за кадром).

Как правило, просмотрщики PDF тоже очень много памяти. Даже контролировать использование памяти в Apple, например, масштабируемый PDF-файл.

В моем текущем проекте я разрабатываю средство просмотра PDF и отображаю UIImage страницы в отдельном потоке (проблемы здесь тоже!) и представление его в то время как масштаб x1. CATiledLayer рендеринг пинает, как только масштаб>1. iBooks использует аналогичный подход двойного дубля, как если вы прокручиваете страницы, вы можете увидеть версию страницы с более низким разрешением всего за секунду до появления четкой версии.

Я рендеринг 2 страниц с каждой стороны в фокусе так, чтобы изображение PDF было готово, чтобы замаскировать слой, прежде чем он начнет рисовать.

Есть ли у кого-нибудь какие-либо идеи, независимо от того, насколько они малы или очевидны, чтобы улучшить производительность / обработку памяти в Drawing PDF?или какие-то другие вопросы обсуждались здесь?

РЕДАКТИРОВАТЬ: Некоторые советы (Кредит-Люк Макнейс,VdesmedT, Мэтт Галлахер, Иоганн):

  • Сохраните любой носитель на диск, когда сможете.

  • Используйте большие tileSizes, если рендеринг на TiledLayers

  • init часто используемые массивы с объектами-заполнителями, альтернативно другой подход к дизайну

  • Обратите внимание, что изображения будут отображаться быстрее, чем CGPDFPageRef

  • использование NSOperations или GCD & Blocks, чтобы подготовить страницы заранее.

  • вызов CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault); до CGContextDrawPDFPage уменьшить использование памяти при рисовании

  • инициация вашего NSOperations с docRef - плохая идея (память), оберните docRef в синглтон.

  • Отменить ненужно NSOperations Когда вы можете, особенно если они будут использовать память, остерегайтесь оставлять контексты открытыми!

  • Перерабатывать объекты страницы и уничтожать неиспользуемые представления

  • Закройте все открытые контексты, как только они вам не понадобятся

  • при получении предупреждений памяти освободите и перезагрузите DocRef и любые страницы кэши

Другие возможности PDF:

Документация

Примеры проектов

  • Apple / ZoomingPDF - масштабирование, UIScrollView, CATiledLayer
  • vfr / reader - масштабирование, пейджинг, UIScrollView, CATiledView
  • лоб / листья - пейджинг с красивыми переходами
  • / Skim - все, что кажется (PDF Reader/ редактор для OSX)

4 ответа

Решение

Я создал такое приложение, используя примерно тот же подход, за исключением:

  • Я кеширую сгенерированное изображение на диск и всегда заранее генерирую два-три изображения в отдельном потоке.
  • Я не накладываю на UIImage но вместо этого нарисуйте изображение в слое, когда масштабирование равно 1. Эти плитки будут автоматически освобождаться при выдаче предупреждений памяти.

Всякий раз, когда пользователь начинает масштабирование, я получаю CGPDFPage и отрендерить его, используя соответствующий CTM. Код в - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) context как:

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

вопрос является объектом, связывающим CGPDFDocumentRef, Я синхронизирую часть, где я получаю доступ к pdfDoc свойство, потому что я освобождаю его и воссоздаю при получении memoryWarnings. Кажется, что CGPDFDocumentRef объект делает какое-то внутреннее кеширование, от которого я не нашел как избавиться.

Для простого и эффективного средства просмотра PDF, когда вам требуется только ограниченная функциональность, вы можете (iOS 4.0+) использовать платформу QuickLook:

Во-первых, вам нужно связать с QuickLook.framework а также #import <QuickLook/QuickLook.h>;

Впоследствии, либо в viewDidLoad или любой из ленивых методов инициализации:

QLPreviewController *previewController = [[QLPreviewController alloc] init];
previewController.dataSource = self;
previewController.delegate = self;
previewController.currentPreviewItemIndex = indexPath.row;
[self presentModalViewController:previewController animated:YES];
[previewController release];

Начиная с iOS 11, вы можете использовать собственный фреймворк PDFKit для отображения и управления PDF-файлами.

После импорта PDFKit, вы должны инициализировать PDFView с локальным или удаленным URL и отображать его в вашем представлении.

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

Узнайте больше о PDFKit в документации Apple Developer.

 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);
     } }
Другие вопросы по тегам