스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
PDFKit의 새로운 기능
앱에서 PDF 문서를 보고 편집하며 저장하도록 지원하는 완전한 기능을 갖춘 프레임워크인 PDFKit을 확인하세요. 라이브 텍스트 및 양식 지원, 이미지로부터 PDF 생성, 대화형 오버레이 빌드, 주석 저장을 포함한 PDFKit의 최신 기능에 대해 안내합니다.
리소스
관련 비디오
WWDC22
WWDC19
-
다운로드
저는 Conrad Carlen이고 PDFKit의 새로운 기능을 말씀드리죠 오늘 다룰 내용입니다 먼저 PDFKit를 빠르게 검토하고 새로운 기능을 살펴보죠 새로운 기능에는 라이브 텍스트와 양식 이미지로 PDF 페이지를 만드는 새로운 방법 그리고 오버레이 뷰가 포함됩니다 PDFKit 원리부터 빠르게 되짚어 보죠 PDFKit 여러분의 앱이 PDF 파일을 읽고 편집하고 쓰는 걸 돕는 완전한 기능의 프레임워크입니다 iOS, macOS와 Mac Catalyst에서 사용할 수 있고 UIViewRepresentable을 통해 SwiftUI에서 사용할 수 있는데 UI 뷰를 앱에 통합시켜 주는 래퍼죠 PDFKit는 여러분의 앱에 필요한 4개의 핵심 클래스로 구성돼 있습니다 PDFView는 SwiftUI나 인터페이스 빌더를 이용해 레이아웃에 포함하는 위젯이죠 PDF 문서의 내용을 표시하고 사용자의 탐색을 돕고 확대 수준을 설정하고 텍스트를 클립보드에 복사합니다
PDFDocument는 PDF 파일을 대표하죠 하위 클래스의 PDFDocument는 흔하지 않아도 항상 사용하게 되죠 PDF 오브젝트의 뿌리이자 나무의 둥치입니다 이게 없으면 나무도 없죠
각 문서는 1개 이상의 PDFPage 오브젝트를 포함합니다 페이지는 콘텐츠를 렌더하고 페이지 고유의 폰트, 이미지 등의 리소스를 저장하죠
오브젝트 그룹의 잎은 PDFAnnotations입니다 선택 사항이죠 PDFPage의 내용은 편집할 수 없는 게 통상적이지만 주석은 상호작용할 수 있고 편집도 가능합니다 오늘 제가 다룰 내용에서 각 오브젝트가 맡는 역할이 있죠 PDFKit의 기본에 관해 더 알아보고 싶다면 아래 링크의 'PDFKit 소개'라는 훌륭한 영상을 확인해 보십시오
이제 iOS 16과 macOS Ventura에 새로 도입된 기능을 얘기해 보죠
PDFKit는 이제 라이브 텍스트를 지원합니다 사진과는 다르게 텍스트는 선택하여 복사하는 양이 적은 경우가 많죠 사진과 달리 PDF의 텍스트는 그저 텍스트일 뿐이고 사람들도 특별한 동작을 기대하지 않습니다 라이브 텍스트는 이런 형태의 PDF 문서에서도 텍스트를 선택하고 검색할 수 있죠 스캔 이미지로 텍스트가 전혀 없습니다
물론 PDF는 여러 페이지가 있을 수 있죠 PDF 문서를 열었을 때 모든 페이지를 OCR로 훑는 노고를 치르기는 싫죠 PDFKit는 여러분이 원할 때 각 페이지와 상호작용할 수 있습니다 OCR이 그 자리에서 이루어지므로 복사본을 만들 필요가 없죠 전체 문서의 텍스트를 저장하기를 원하면 그렇게 할 수 있죠
라이브 텍스트에 더하여 PDFKit의 양식 처리가 개선됐습니다 텍스트 영역을 포함하지 않아도 문서의 양식 영역을 자동으로 인식하죠 익숙한 방식대로 탭 키로 이동하면서 텍스트를 입력할 수 있죠
다음은 이미지를 PDF 페이지로 만드는 API를 얘기해 봅시다
iOS 16과 macOS Ventura에는 새롭고 유연한 API를 이용해 앱이 이미지를 입력값으로 PDF 페이지를 만들 수 있죠 앱은 CGImageRef를 이용해 이미지를 공급합니다 고품질 JPEG 인코딩을 이용하여 PDFKit가 여러분이 제공한 CGImageRef를 압축하죠 CGImageRef는 CoreGraphics의 네이티브 데이터 유형이어서 추가 변환이 필요 없습니다
가장 흔한 사례를 다루는 여러 가지 선택지가 있죠
mediaBox는 페이지의 크기를 특정합니다 이미지에 정확히 맞추거나 Letter처럼 크기를 선택할 수 있죠
rotation은 가로 또는 세로 방향을 지정합니다
upscaleIfSmaller는 기본적으로 이미지가 mediaBox보다 크면 이미지를 축소하여 맞추죠 upscaleIfSmaller를 적용돼 있지만 이미지가 작으면 페이지에 맞춰 크기를 키웁니다
이제 많은 분이 여쭤보신 질문에 대답할게요 PencilKit로 PDF 페이지에 그림을 그리는 방법이죠 답은 오버레이 뷰를 사용하는 겁니다
과거에는 PDF에 그림을 그리는 유일한 방법은 PDFPage를 하위 클래스로 만들어 그리기 메서드를 오버라이드하거나 커스텀 PDF 주석을 사용하는 거였죠 하지만 iOS 16과 macOS Ventura부터 각 PDF 페이지 위에 뷰를 오버레이 할 수 있습니다 이를 통해 앱에서 상호작용이 가능한 뷰를 PDF 페이지 위에 띄울 수 있죠 뷰를 오버레이 할 때 세 가지를 알아야 합니다 먼저 새로운 프로토콜을 사용해 PDF 페이지 위에 오버레이 뷰를 설치해야 하고
저장할 때 여러분의 콘텐츠를 PDF에 통합해야 하죠 저장 이야기가 나왔으니 나중에 PDF 문서를 저장하는 가장 좋은 방법을 다룰게요
PDF 페이지에 오버레이 뷰를 설치하는 건 직관적입니다 PDF의 분량이 수백 페이지일 수 있으므로 PDF를 열 때 모든 페이지의 뷰를 생성할 수는 없죠 사용자가 위아래로 빠르게 스크롤 한다면 뷰를 언제 생성할지 알 수 없습니다
다행히도 PDFKit는 사용자가 페이지를 스크롤하기 전에 스마트하게 콘텐츠를 준비하도록 설계돼 있죠 언제 오버레이 뷰를 요청할지 알고 있습니다 여러분의 앱은 새로운 프로토콜로 보낸 요청에 응답하면 되죠
PDFPageOverlayViewProvider가 새로운 프로토콜입니다 참고로 PDFKitPlatformView는 플랫폼에 따라 UIView 또는 NSView를 정의하죠 가장 적용이 필요한 메서드는 overlayViewForPage입니다 뷰의 인스턴스를 제공하면 PDFKit가 적절한 제한 조건으로 크기를 측정하죠 페이지의 각도가 0이 아니면 문서를 회전합니다
다음의 두 메서드는 선택 사항이죠 willDisplayOverlayView로 고유 제스처 핸들러를 설치하거나 PDFKit와의 실패 관계를 설정할 수 있습니다
willEndDisplayingOverlayView는 PDFKit가 뷰를 종료할 때 호출하며 페이지가 스크롤 범위 밖으로 나간 경우죠 여기서 뷰를 풀어 줘도 되지만 이 메서드의 다른 용도도 있습니다 뷰가 표현하려는 것의 데이터가 있다면 이 메서드를 이용하여 그 데이터를 유보할 수 있죠 PencilKit의 예시에서 그걸 해 보겠지만 뷰의 데이터가 다른 곳에 있다면 적용하지 않아도 됩니다
이 예시에서는 이 클래스를 제공자로 사용하죠 PDFPageOverlayViewProvider 프로토콜을 적용합니다 iOS이므로 PDFKitPlatformView는 UIView죠 맵을 사용하여 PDFPage에서 UIView로 넘어갑니다 이건 절차 메서드의 템플릿입니다 다음은 적용을 살펴보죠 overlayViewForPage는 pageToView 맵을 확인하여 해당 페이지에 뷰를 만들었는지 보죠 안 만들었으면 새 뷰를 생성합니다 어떤 경우든 페이지의 그림을 캔버스 뷰로 가져오죠 이 예시에서는 PDFPage의 하위 클래스를 사용합니다 drawing 속성을 추가할 뿐이죠
이제 다음 메서드에 집중합시다 WillEndDisplayingOverlay죠
willEndDisplayingOverlayView는 간단합니다 뷰에서 그림을 가져와서 커스텀 페이지 클래스에 저장하죠 이제 코드를 작성했으니 실제로 어떤지 봅시다
저는 해마다 이맘때쯤 메인주에서 낚시하지만 이번에는 WWDC에 와 있죠 그래서 다른 사람이 저 대신 여행을 갔는데 그 사람에게 좋아하는 장소를 소개하고 싶어요 이 앱으로 그렇게 할 수 있죠 오버레이 뷰에서 PencilKit를 사용합니다 우리가 본 코드와 다른 게 조금 추가된 앱이죠 오버레이 뷰를 화면에 띄우는 코드 전체가 30줄에 불과합니다 그랜드 레이크 스트림이에요 이건 댐 웅덩이죠 항상 물고기가 많아요 낚시하기 좋은 곳이죠 숲속의 길을 따라가면 댐 웅덩이로 가서 낚시할 수 있습니다 이런 것들을 낚거나 이 길을 따라 댐을 넘어 내려올 수도 있죠 여기서 이 위까지 낚시할 수도 있고 섬을 돌아서 이쪽으로 내려올 수도 있죠 여기는 절대 지나지 마세요 유속이 빠르고 물이 깊죠 이걸 피해서 부화장으로 내려오세요 부화장 따라 걸어 내려가다가 이 웅덩이로 오세요 다양한 곳으로 낚싯줄을 던질 수 있죠 정말 좋은 곳이에요 항상 여기서 물고기를 낚죠
이제 페이지에 표시했으니 확대와 스크롤도 해 봅시다
반응성이 좋죠?
지금까지 PDFKit의 오버레이 뷰를 살펴봤습니다 이렇게 그린 그림은 어떻게 저장할까요? PDFAnnotation 클래스를 사용하면 됩니다 저장할 때의 목표는 2개죠 고 충실도로 화면에 나타난 것과 맞추고 왕복으로 편집해야 합니다 PDF 주석에 이를 돕는 기능이 있죠 PDF 주석에는 '외형 스트림'이 있는데 PDF 그리기 명령의 스트림입니다 Quartz2D를 사용해 그린 거의 모든 것은 외형 스트림에 기록할 수 있죠 다른 것도 이미지로 렌더하고 기록할 수도 있습니다 Metal을 사용하면 그렇게 할 수 있죠 PDF 그림으로 기록되므로 어도비 리더나 크롬에서도 똑같이 보입니다
PDF 주석은 PDF 문서에 딕셔너리로 저장되죠 따라서 개인 key/value 쌍에 커스텀 데이터를 저장할 수 있어요 이제 코드를 살펴보죠 PDFAnnotation의 하위 클래스부터 만드세요 draw() 메서드를 오버라이드하기 위해서입니다 PDFKit는 외형 스트림을 저장할 때 이전 슬라이드에서 언급한 메서드를 호출하죠
문서를 저장하려면 UIDocument의 contents()를 오버라이드하세요 나중의 참고할 수 있도록 함수의 개요를 보여드리죠 PDFDocument의 모든 페이지를 루핑합니다 루프의 내용은 다음에 채울게요
각 페이지에 다음 작업을 합니다 커스텀 클래스의 주석을 생성하고 그림을 데이터로 인코딩하고 주석에 데이터를 추가하죠 다음에 이 문서를 열 때 forAnnotationKey 값을 사용하여 저장된 그림 데이터를 다시 읽고 오버레이 뷰에 띄울 수 있습니다
마지막으로 페이지에 주석을 추가하세요 다시 contents() 오버라이드로 돌아오죠 이제 페이지에 주석을 추가했으니 dataRepresentation()을 사용하여 결과를 반환합니다
여러분의 콘텐츠가 주석으로 저장되어 있으면 문서를 받은 사람이 이동하고 크기를 조절하고 삭제할 수 있죠 대개는 필요한 기능이지만 주석을 페이지 일부로 고정하고 싶을 때도 있습니다 iOS 16과 macOS Ventura의 PDFDocumentWriteOption으로 쉽게 할 수 있죠 burnInAnnotationsOption = true를 저장 옵션에 추가하면 끝입니다
PDF 쓰기 옵션도 iOS 16과 macOS Ventura에 몇 개 추가됐죠 한 번 살펴봅시다 CoreGraphics는 항상 최대 충실도로 PDF에 이미지를 저장하려고 했죠 모든 이미지는 무손실 압축으로 풀 해상도로 저장됩니다 PDF를 대형 프린터로 인쇄할 때 더 좋죠 더 가능성이 큰 상황은 화면에 띄우는 겁니다 고 충실도 이미지 데이터로 파일이 엄청나게 커지죠 이를 해결하기 위한 두 가지 선택지를 소개합니다
saveAllImagesAsJPEG는 말 그대로의 기능입니다 이미지를 어떻게 생성했든 PDF의 JPEG 인코딩으로 저장하죠
optimizeImagesForScreen은 이미지를 최대 HiDPI 화면 해상도로 다운샘플링 합니다 두 가지 선택지를 함께 사용할 수도 있죠
createLinearizedPDF는 인터넷에 최적화된 특별한 종류의 PDF를 생성합니다 인터넷이 나오기 직전에 설계됐던 PDF 형식은 파일의 끝부터 읽죠 따라서 무엇이든 표시하기 전에 파일 전체를 읽어야 합니다 순차적 PDF는 파일의 처음 부분에 있는 첫 페이지 정보를 모두 가지고 있어 웹 브라우저가 빠르게 표시하는 동안 나머지는 로딩할 수 있죠
PDFDocument의 dataRepresentation이나 writeToURL 메서드에 통과시키세요 이상입니다 PDFKit는 강력하고 사용이 쉬우며 iOS와 macOS 기반의 앱에서 널리 사용되고 있죠 iOS 16과 macOS Ventura의 새로운 기능과 함께 이 기능을 활용하면 좋겠습니다
더 알고 싶다면 아래 세션을 참고하여 추가 정보를 얻어가십시오 시청해 주셔서 감사합니다
-
-
6:54 - Implementing the overlay protocol
class Coordinator: NSObject, PDFPageOverlayViewProvider { var pageToViewMapping = [PDFPage: UIView]() func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? { var resultView: PKCanvasView? = nil if let overlayView = pageToViewMapping[page] { resultView = overlayView } else { let canvasView = PKCanvasView(frame: .zero) canvasView.drawingPolicy = .anyInput canvasView.tool = PKInkingTool(.pen, color: .systemYellow, width: 20) canvasView.backgroundColor = UIColor.clear pageToViewMapping[page] = canvasView resultView = canvasView } // If we have stored a drawing on the page, set it on the canvas let page = page as! MyPDFPage if let drawing = page.drawing { resultView?.drawing = drawing; } return resultView } func pdfView(_ pdfView: PDFView, willEndDisplayingOverlayView overlayView: UIView, for page: PDFPage) { let overlayView = overlayView as! PKCanvasView let page = page as! MyPDFPage page.drawing = overlayView.drawing pageToViewMapping.removeValue(forKey: page) } }
-
10:22 - Create a subclass of PDFAnnotation
// Implement a subclass with a drawing override class MyPDFAnnotation: PDFAnnotation { override func draw(with box: PDFDisplayBox, in context: CGContext) { UIGraphicsPushContext(context) context.saveGState() let page = self.page as! MyPDFPage if let drawing = page.drawing { let image = drawing.image(from: drawing.bounds, scale: 1) image.draw(in: drawing.bounds) } context.restoreGState() UIGraphicsPopContext() } }
-
10:35 - Add annotations to your document when saving
override func contents(forType typeName: String) throws -> Any { if let pdfDocument = pdfDocument { // Go through all pages in the document for i in 0...pdfDocument.pageCount-1 { if let page = pdfDocument.page(at: i) { if let drawing = (page as! MyPDFPage).drawing { // Create an annotation of our custom subclass let newAnnotation = MyPDFAnnotation(bounds: drawing.bounds, forType: .stamp, withProperties: nil) // Add our custom data let codedData = try! NSKeyedArchiver.archivedData(withRootObject: drawing, requiringSecureCoding: true) newAnnotation.setValue(codedData, forAnnotationKey: PDFAnnotationKey(rawValue: "drawingData")) // Add our annotation to the page page.addAnnotation(newAnnotation) } } } // -- Option #1: Save the document to a data representation if let resultData = pdfDocument.dataRepresentation() { return resultData } // -- Option #2: Save the document to a data representation and "burn in" annotations let options = [PDFDocumentWriteOption.burnInAnnotationsOption: true] if let resultData = pdfDocument.dataRepresentation(options: options) { return resultData } } // Fall through to returning empty data return Data() }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.