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

369

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

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

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

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

UIImage Метод:

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

Метод CATiledLayer:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Apple / ZoomingPDF - масштабирование UIScrollView,CATiledLayer
  • VFR / читатель - масштабирование, пейджинг, UIScrollView,CATiledView
  • лоб / листья - пейджинг с красивыми переходами
  • / Skim - все, что кажется (PDF Reader / редактор для OSX)
Люк Макнейс
источник
комментирование, чтобы гарантировать, что люди получат уведомление о редактировании
Люк Макнейс
+1 и спасибо за добавление всей этой информации, хотелось бы, чтобы она была у меня при разработке читателя;) также спасибо за добавление моего вопроса об аннотациях PDF (он также содержит ответы с примером кода). Несколько дней назад я открыл это: stackoverflow.com/questions/4097044/pdf-search-on-the-iphone У вас есть какие-либо советы?
pt2ph8
Я сам еще не рассказал об этом, поэтому я не мог сказать ничего, кроме как указать вам на блог о случайных идеях: random-ideas.net/posts/42 Хотя спасибо за этот пост, я пытаюсь собрать все проблемы PDF в одном место.
Люк Макнейс
8
В моей компании для рендеринга PDF, нотации и т. Д. Мы использовали стороннее решение PSPDFKit, оно не дешевое, но стоит: pspdfkit.com
János
1
+1 Я следовал этим полезным советам для своей открытой программы просмотра PDF
Swifty

Ответы:

103

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

  • Я кеширую сгенерированное изображение на диск и всегда заранее генерирую два-три изображения в отдельном потоке.
  • Я не накладываю на них 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объект делает некоторое внутреннее кэширование, от которого я не нашел, как избавиться.

VdesmedT
источник
1
каков ваш подход, когда пользователь начинает масштабирование?
Люк Макнейс
2
@Luke: я изменил пост, чтобы ответить
VdesmedT
1
Большое спасибо за это, и совет о кешировании CGPDFDocumentRef.
Люк Макнейс
Просто быстрый вопрос: почему вы получаете pdfPageRef и трансформируетесь, когда масштаб равен 1,0? потому что я вижу, что вы рисуете baseImage (изображение от bg worker?) перед созданием преобразования.
Люк Макнейс
@Luke: Опубликуйте здесь любое улучшение производительности, которое вы могли бы сделать, пожалуйста. Спасибо !
VdesmedT
67

Для простого и эффективного средства просмотра 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];
Джошуа Дж. Маккиннон
источник
Да, это концептуально то же самое, что и использование UIWebView. Мы не тратили бы часы на то, чтобы понять, как использовать CGPDF *, если бы это было так просто.
pt2ph8
5
Да, конечно. Я конечно не представляю это как полное решение. Но некоторые читатели этого вопроса могут не знать, что для некоторых целей это разумное решение. Обновил ответ, чтобы прояснить это.
Джошуа Дж. Маккиннон
6
+1 за это. Мой основной интерес заключается в том, чтобы позволить пользователю просматривать некоторую документацию в приложении в формате PDF. Я не забочусь о поиске, выделении или любых других наворотах - просто качественный рендеринг PDF.
меммоны
2
Моя категория UIImage + PDF - это быстрый PDF-рендеринг со встроенным кэширующим слоем. github.com/mindbrix/UIImage-PDF
Mindbrix
6

Начиная с 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.

оборота the4kman
источник
-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
источник