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
500
Other Decks in Technology
See All in Technology
AIコーディングの次。コードレビューと理解負荷を解消して組織の開発生産性を高める
moongift
PRO
1
1.3k
ラジオの科学
frievea
0
240
Flutterをカメラで動かしたかった話
sony
0
120
Software Supply Chain Attackからクラウド環境を守るためにできること
lhazy
2
170
名古屋の市バスGTFS-JPデータ×スガキヤ 最寄りバス停検索をAmazon ElastiCache Serverless for Valkeyで最適化する
usanchuu
1
330
クラウドセキュリティ入門 ~安全なクラウド利用のための基礎知識~
lhazy
10
7.8k
AIが自社のTableauを操作する世界へ:Tableau MCP超入門
tbtykk
0
110
[ChatGPT Work LT]事務作業が苦手な人のための バックオフィスの「半」自動化
chimaki_iot
0
220
NetBoxを利用した作業効率化の試み_NetDevNight4
tnoha
0
470
名刺メーカーDevグループ 紹介資料
sansan33
PRO
0
1.2k
AIは実装を速くする。では、私たちは何を今作るべきか?-立場を越えてリリースに向き合ったチーム開発の実践 / 20260801 Hiromi Nakaya and Naoki Takahashi
shift_evolve
PRO
3
400
Sansan Engineering Unit 紹介資料
sansan33
PRO
1
4.9k
Featured
See All Featured
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Code Review Best Practice
trishagee
74
20k
The Invisible Side of Design
smashingmag
301
52k
Making the Leap to Tech Lead
cromwellryan
135
10k
Heart Work Chapter 1 - Part 1
lfama
PRO
8
36k
ラッコキーワード サービス紹介資料
rakko
1
4.2M
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
Optimizing for Happiness
mojombo
378
71k
GitHub's CSS Performance
jonrohan
1033
470k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
980
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
67
56k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
360
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