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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
徹底討論!ECS vs EKS!
daitak
3
1.8k
AWS PrivateLink × SCIM で実現する セキュアで運⽤負荷の低い Databricks 基盤の構築
tsuda7
0
110
Text-to-SQLをAgentCoreで実現し、生成されるSQLの精度を定量的に評価する
yakumo
2
150
AIをフル活用してオンコール機能のプロトタイプを2日で作った話 / Building an AI-Powered On-Call Prototype in Just Two Days
nari_ex
0
150
初めてのDatabricks勉強会
taka_aki
2
200
本当の”仕事”を手放せる未来が見えた
mu7889yoon
0
200
フルカイテン株式会社 エンジニア向け採用資料
fullkaiten
0
11k
Multi-Agent並列開発を 安全に回すための技術 / Technology for Safely Multi-Agent Parallel Development
tooppoo
0
220
きのこカンファレンス2026_肩書きを外したとき私は誰か
yamasatimi
1
100
5分でわかる Amazon Connect_20260608
hwangbyeonghun
0
140
Hatena Engineer Seminar 37 jj1uzh
jj1uzh
0
210
toB プロダクトから見たWAF
tokai235
0
250
Featured
See All Featured
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.1k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
210
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
400
Building Applications with DynamoDB
mza
96
7.1k
The Invisible Side of Design
smashingmag
301
52k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
160
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.7k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.5k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
400
sira's awesome portfolio website redesign presentation
elsirapls
0
290
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