เครื่องมืออ่าน PDF ที่รวดเร็วและแบบ Lean สำหรับ iPhone / iPad / iOs - เคล็ดลับและคำแนะนำ?


368

เมื่อไม่นานมานี้มีคำถามมากมายเกี่ยวกับการวาด PDF

ใช่คุณสามารถแสดงผล PDF ได้อย่างง่ายดายด้วยUIWebViewแต่มันไม่สามารถให้ประสิทธิภาพและการทำงานที่คุณคาดหวังจากโปรแกรมดู PDF ที่ดี

คุณสามารถวาดหน้าไฟล์ PDF ไปยัง CALayerหรือเพื่อ UIImage Apple ยังมีรหัสตัวอย่างเพื่อแสดงวิธีการวาด PDF ขนาดใหญ่ใน Zoomable UIScrollview

แต่ปัญหาเดียวกันก็ยังคงมีอยู่

วิธี UIImage:

  1. PDF ในขนาดที่UIImageไม่ออพติคอลรวมถึงวิธีการเลเยอร์
  2. CPU และหน่วยความจำได้รับผลกระทบUIImagesจากการPDFcontext จำกัด / ป้องกันการใช้เพื่อสร้างการเรนเดอร์แบบเรียลไทม์ของระดับการซูมใหม่

วิธี CATiledLayer:

  1. มีค่าโสหุ้ยที่สำคัญ (เวลา) การวาดหน้าเต็มรูปแบบ PDF ไปที่CALayer: แต่ละแผ่นสามารถมองเห็นการแสดงผล (แม้จะมีการปรับขนาดกระเบื้อง)
  2. CALayers ไม่สามารถเตรียมล่วงหน้า (แสดงนอกหน้าจอ)

โดยทั่วไปแล้วโปรแกรมดู PDF นั้นค่อนข้างหนักในหน่วยความจำเช่นกัน แม้แต่ตรวจสอบการใช้หน่วยความจำของตัวอย่าง PDF ที่ซูมได้ของ Apple

ในโครงการปัจจุบันของฉันฉันกำลังพัฒนาโปรแกรมดู PDF และกำลังแสดงUIImageหน้าหนึ่งในเธรดแยกต่างหาก (ปัญหาที่นี่ด้วย!) และนำเสนอในขณะที่สเกลคือ x1 CATiledLayerการเรนเดอร์จะเริ่มขึ้นในทันทีที่สเกลเป็น> 1 iBooks ใช้แนวทางแบบสองครั้งที่คล้ายคลึงกันราวกับว่าคุณเลื่อนหน้าเว็บคุณสามารถดูรุ่นความละเอียดต่ำกว่าของหน้าเว็บในเวลาน้อยกว่าหนึ่งวินาทีก่อนที่จะมีหน้าจอคมชัดปรากฏขึ้น

การแสดงผล 2 หน้าแต่ละด้านของหน้าอยู่ในโฟกัสเพื่อให้ภาพ PDF พร้อมที่จะปิดบังเลเยอร์ก่อนที่จะเริ่มวาดหน้าเพจจะถูกทำลายอีกครั้งเมื่อพวกเขาอยู่ที่ +2 หน้าออกจากหน้าที่มุ่งเน้น

ใครบ้างมีความเข้าใจไม่ว่าเล็กหรือชัดเจนในการปรับปรุงประสิทธิภาพ / การจัดการหน่วยความจำของ Drawing PDF หรือไม่? หรือปัญหาอื่น ๆ ที่กล่าวถึงที่นี่?

แก้ไข:เคล็ดลับบางอย่าง (เครดิต - Luke Mcneice, VdesmedT, Matt Gallagher, Johann):

  • บันทึกสื่อใด ๆ ลงในดิสก์เมื่อคุณสามารถ

  • ใช้ไทล์ขนาดใหญ่ขึ้นหากการเรนเดอร์บน TiledLayers

  • init อาร์เรย์ที่ใช้บ่อยกับวัตถุตัวยึด alternitively อีกวิธีการออกแบบเป็นอย่างใดอย่างหนึ่ง

  • โปรดทราบว่ารูปภาพจะแสดงเร็วกว่า CGPDFPageRef

  • ใช้NSOperationsหรือ GCD & บล็อกเพื่อเตรียมหน้าล่วงหน้า

  • โทรCGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);ก่อนCGContextDrawPDFPageเพื่อลดการใช้หน่วยความจำขณะวาด

  • เริ่มต้นNSOperationsด้วยdocRef ของคุณเป็นความคิดที่ไม่ดี (หน่วยความจำ), ห่อ docRef ลงในซิงเกิล

  • ยกเลิกความจำเป็นNSOperationsเมื่อคุณทำได้โดยเฉพาะอย่างยิ่งหากพวกเขาจะใช้หน่วยความจำระวังอย่าเปิดบริบทแม้ว่า!

  • รีไซเคิลหน้าวัตถุและทำลายมุมมองที่ไม่ได้ใช้

  • ปิดบริบทที่เปิดทันทีที่คุณไม่ต้องการ

  • เมื่อได้รับคำเตือนจากหน่วยความจำและรีโหลด DocRef และแคชหน้าใด ๆ

คุณสมบัติ PDF อื่น ๆ :

เอกสาร

  • Quartz PDFObjects (ใช้สำหรับข้อมูลเมตา, คำอธิบายประกอบ, นิ้วโป้ง)
  • Abobe PDF Spec

ตัวอย่างโครงการ

  • Apple / ZoomingPDF - ซูมUIScrollView,,CATiledLayer
  • VFR / อ่าน - ซูมเพจUIScrollView,CATiledView
  • คิ้ว / ใบไม้ - เพจจิ้งที่มีการเปลี่ยนที่ดี
  • / skim - ทุกอย่างที่ปรากฏ (ตัวอ่าน PDF / ตัวแก้ไขสำหรับ OSX)

แสดงความคิดเห็นเพื่อให้แน่ใจว่า peeps ได้รับการแจ้งเตือนการแก้ไข
Luke Mcneice

+1 และขอขอบคุณสำหรับการเพิ่มข้อมูลทั้งหมดนี้ฉันหวังว่าฉันจะได้มันเมื่อฉันกำลังพัฒนาผู้อ่านของฉัน;) ขอบคุณสำหรับการเพิ่มคำถามของฉันเกี่ยวกับคำอธิบายประกอบ PDF (มันยังมีคำตอบพร้อมรหัสตัวอย่าง) ไม่กี่วันที่ผ่านมาฉันเปิดสิ่งนี้: stackoverflow.com/questions/4097044/pdf-search-on-the-iphoneคุณมีคำแนะนำใด ๆ
pt2ph8

ฉันยังไม่ได้พูดถึงเรื่องนี้ดังนั้นฉันจึงไม่สามารถพูดอะไรได้นอกจากชี้ให้คุณไปยังบล็อกความคิดแบบสุ่ม: random-ideas.net/posts/42ขอบคุณสำหรับการโพสต์แม้ว่าฉันพยายามรวบรวมปัญหา PDF ทั้งหมดในที่เดียว สถานที่.
Luke Mcneice

8
ที่ บริษัท ของฉันเราใช้สำหรับการเรนเดอร์ Pdf, notation และอื่น ๆ ที่เรียกว่าโซลูชันของบุคคลที่สามPSPDFKitมันไม่ถูก แต่คุ้มค่า: pspdfkit.com
János

1
+1 ฉันได้ปฏิบัติตามคำแนะนำที่เป็นประโยชน์เหล่านี้สำหรับโปรแกรมดูโอเพนซอร์ส pdf Swifty PDF github.com/prcela/SwiftyPDF ของฉัน
Prcela

คำตอบ:


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วัตถุจะทำการแคชภายในที่ฉันไม่พบวิธีการกำจัด


1
แนวทางของคุณคืออะไรเมื่อผู้ใช้เริ่มซูม
ลุค Mcneice

2
@Luke: ฉันได้แก้ไขโพสต์เพื่อตอบ
VdesmedT

1
ขอบคุณมากสำหรับสิ่งนี้และเคล็ดลับเกี่ยวกับการแคช CGPDFDocumentRef
Luke Mcneice

แค่คำถามสั้น ๆ : ทำไมคุณถึงได้รับ pdfPageRef และแปลงเมื่อขนาดเป็น 1.0 เพราะฉันเห็นว่าคุณวาด baseImage (ภาพจากคนงาน bg?) ก่อนที่คุณจะสร้างการแปลง
Luke Mcneice

@ ลุค: โพสต์ที่นี่การปรับปรุงประสิทธิภาพใด ๆ ที่คุณสามารถทำได้โปรด ขอบคุณ !
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
ใช่แน่นอน. แน่นอนฉันไม่ได้นำเสนอเป็นโซลูชั่นที่สมบูรณ์ แต่ผู้อ่านบางคนของคำถามนี้อาจไม่ทราบว่าสำหรับบางจุดประสงค์นี่เป็นทางออกที่สมเหตุสมผล อัปเดตคำตอบเพื่อให้ชัดเจน
Joshua J. McKinnon

6
+1 สำหรับสิ่งนี้ ความสนใจหลักของฉันคือการอนุญาตให้ผู้ใช้ดูเอกสารในแอปที่อยู่ในรูปแบบ PDF ฉันไม่สนใจเกี่ยวกับการค้นหาการเน้นหรือระฆังและนกหวีดอื่น ๆ - เพียงแค่การแสดงผล PDF ที่มีประสิทธิภาพ
memmons

2
หมวดหมู่ UIImage + PDF ของฉันเป็นตัวเรนเดอร์ PDF ที่ไร้สาระอย่างรวดเร็วพร้อมเลเยอร์การแคชในตัว github.com/mindbrix/UIImage-PDF
Mindbrix

6

ตั้งแต่iOS 11คุณสามารถใช้เฟรมเวิร์กดั้งเดิมที่เรียกว่าPDFKitสำหรับการแสดงและจัดการ PDF

หลังจากนำเข้า PDFKit คุณควรเริ่มต้น a 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


-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);
     } }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.