ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
PDFKitの新機能
PDFKitはあなたのAppに、PDFドキュメントの表示・編集・保存の機能を提供するフル機能のフレームワークです。Live Textやフォームのサポート、画像からのPDF作成、インタラクティブオーバーレイの構築、注釈の保存など、PDFKitの最新機能を紹介します。
リソース
関連ビデオ
WWDC22
WWDC19
-
ダウンロード
Conrad Carlenです PDFKitの新機能についてお話します 議題はこちら まずPDFKitの簡単なおさらい 次にLive Textとフォームなどの新機能 画像からPDFを作成する 新しい方法 最後にオーバーレイビューについて まずPDFKitの機能を見直してみましょう PDFKitはAppのフレームワークで 閲覧や編集を可能にします iOSやmacOS Mac Catalystで利用でき UIViewをAppに組み込むためのラッパーである UIViewRepresentableを使って SwiftUIで使用することも可能です PDFKitは機能のほとんどをカバーする 4つのコアクラスで構成されます PDFViewはSwiftUIやIBを使って レイアウトするウィジェットです PDFドキュメントのコンテンツを表示し ズーム設定やテキストのコピーも可能
PDFDocumentはPDFファイルを表します 通常サブクラス化しませんが 必ず使うクラスで PDFオブジェクトグラフのルート またはツリーの幹です これらのいずれかがツリーの作成に必須です
各ドキュメントには1つか複数の PDFPageオブジェクトを含みます コンテンツをレンダリングし そのページに固有のフォントや画像などの リソースを保存します
オブジェクトグラフの葉の部分には PDFAnnotationsのオプションもあります PDFPageは編集を目的としませんが 注釈は多くの場合編集可能です 各オブジェクトの役割も説明します PDFKitの基本について詳しく知るには 下にリンクされている "Introducing PDFKit" のセッションをご覧ください
ではiOS16とVenturaで導入された 新機能について
PDFKitはLive Textをサポートします これはタップしてコピーできるテキストが 少量である写真Appとは異なります 写真と違って PDFで文字が表示されれば テキストとして扱えることが期待されます Live Textを使ってPDF文書内の文字列を選択し 検索できるようになりました これはビットマップをスキャンしただけのもので テキストはまったくありません
もちろんPDFは多くのページを持てるので PDF文書を開いたときに 全ページにOCRを適用するのではなく PDFKitは各ページを操作するときに オンデマンドで実行します OCRはそのファイルに適用されるので 文書のコピーを作成する必要はありません 文書全体のテキストを保存したい場合は 保存時にそのオプションがあります
さらにPDFKitはフォーム処理を改善しました フォームフィールドを含む文書は テキストフィールドが組み込まれていなくても 自動的に認識されます テキストフィールドをタブで移動して 期待通りにテキストを入力できます
次は画像からPDFを作成するための 新APIについて説明します
iOS16とmacOS Venturaには 画像を入力としてPDFを作成できる 柔軟な新らしいAPIを用意しました AppはCGImageRefで画像を提供します PDFKitはCGImageRefを受け取り 高品質のJPEGエンコーディングで圧縮します CGImageRefはCoreGraphicsの ネイティブデータ型なので 追加の変換は不要です
最も一般的なケースを処理するのに役立つ いくつかのオプションがあります
MediaBoxでページの大きさを指定 画像に合わせるか用紙サイズを選択できます
縦向きや横向きの指定もできます
画像がMediaBox より大きい場合 MediaBoxに収まるよう 画像を縮小します UpscaleIfSmallerが指定されている場合 画像がMediaBoxより小さい時は ページ全体に拡大されます
多く寄せられた質問にお答えします 「PencilKitでPDFに描画するには?」 オーバーレイビューを使用します
以前はPDFPageをサブクラス化し 描画メソッドをオーバーライドするか カスタムPDFアノテーションが必要でした iOS16とVenturaでは各PDFのページ上に 独自のビューをオーバーレイできます これでPDF上にインタラクティブな ライブビューを作成できます オーバーレイについて知っておくべき3点は まず新プロトコルによりオーバーレイビューを PDFページにインストールします
コンテンツはPDFに組込んで保存します PDF文書を保存する際の ベストプラクティスについて紹介します
オーバーレイビューのインストールは簡単です PDFは数百ページにもなる可能性もあるため 開く時にすべてのページのビューを 作成することはできません ユーザーが高速でスクロールした場合は? ビューをいつ作成するべきか知る方法は?
PDFKitはページをスクロールして表示する前に コンテンツをインテリジェントに 準備するよう設計されています オーバーレイビューを要求する タイミングも万全です Appは新しいプロトコルによるリクエストに 応答する必要があるだけです
PDFPageOverlayViewProviderが 新しいプロトコルです PDFKitPlatformViewは プラットフォームに応じて UIViewかNSViewとして定義されます 最も重要なメソッドはoverlayViewForPageです ビューのインスタンスを提供するだけで PDFKitは適切な制約を適用して そのサイズを調整します またページの回転がゼロ以外の場合は 回転させます
次の2つのメソッドはオプションです willDisplayOverlayViewを使用して 独自のジェスチャーハンドラをインストールしたり PDFKitのジェスチャーとの衝突を 調整することができます
willEndDisplayingOverlayViewはPDFKitが あなたのビューを終了したときに呼び出されます おそらくスクロールアウトされたためです ここでビューを解放することができますが このメソッドにはもう一つ重要な使い方があります 描画を表すデータがある場合はこのメソッドで データを取得しておくこともできます ここではPencilKitを使用していますが ビューデータが別の場所にある場合は これを実装する必要はありません
これがこの例でプロバイダとして 使用しているクラスです PDFPageOverlayViewProviderプロトコルを 実装しています これはiOSなのでUIViewです PDFPage から UIView へは map を使用します プロトコルメソッドの宣言はこうなります 次に実装を見てみましょう overlayViewForPageはpageToViewMappingで ビューが作成済かどうかを確認します まだの場合は新しいビューを作成します いずれの場合もページからdrawingを取得して キャンバスビューに設定します ここではPDFPageのサブクラスを使っています drawingプロパティを追加するだけです
次のメソッドを見てみましょう willEndDisplayingです
OverlayViewはシンプルです ビューからdrawingを取得し カスタムページクラスに保存します 実際の動作を見てみます
この時期にはいつも釣りをしますが 今年はWWDCですので 別の人が代わりに参加しているため お気に入りスポットを彼に 紹介したいと思います このAppではPencilKitを オーバーレイ表示で使用します 構成は今見たコード以外はごくわずかで オーバーレイを表示するためのコードは約30行です グランドレイクはダム湖なんですが たくさん魚がいる穴場ですね ここを下ると ダム湖に行って釣りができます あるいこの道を進んで ダムを越えてからここを通って 釣りすることもできます そこからここでも釣れます 一周してからの先には 間違っても行かないように 流れが急に深くなります ここは避けて 養魚場へ 養魚場の横を進んで この水場に移ると あちこちでキャスティングできます ここは穴場で私はいつもここで魚を釣っています
さてルートが書けたので ズームとスクロールを実行してみましょう
良いレスポンスですよね
以上になります PDFKitのオーバーレイビューでした スケッチができたので次は保存についてです それにはPDFAnnotationクラスを使用します 保存するときに実現したいことは2つ 画面上の表示を 忠実に一致させることと ラウンドトリップ編集です PDFアノテーションにはこれを 容易にするいくつかの機能があり 「アピアランスストリーム」と呼ばれる PDFの描画コマンドのストリームを 持つことができます Quartz2Dで描画できるものは ほぼすべてを記録できます それ以外は画像にレンダリングして記録します Metalを使用している場合は画像で記録しています PDFの描画として記録されているため Adobe ReaderやChromeなどでも 同じように表示されます
PDFの注釈はdictionaryとしてPDF文書に保存します カスタムデータをプライベートキーと値のペアで 保存することもできます どのようなコードになるのか見てみましょう PDFAnnotationのサブクラスの作成から始めます drawメソッドをオーバーライドする作業です PDFKitは前のスライドで説明した アピアランスストリームを保存するときに このメソッドを呼び出します
ドキュメントを保存するためcontentsを上書きします 関数の概要を次に示します PDFDocumentのすべてのページをループします ループの中の処理は...
各ページに対して次の作業を行います カスタムクラスのアノテーションを作成し drawingをデータにエンコードして データをアノテーションに追加します 次にこの文書を開くときに value:forAnnotationKey を使用して 保存されたdrawingデータを読み出し それをオーバーレイビューに配置します
最後にページに注釈を追加します contentsのオーバーライドに戻ります ページに注釈を追加したので PDFDocumentのdataRepresentationで結果を返します
コンテンツが注釈として保存されると コンテンツの移動やサイズ変更や削除も可能になります 望まれる機能ですね ページの一部として注釈を“焼き付け”たい場合 iOS16とmacOS Venturaには これを行うPDFDocumentWriteOptionが使えます 保存オプションに burnInAnnotationsOption=trueを 追加するだけです 処理はこれだけです
PDF書き込みオプションと言えば iOS16とmacOSVenturaで 利用できるようになったもを 見ていくことにします CoreGraphicsは常に最高画質で 画像をPDFに保存するため 画像はロスレス圧縮でフル解像度です PDFが大判プリンターで 印刷される場合に最高です 画面に表示されることが多いPDFでは 忠実度の高い画像データをすべて保存すると ファイルサイズが非常に大きくなってしまいます これに対処するために 2つのオプションがあります
saveAllImagesAsJPEGはその名のとおり 画像はすべてJPEGエンコーディングで PDFに保存します
optimizeImagesForScreenは 画像をHiDPIスクリーンの解像度に ダウンサンプリングします これら2つのオプションは同時に使用できます
createLinearizedPDFはインターネット用に 最適化された特別な種類のPDFを作成します インターネット登場前に設計されたPDF形式は ファイルの最後から読み取られます つまり表示する前に全体を ダウンロードする必要がありました 線形化されたPDFにはファイルの先頭に 最初のページの表示に必要な すべてが含まれており ブラウザーは残りの部分がロード中でも 表示できるようになっています
これらのオプションはPDFDocument の dataRepresentation や writeToURL メソッドに 渡すことも可能です 以上になります PDFKitはパワフルで使いやすく iOSやmacOSの多くのAppで使われており iOS16とmacOSVenturaで 新機能も追加されました どんな風に使割れるか楽しみです
詳細については以下のセッションを ご確認ください ご清聴ありがとうございます
-
-
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() }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。