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
ras0q
August 24, 2024
Technology
1.3k
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.3k
Embedded FrameworkからSPMへ 段階的移行の軌跡
ras0q
0
520
Other Decks in Technology
See All in Technology
少人数データチームのDevin活用実践事例
runandy16
2
210
魔法少女がClaude Codeを監視する / ccportal-deck
noriyukitakei
3
110
Driving AI Adoption Using In-House GPUs to Serve Qwen
po3rin
1
280
PdMをやめて、 "プロダクトビルダー"という 働き方に変えました / PdM to Product Builder
shikichee
2
610
全社に広がるMCPサーバーを、 どう安全に管理するか MCPass開発の舞台裏
mtpooh
3
240
VPCでもFloatingIPを使いたいんだ
y_kotani
1
140
Jetpack Compose で挑む新聞紙面UI ─ 複合ジェスチャー・ポリゴン記事領域・適応的ページ構成という3つの壁/droidkaigi2026
nikkei_engineer_recruiting
0
130
Azure App Service / Container Apps の組み込み認証
kuniteru
0
130
Bill One 開発エンジニア 紹介資料
sansan33
PRO
7
20k
Continuous Delivery! It is not what you think it is
tdpauw
0
120
KAEN Company Deck
kaen
PRO
0
240
tamachi.goを支える技術
rymiyamoto
0
130
Featured
See All Featured
Making the Leap to Tech Lead
cromwellryan
135
10k
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
680
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
490
Believing is Seeing
oripsolob
1
200
Speed Design
sergeychernyshev
33
2.1k
VelocityConf: Rendering Performance Case Studies
addyosmani
331
25k
sira's awesome portfolio website redesign presentation
elsirapls
0
380
Git: the NoSQL Database
bkeepers
PRO
432
67k
Designing for humans not robots
tammielis
254
26k
Accessibility Awareness
sabderemane
1
190
Making Projects Easy
brettharned
120
6.7k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
400
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