Быстрый и Lean PDF Viewer для iPhone / iPad / iOs - советы и подсказки?
В последнее время появилось много вопросов о рисовании PDF.
Да, вы можете рендерить PDF очень легко с UIWebView
но это не может дать производительность и функциональность, которую вы ожидаете от хорошего средства просмотра PDF.
Вы можете нарисовать страницу PDF в CALayer или в UIImage. У Apple даже есть пример кода, чтобы показать, как нарисовать большой PDF в Zoomable UIScrollview
Но одни и те же проблемы продолжают возникать.
UIImage Метод:
- PDF в
UIImage
не оптически масштабировать так же, как подход Layer. - Процессор и память ударили при создании
UIImages
изPDFcontext
ограничивает / запрещает использовать его для создания в реальном времени новых уровней масштабирования.
Метод CATiledLayer:
- Theres значительные накладные расходы (время) рисования полной страницы PDF в
CALayer
: можно увидеть отдельные плитки (даже с настройкой tileSize) 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:
Получение ссылок внутри PDF (и здесь и здесь)
Получение цели ссылки (Получение номера страницы из
/Dest
массив)
Получение необработанного текста (и здесь, и здесь, и здесь (с ориентацией на позиционирование))
Поиск(и здесь) (не работает со всеми PDF-файлами (некоторые просто показывают странные символы, я думаю, это проблема кодирования, но я не уверен) -Credit BrainFeeder)
CALayer и Off-Screen Rendering - рендеринг следующей страницы для быстрого / плавного отображения
Документация
- Кварц PDFObjects (Используется для мета-информации, аннотаций, превью)
- Abobe PDF Spec
Примеры проектов
- 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)
}
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);
} }