Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
PencilKitで実装するPDFへの手書き注釈 / Handwritten-annotati...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
ras0q
August 24, 2024
Technology
1.2k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
PencilKitで実装するPDFへの手書き注釈 / Handwritten-annotations to PDF with PencilKit
https://github.com/ras0q/iosdc2024#readme
を御覧ください
ras0q
August 24, 2024
More Decks by ras0q
See All by ras0q
iOS16で変わった画面の向きを操作する方法 - iOSDC Japan 2023
ras0q
0
4.2k
Embedded FrameworkからSPMへ 段階的移行の軌跡
ras0q
0
490
Other Decks in Technology
See All in Technology
ChatworkとBPaaS 異なる特性で学んだAI機能開発の ベストプラクティス
kubell_hr
2
2.9k
ITエンジニアを取り巻く環境とキャリアパス / A career path for Japanese IT engineers
takatama
4
1.8k
Unlocking the Apps
pimterry
0
240
AI Adaptable なテストを整える工夫 / Ways to Make Your Tests AI-Adaptable
bitkey
PRO
3
220
探して_入れて_作って_使う_Agent_Skills___LT.pdf
peintangos
2
160
Databricks における 生成AIガバナンスの実践
taka_aki
1
320
Oracle AI Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
6
1.5k
新アーキテクチャ「TiDB X」解説とDedicated比較 TiDB Cloud Premiumのゲーム運用活用を検証
staffrecruiter
0
120
ポケモンの型をTypeScriptの型システムで表現してみた
subroh0508
0
330
Mastering Ruby Box
tagomoris
3
150
個人最適 から 全体最適 へ AI情報共有会・AIギルド・AI-DLC で進める カンリーの組織展開
rfdnxbro
0
1.6k
「嘘をつくテスト」の失敗例から学ぶ 良いテストコード #frontend_phpcon_do
asumikam
0
480
Featured
See All Featured
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
420
Making Projects Easy
brettharned
120
6.7k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.6k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
How to train your dragon (web standard)
notwaldorf
97
6.7k
ラッコキーワード サービス紹介資料
rakko
1
3.6M
Agile that works and the tools we love
rasmusluckow
331
21k
Done Done
chrislema
186
16k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.2k
Code Review Best Practice
trishagee
74
20k
Transcript
PencilKitで実装する PDFへの手書き注釈 iOSDC Japan 2024 Day2 Track B Ras (@ras0q)
Apple Pencilを活用した iOSアプリを作りたい!
Apple Pencilを活用したiOSアプリを作りたい! アプリの機能の1つとして手書きの描画機能を実装したい お絵かき 手書きメモ 世の中のPDF注釈アプリが需要にマッチしないため自作したい 複雑すぎる操作UI ファイルのアクセス制限 無限に現れる広告 など…
このトークを見ることでApple Pencilを一層活用できるようになります!!!
Keynote 1. PencilKitとは? 2. PencilKitでアプリを作る 3. PDFに描画する 4. 描画をPDF注釈として保存する 5.
PencilKitを使ってみた感想 ↓↓サンプルレポジトリ ↓↓ github.com/ras0q/iosdc2024 Powered by
Ras ras0q.com 東京工業大学 大学院 修士1年 10月に大学が改名するらしい デジタル創作同好会traP Web開発をメインに創作をしています traPortfolio をリリースしました
ピクシブ株式会社 アルバイト iOSアプリエンジニア育成プロジェクト pixiv / pixiv Sketch / Pastela iOSDC Japan 参加(3) 登壇(2)
Keynote 1. PencilKitとは 2. PencilKitでアプリを作る 3. PDFに描画する 4. 描画をPDF注釈として保存する 5.
PencilKitを使ってみた感想 ↓↓サンプルレポジトリ ↓↓ github.com/ras0q/iosdc2024
PencilKit?
PencilKit? 引用元: WWDC19
PencilKit ドローイングをiOSアプリに組み込むことができる純正ライブラリ 指やApple Pencilからの入力を受け取ってアプリで使う画像データに変換する 描画ツール搭載 鉛筆のほかに 万年筆や定規も... 純正アプリにも ファイル, メモ,
写真... アプリ間の連携 別アプリへ図形の コピペが可能
Keynote 1. PencilKitとは? ✅ 2. PencilKitでアプリを作る 3. PDFに描画する 4. 描画をPDF注釈として保存する
5. PencilKitを使ってみた感想 ↓↓サンプルレポジトリ ↓↓ github.com/ras0q/iosdc2024
try! PencilKit in 1 minute
try! PencilKit import UIKit class ViewController: UIViewController { override func
viewDidLoad() { super.viewDidLoad() } }
try! PencilKit import PencilKit private lazy var canvasView = PKCanvasView(frame:
view.frame) view.addSubview(canvasView) import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } }
try! PencilKit private lazy var toolPicker = PKToolPicker() toolPicker.addObserver(canvasView) toolPicker.setVisible(true,
forFirstResponder: canvasView) canvasView.becomeFirstResponder() import PencilKit import UIKit class ViewController: UIViewController { private lazy var canvasView = PKCanvasView(frame: view.frame) override func viewDidLoad() { super.viewDidLoad() view.addSubview(canvasView) } }
try! PencilKit import PencilKit import UIKit class ViewController: UIViewController {
private lazy var canvasView = PKCanvasView(frame: view.frame) private lazy var toolPicker = PKToolPicker() override func viewDidLoad() { super.viewDidLoad() view.addSubview(canvasView) toolPicker.addObserver(canvasView) toolPicker.setVisible(true, forFirstResponder: canvasView) canvasView.becomeFirstResponder() } }
✅ PencilKitでアプリを作る
Keynote 1. PencilKitとは? ✅ 2. PencilKitでアプリを作る ✅ 3. PDFに描画する 4.
描画をPDF注釈として保存する 5. PencilKitを使ってみた感想 ↓↓サンプルレポジトリ ↓↓ github.com/ras0q/iosdc2024
try! PDF Integration
PDFKit import UIKit class ViewController: UIViewController { override func viewDidLoad()
{ super.viewDidLoad() } }
PDFKit import PDFKit private lazy var pdfDocument = PDFDocument(somePDFURL) import
UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } }
PDFKit private lazy var pdfView: PDFView = { let view
= PDFView(frame: view.frame) view.document = pdfDocument return view }() view.addSubview(pdfView) import PDFKit import UIKit class ViewController: UIViewController { private lazy var pdfDocument = PDFDocument(somePDFURL) override func viewDidLoad() { super.viewDidLoad() } }
PDFKit import PDFKit import UIKit class ViewController: UIViewController { private
lazy var pdfDocument = PDFDocument(somePDFURL) private lazy var pdfView: PDFView = { let view = PDFView(frame: view.frame) view.document = pdfDocument return view }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(pdfView) } }
✅ 画面にPDFを表示する
PencilKitを使ってPDFに描画できるようにするには?
None
None
None
None
PDFPageOverlayViewProvider class ViewController: UIViewController { private lazy var pdfView: PDFView
= { let view = PDFView(frame: view.frame) view.document = pdfDocument return view }() }
PDFPageOverlayViewProvider view.pageOverlayViewProvider = self extension ViewController: PDFPageOverlayViewProvider { func pdfView(_:
PDFView, overlayViewFor page: PDFPage) -> UIView? { } } class ViewController: UIViewController { private lazy var pdfView: PDFView = { let view = PDFView(frame: view.frame) view.document = pdfDocument return view }() }
PDFPageOverlayViewProvider // 各ページに被せるキャンバスを作成 private lazy var canvasViews = (0..<ページ数).map {
_ in PKCanvasView() } // ページ番号に対応するキャンバスを返す canvasViews[page.ページ番号] class ViewController: UIViewController { private lazy var pdfView: PDFView = { let view = PDFView(frame: view.frame) view.document = pdfDocument view.pageOverlayViewProvider = self return view }() } extension ViewController: PDFPageOverlayViewProvider { func pdfView(_: PDFView, overlayViewFor page: PDFPage) -> UIView? {
PDFPageOverlayViewProvider class ViewController: UIViewController { private lazy var pdfView: PDFView
= { let view = PDFView(frame: view.frame) view.document = pdfDocument view.pageOverlayViewProvider = self return view }() // 各ページに被せるキャンバスを作成 private lazy var canvasViews = (0..<ページ数).map { _ in PKCanvasView() } } extension ViewController: PDFPageOverlayViewProvider { func pdfView(_: PDFView, overlayViewFor page: PDFPage) -> UIView? { // ページ番号に対応するキャンバスを返す canvasViews[page.ページ番号]
描画ツールも設定する class ViewController: UIViewController { private lazy var pdfView: PDFView
= ... private lazy var canvasViews = ... override func viewDidLoad() { // PDFViewの設定... } }
描画ツールも設定する private lazy var toolPicker = PKToolPicker() for canvasView in
canvasViews { toolPicker.addObserver(canvasView) } // 今回のfirst responderはPDFView toolPicker.setVisible(true, forFirstResponder: pdfView) pdfView.becomeFirstResponder() class ViewController: UIViewController { private lazy var pdfView: PDFView = ... private lazy var canvasViews = ... override func viewDidLoad() { // PDFViewの設定... } }
描画ツールも設定する pdfView.addGestureRecognizer(canvasView.drawingGestureRecognizer) class ViewController: UIViewController { private lazy var pdfView:
PDFView = ... private lazy var canvasViews = ... private lazy var toolPicker = PKToolPicker() override func viewDidLoad() { // PDFViewの設定... for canvasView in canvasViews { toolPicker.addObserver(canvasView) } // 今回のfirst responderはPDFView toolPicker.setVisible(true, forFirstResponder: pdfView) pdfView.becomeFirstResponder() } }
描画ツールも設定する class ViewController: UIViewController { private lazy var pdfView: PDFView
= ... private lazy var canvasViews = ... private lazy var toolPicker = PKToolPicker() override func viewDidLoad() { // PDFViewの設定... for canvasView in canvasViews { toolPicker.addObserver(canvasView) pdfView.addGestureRecognizer(canvasView.drawingGestureRecognizer) } // 今回のfirst responderはPDFView toolPicker.setVisible(true, forFirstResponder: pdfView) pdfView.becomeFirstResponder() } }
❓ PDFに描画する (完成…?) 最後のページにしか描画できない (動画は上のページにも描こうとしています) WARNING: "Drawing did change that
is not in text."
❓ PDFに描画する (完成…?) 最後のページにしか描画できない (動画は上のページにも描こうとしています) WARNING: "Drawing did change that
is not in text." pdfView.addGestureRecognizer(...) 1. Recognizerの認識範囲はPDFView全体 2. 複数追加されたRecognizerの範囲が重複する 3. 最後に追加されたRecognizerのみが発火する → 最後のページしか正常に認識されない 😭
❓ PDFに描画する (完成…?) 最後のページにしか描画できない (動画は上のページにも描こうとしています) WARNING: "Drawing did change that
is not in text." pdfView.addGestureRecognizer(...) 1. Recognizerの認識範囲はPDFView全体 2. 複数追加されたRecognizerの範囲が重複する 3. 最後に追加されたRecognizerのみが発火する → 最後のページしか正常に認識されない 😭 → 描画ごとに発火させるRecognizerを切り替える必要がある
Override func hitTest(_:with:) タップしたページのキャンバスのRecognizerのみを有効化させる class CanvasPDFView: PDFView { override func
hitTest(_ point:CGPoint,with e:UIEvent?) -> UIView? { return super.hitTest(point, with: e) } }
Override func hitTest(_:with:) タップしたページのキャンバスのRecognizerのみを有効化させる if let activePage = page(for: point,
nearest: true) { } class CanvasPDFView: PDFView { override func hitTest(_ point:CGPoint,with e:UIEvent?) -> UIView? { return super.hitTest(point, with: e) } }
Override func hitTest(_:with:) タップしたページのキャンバスのRecognizerのみを有効化させる protocol CanvasPDFViewDelegate: AnyObject { func switchActivePage(to
page: PDFPage) } var interactionDelegate: (any CanvasPDFViewDelegate)? interactionDelegate?.switchActivePage(to: activePage) class CanvasPDFView: PDFView { override func hitTest(_ point:CGPoint,with e:UIEvent?) -> UIView? { if let activePage = page(for: point, nearest: true) { } return super.hitTest(point, with: e) } }
ViewController側で有効なRecognizerを切り替える class ViewController: UIViewController { private lazy var pdfView: PDFView
= { let view = PDFView(frame: view.frame) view.document = pdfDocument view.pageOverlayViewProvider = self return view }() }
ViewController側で有効なRecognizerを切り替える class ViewController: UIViewController { private lazy var pdfView: CanvasPDFView
= { let view = PDFView(frame: view.frame) view.document = pdfDocument view.pageOverlayViewProvider = self return view }() }
ViewController側で有効なRecognizerを切り替える view.interactionDelegate = self extension ViewController: CanvasPDFViewDelegate { func switchActivePage(to
page: PDFPage) { let activeCanvasView = canvasView[page.ページ番号] for canvasView in canvasViews { let isActiveView = (canvasView == activeCanvasView) canvasView.drawingGestureRecognizer.isEnabled = isActiveView } class ViewController: UIViewController { private lazy var pdfView: CanvasPDFView = { let view = PDFView(frame: view.frame) view.document = pdfDocument view.pageOverlayViewProvider = self return view }() }
✅ PDFに描画する
Keynote 1. PencilKitとは? ✅ 2. PencilKitでアプリを作る ✅ 3. PDFに描画する ✅
4. 描画をPDF注釈として保存する 5. PencilKitを使ってみた感想 ↓↓サンプルレポジトリ ↓↓ github.com/ras0q/iosdc2024
try! PDF Annotation
PKCanvasView → PDFView
PKCanvasView → PDFView
注釈用クラスのイニシャライザ class CanvasPDFAnnotation: PDFAnnotation { private let drawing: PKDrawing init(drawing:
PKDrawing, page: PDFPage) { self.drawing = drawing var pdfBounds = drawing.bounds pdfBounds.origin.y = page.bounds(for: .mediaBox).height - drawing.bounds.height - drawing.bounds.origin.y super.init(bounds: pdfBounds, forType: .ink) self.page = page } }
注釈用クラスのイニシャライザ var pdfBounds = drawing.bounds pdfBounds.origin.y = page.bounds(for: .mediaBox).height -
drawing.bounds.height - drawing.bounds.origin.y class CanvasPDFAnnotation: PDFAnnotation { private let drawing: PKDrawing init(drawing: PKDrawing, page: PDFPage) { self.drawing = drawing super.init(bounds: pdfBounds, forType: .ink) self.page = page } }
注釈用クラスのイニシャライザ これは? var pdfBounds = drawing.bounds pdfBounds.origin.y = page.bounds(for: .mediaBox).height
- drawing.bounds.height - drawing.bounds.origin.y class CanvasPDFAnnotation: PDFAnnotation { private let drawing: PKDrawing init(drawing: PKDrawing, page: PDFPage) { self.drawing = drawing super.init(bounds: pdfBounds, forType: .ink) self.page = page } }
View Coordinates → PDF Coordinates 座標系をy軸反転させる必要がある boundsの基準点も変わる 図形が反転するわけではない
再掲: 注釈用クラスのイニシャライザ var pdfBounds = drawing.bounds pdfBounds.origin.y = page.bounds(for: .mediaBox).height
- drawing.bounds.height - drawing.bounds.origin.y class CanvasPDFAnnotation: PDFAnnotation { private let drawing: PKDrawing init(drawing: PKDrawing, page: PDFPage) { self.drawing = drawing super.init(bounds: pdfBounds, forType: .ink) self.page = page } }
キャンバスの描画を注釈として追加する 注釈の追加/更新時に PDFAnnotation#draw() が呼ばれる class CanvasPDFAnnotation: PDFAnnotation { override func
draw(with box: PDFDisplayBox,in context: CGContext) { super.draw(with: box, in: context) } }
キャンバスの描画を注釈として追加する 注釈の追加/更新時に PDFAnnotation#draw() が呼ばれる UIGraphicsPushContext(context) context.saveGState() defer { context.restoreGState() UIGraphicsPopContext()
} class CanvasPDFAnnotation: PDFAnnotation { override func draw(with box: PDFDisplayBox,in context: CGContext) { super.draw(with: box, in: context) } }
キャンバスの描画を注釈として追加する 注釈の追加/更新時に PDFAnnotation#draw() が呼ばれる let image = drawing.image(from: drawing.bounds, scale:
1.0) class CanvasPDFAnnotation: PDFAnnotation { override func draw(with box: PDFDisplayBox,in context: CGContext) { super.draw(with: box, in: context) UIGraphicsPushContext(context) context.saveGState() defer { context.restoreGState() UIGraphicsPopContext() } } }
キャンバスの描画を注釈として追加する 注釈の追加/更新時に PDFAnnotation#draw() が呼ばれる context.draw(image.cgImage!, in: bounds) // PDF座標系のbounds class
CanvasPDFAnnotation: PDFAnnotation { override func draw(with box: PDFDisplayBox,in context: CGContext) { super.draw(with: box, in: context) UIGraphicsPushContext(context) context.saveGState() defer { context.restoreGState() UIGraphicsPopContext() } let image = drawing.image(from: drawing.bounds, scale: 1.0) } }
キャンバスの描画を注釈として追加する 注釈の追加/更新時に PDFAnnotation#draw() が呼ばれる class CanvasPDFAnnotation: PDFAnnotation { override func
draw(with box: PDFDisplayBox,in context: CGContext) { super.draw(with: box, in: context) UIGraphicsPushContext(context) context.saveGState() defer { context.restoreGState() UIGraphicsPopContext() } let image = drawing.image(from: drawing.bounds, scale: 1.0) context.draw(image.cgImage!, in: bounds) // PDF座標系のbounds } }
PDF注釈の使用例 ページ全体の描画を1つの注釈に 各ストロークの描画をそれぞれの注釈に let annotation = CanvasPDFAnnotation( drawing: canvasView.drawing, page:
page ) page.addAnnotation(annotation) for stroke in canvasView.drawing.strokes { let annotation = CanvasPDFAnnotation( drawing: PKDrawing(strokes: [stroke]), page: page ) page.addAnnotation(annotation) }
PDFView → Raw PDF File
PDFView → Raw PDF File
注釈をファイルに保存する PDFDocument#dataRepresentation() から Data を抽出 ファイルURLを指定し書き込む let data = pdfDocument.dataRepresentation()
let documentURL = pdfDocument.documentURL try data.write(to: documentURL)
✅ 描画をPDF注釈として保存する
Keynote 1. PencilKitとは? ✅ 2. PencilKitでアプリを作る ✅ 3. PDFに描画する ✅
4. 描画をPDF注釈として保存する ✅ 5. PencilKitを使ってみた感想 ↓↓サンプルレポジトリ ↓↓ github.com/ras0q/iosdc2024
PencilKitを使ってみた感想
ご清聴ありがとうございました! サンプルレポジトリ も是非ご覧ください! Presenter: Ras (@ras0q )
References PencilKit | Apple Developer Documentation PDFKit | Apple Developer
Documentation What’s new in PDFKit - WWDC22 - Videos - Apple Developer iOSのPDFKitを利用してPDFを編集する | Zenn