iPhone / iPad / IO를위한 빠르고 마른 PDF 뷰어-팁과 힌트?


368

최근에 PDF 그리기에 대한 많은 질문이있었습니다.

그렇습니다. PDF를 아주 쉽게 렌더링 할 수는 UIWebView있지만 훌륭한 PDF 뷰어에서 기대할 수있는 성능과 기능을 제공 할 수는 없습니다.

PDF 페이지 를 CALayer 또는 UIImage에 그릴 수 있습니다 . Apple 은 Zoomable UIScrollview에서 큰 PDF 그리는 방법을 보여주는 샘플 코드도 있습니다.

그러나 같은 문제가 계속 자랍니다.

UIImage 메소드 :

  1. PDF UIImage는 레이어 방식뿐만 아니라 광학적으로 확장되지 않습니다.
  2. CPU 및 메모리 는 새로운 줌 레벨의 실시간 렌더링을 생성하기 위해이를 사용하여 한계 / 예방 UIImages으로부터 생성에 도달했습니다 PDFcontext.

CATiledLayer 방법 :

  1. 전체 PDF 페이지를 다음으로 그리기위한 상당한 오버 헤드 (시간) CALayer가 있습니다 : 개별 타일이 렌더링되는 것을 볼 수 있습니다 (타일 크기 조정을 사용하더라도)
  2. CALayers 미리 준비 할 수 없습니다 (스크린 외부에서 렌더링).

일반적으로 PDF 뷰어는 메모리가 상당히 무겁습니다. 애플의 줌이 가능한 PDF 예제의 메모리 사용량도 모니터링하십시오.

현재 프로젝트에서 PDF 뷰어를 개발 중이며 UIImage페이지를 별도의 스레드로 렌더링하고 (여기에서도 문제가 있습니다!) 크기가 x1 인 동안 표시합니다. CATiledLayer스케일이 1보다 크면 렌더링이 시작됩니다. iBooks는 페이지를 스크롤 할 때와 비슷한 이중 테이크 접근 방식을 사용하여 선명한 버전이 나타나기 전에 1 초 이내에 페이지의 저해상도 버전을 볼 수 있습니다.

Im은 PDF 이미지가 도면을 시작하기 전에 레이어를 마스킹 할 준비가되도록 페이지의 각면에 2 페이지 씩 초점을 맞 춥니 다.

그림 PDF의 성능 / 메모리 처리를 개선하기 위해 작거나 명백한 사람에 대한 통찰력이 있습니까? 또는 여기에 논의 된 다른 문제가 있습니까?

편집 : 몇 가지 팁 (Credit-Luke Mcneice, VdesmedT, Matt Gallagher, Johann) :

  • 가능하면 미디어를 디스크에 저장하십시오.

  • TiledLayers에서 렌더링하는 경우 더 큰 tileSizes를 사용하십시오.

  • 초기화 자주 자리 객체와 배열을 사용 alternitively 다른 설계 방식은 이것

  • 이미지는 a보다 빠르게 렌더링됩니다. CGPDFPageRef

  • NSOperations또는 GCD 및 블록 을 사용 하여 미리 페이지를 준비하십시오.

  • 그리기 중 메모리 사용량을 줄이기 위해 전화 CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);하기CGContextDrawPDFPage

  • docRef로 당신 NSOperations을 초기화하는 것은 나쁜 생각 (메모리)입니다 .docRef를 싱글 톤으로 감싸십시오.

  • 불필요하게 취소 NSOperations할 수 있습니다. 특히 메모리를 사용하는 경우 컨텍스트를 열어 두지 마십시오!

  • 페이지 개체를 재활용하고 사용하지 않는보기를 삭제하십시오.

  • 필요없는 즉시 열린 컨텍스트를 모두 닫으십시오.

  • 메모리 경고를 받고 DocRef 및 모든 페이지 캐시를 다시로드

다른 PDF 기능 :

선적 서류 비치

프로젝트 예


친구들이 편집 알림 얻을 수 있도록 주석
누가 복음 Mcneice

+1하고이 모든 정보를 추가해 주셔서 감사합니다. 독자를 개발할 때이 정보를 얻었 으면 좋겠습니다.) PDF 주석에 대한 질문 (샘플 코드가 포함 된 답변도 포함)을 추가해 주셔서 감사합니다. 며칠 전에 이것을 열었습니다. stackoverflow.com/questions/4097044/pdf-search-on-the-iphone 팁이 있습니까?
pt2ph8

나는 이것을 직접 다루지 않았으므로 임의의 아이디어 블로그를 가리킬 수있는 것 외에는 아무것도 말할 수 없었다 : random-ideas.net/posts/42 게시물에 감사드립니다.하지만 모든 PDF 문제를 하나로 모 으려고합니다 장소.
Luke Mcneice

8
우리 회사에서는 PDF 렌더링, 표기법 등의 타사 솔루션을 사용하여 PSPDFKit저렴하지는 않지만 가치가 있습니다. pspdfkit.com
János

1
+1 오픈 소스 PDF 뷰어를위한 유용한 팁을 따랐습니다. Swifty PDF github.com/prcela/SwiftyPDF
Prcela

답변:


103

나는 다음과 같은 것을 제외하고는 거의 동일한 접근법을 사용하여 이러한 종류의 응용 프로그램을 작성했습니다.

  • 생성 된 이미지를 디스크에 캐시하고 항상 별도의 스레드에서 2 ~ 3 개의 이미지를 미리 생성합니다.
  • 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. pdfDocmemoryWarnings를받을 때 속성을 해제하고 다시 작성하기 때문에 속성에 액세스하는 부분을 동기화합니다 . CGPDFDocumentRef객체가 제거하는 방법을 찾지 못한 내부 캐싱을 수행 하는 것 같습니다 .


1
사용자가 확대 / 축소를 시작할 때 어떻게 접근합니까?
Luke Mcneice

2
@Luke : 나는 대답을 게시물을 수정했습니다
VdesmedT

1
이것에 대해 많은 감사와 CGPDFDocumentRef 캐싱에 대한 팁.
Luke Mcneice

간단한 질문 : 스케일이 1.0 일 때 왜 pdfPageRef를 얻고 변환합니까? 변환을 만들기 전에 baseImage (bg 워커의 이미지?)를 그리는 것을 볼 수 있기 때문입니다.
Luke Mcneice

@Luke : 성능 향상을 위해 여기에 게시하십시오. 감사합니다 !
VdesmedT

67

간단하고 효과적인 PDF 뷰어를 위해 제한된 기능 만 필요한 경우 이제 QuickLook 프레임 워크를 사용할 수 있습니다 (iOS 4.0 이상).

먼저 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 부터 PDF를 표시하고 조작하기 위해 PDFKit 이라는 기본 프레임 워크를 사용할 수 있습니다 .

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

Apple Developer documentation에서 PDFKit에 대해 더 읽으십시오.


-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.