Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Effective PencilKit / 新聞スクラップ体験の実現

Go Takagi
September 12, 2022

Effective PencilKit / 新聞スクラップ体験の実現

iOSDC2022 Day 2 Track B 13:00〜
Effective PencilKit / 新聞スクラップ体験の実現

WWDC 2019 で発表された PencilKit を利用することで、数行のコードで 標準メモアプリと同様の手描き体験をアプリに導入できます。

発表に先立ち、日本経済新聞社の紙面ビューアーアプリでは、Apple Pencil を用いた紙面画像にメモやハイライトを書き込める機能をリリースしました。

アプリの機能要件を満たすための独自拡張の実現には、様々な制約が立ちはだかりました。
たとえば、キャンバスに画像を載せる、ズームやスクロールなどビューアーとしての操作は残しつつ書き込みを一時的に無効にするなど、一見すると単純そうですが一筋縄ではいきません。

本セッションでは PencilKit の開発ノウハウを、ドキュメントと内部の動きから洞察した知見の両面から解説します。開発経験を踏まえ、紙の新聞に書き込みを行うユーザー体験をどのようにアプリへ落とし込んでいったか説明できればと思います。

Go Takagi

September 12, 2022
Tweet

More Decks by Go Takagi

Other Decks in Technology

Transcript

  1. E ff ective PencilKit 新聞スクラップ体験の実現 Go Takagi 20 22 /

    09 / 12 iOSDC JAPAN 2 022 Day 2 #Track B 13 : 0 0 〜
  2. Me ( Go Takagi ) ‣ID • shimastripe / shimastriper

    ‣Work • 株式会社 ⽇本経済新聞社 ‣ iOS 紙⾯ビューアーアプリ の開発を担当 • iOSDC NOC チーム ‣Like • 柴⽝が⼤好きです! 2 2年ぶりにカムバックです!
  3. Keynote ‣PencilKit を拡張する • Example より更に先のユースケースへ対応 ‣Points • PencilKit で

    Preview / Edit できる画⾯ • 独⾃のペンツールを統合 • テキストハイライト機能 • ツールパレット • Test‧その他Tips 3
  4. 紙⾯ビューアー: 新聞紙⾯画像に特化 ‣紙⾯画像形式で読める • 縦 (+ 横) 書き 両対応 •

    iPad 利⽤者多い • オフライン動作 • Wi-Fi で夜間に⾃動DL • iOSDC 2 0 2 0 もぜひ 🙏 5 AppStore ページより
  5. 描いたメモは保存(ロック)し、表⽰+⾮表⽰できる ‣編集モードが切り替えできる • Preview / Edit • Preview 中は Scroll

    / Zoom のみ可能 ‣描き込み表⽰が切り替えできる • 描き込み ON / OFF ‣保存したデータはSyncされる • 端末をまたいで描き込みメモが残せる 1 0
  6. WWDC: 19 登場 ‣3⾏のコードで Apple Pencil 描き込み体験を実現 • キャンバスの作成 •

    ビュー階層の追加 • Ink の選択 ‣iPad だけでなく iPhone も可能 • 指で描き込み 1 4 let canvas = PKCanvasView(frame: view.frame) view.addSubview(canvas) canvas.tool = PKInkingTool(.pen, color: .black, width: 30)
  7. Apple Pencil ‣低レイテンシ • 1秒間に240回スキャニングして情報を送る (240 Hz) • 描いたイメージとディスプレイへの描画遅れが極⼩ ‣たくさんの情報

    • altitude (⾼度) / pressure (圧⼒) / azimuth (⽅位) ‣Scribble による⼿書き認識 (iOS 1 4 +) • タブレットの⼊⼒⽅法に新しい体験 1 5
  8. PencilKit のここが凄い! ‣Apple Pencil 体験を容易に導⼊できる • OS 標準の Canvas が再現できる

    ‣低レイテンシ • Pencil から送られる⼊⼒を素早く画⾯に反映 • Metal の API を⽤いたレンダリング ‣描画したものが構造的なデータで表されている • 永続化して再編集可能 ‣Image形式で出⼒可能 • サムネイル画像が⽣成できる 1 6 WWDC: 1 9 Introducing PencilKit
  9. 独⾃実装する場合 (≠ PencilKit ) ‣UIKit のタッチイベントは 60 Hz • 精度⾼く滑らかに描画するために、2種類のデータをハンドリングする

    • 予測値を受けとって爆速描画‧遅れて来る実測値を更新反映 (補正) • ⼀筋縄ではいかない!PencilKit とても便利 1 7 https://developer.apple.com/documentation/uikit/pencil_interactions/handling_input_from_apple_pencil https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_touches_in_your_view/getting_high- fi delity_input_with_coalesced_touches
  10. Example App: Drawing with PencilKit ‣⼀通りまとまってる • Canvas を表⽰する •

    描き込み領域の⾃動拡張⽅法 • ペンのツールパレットを表⽰する • Undo / Redo • 署名 (Signature) • サムネイル⽣成 • PKDrawing (描き込みデータ) の永続化 1 8 https://developer.apple.com/documentation/pencilkit/drawing_with_pencilkit ⼀通りアプリが作れそう!
  11. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 2 0
  12. ⼤まかな構成 ‣PKCanvasView • キャンバスとなる UIScrollView ‣PKToolPicker • ペンツール (ペン‧鉛筆‧消しゴム‧定規) •

    PKTool: PKInkingTool / PKEraserTool / PKLassoTool ‣PKDrawing • 描き込みデータを表す構造体‧ストロークのパスも取れる • Codable に対応、永続化‧復元が容易にできる • UIImage への変換も可能 2 3 https://developer.apple.com/documentation/pencilkit/drawing_with_pencilkit
  13. 拾えるイベント ‣PKCanvasViewDelegate • UIScrollViewDelegate • PKDrawing 周りの変更イベント • Tool を使って描き始め

    / 終わり ‣PKToolPickerObserver • ToolPicker の選択 / 移動 / 表⽰ 2 4 ダークモードだと⾊が反転 https://developer.apple.com/documentation/pencilkit/drawing_with_pencilkit
  14. Example から学べること ‣⼀連の描き込み体験は作れる • 描き込みキャンバスの実現 • Drawing の変更時に Undo /

    Redo を適宜更新 • 余⽩が狭くなったらキャンバスサイズの更新 • リアルタイムで更新して無限キャンバスも • Canvas をカスタマイズする要素は⾒当たらない • Canvasにイメージを⼊れる⽅法はわからない...... ‣永続化‧画像化 • PKDrawing を Data に変換して保存 • UIImage を書き出して保存 2 5
  15. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 2 7
  16. UIImageView in UIScrollView ‣⼤きいコンテンツを部分的に表⽰するカメラ(View) • ContentView: ScrollView の subview •

    Zoomやスクロール、余⽩をカスタマイズできる 2 9 https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/UIScrollView_pg/Introduction/Introduction.html
  17. 既存紙⾯画像ページ: ⼀般的なImageViewer ‣UIScrollView の中に Image • ズーム‧スクロールができる • 画像は3段階の画質をサポート ‣カスタマイズ

    • ダブルタップでカスタムジェスチャー • 横書きモードの表⽰‧保存‧共有 • iPad SplitView 3 0 UIScrollView
  18. ImageViewer に描き込みを⾏いたい ‣ImageViewer に PKCanvasView ( UIScrollView ) を重ねる •

    重ねて同時に操作してみる? 3 1 UIScrollView PKCanvasView
  19. 2枚の同期が難しい ‣Zoom の調整 • ContentView 同⼠のサイズが違うと、 Zoom する⼤きさや位置が変わる • iPhone,

    iPad, SplitView でも対応しないといけない ‣PencilKit 側の描画が遅れる • 別の View 越しにイベントを伝えているため? • ⽬で⾒てアウトな品質に ‣何より管理が複雑 • 機能追加するたびに UIScrollView が増えたりしたら......⽅針を変える 3 3
  20. PKCanvasView = UIScrollView ‣UIScrollView として観察してみる • ContentView = 描き込み領域? •

    発⾒できれば • ContentView に Image を Insert できないか • 実現できれば ScrollView 1 枚化ができそう 3 4 https://developer.apple.com/documentation/pencilkit/drawing_with_pencilkit
  21. Debug Hierarchy で覗いてみる ‣PKCanvasView の subviews には • UIView (

    ContentView っぽい) • subviews に PKCanvasAttachmentView • PKTiledCanvasView (hidden) • PKTiledView • PKSelectionGestureView • _UIScrollViewScrollIndicator * 2 • スクロールインジケーター 3 5
  22. Debug Hierarchy で覗いてみる ‣PKCanvasView の subviews には • UIView (

    ContentView っぽい) • subviews に PKCanvasAttachmentView • PKTiledCanvasView (hidden) • PKTiledView • PKSelectionGestureView • _UIScrollViewScrollIndicator * 2 • スクロールインジケーター 3 6
  23. (参考までに) UIScrollView の場合は ‣構造が似ている • UIImageView ( ContentView ) •

    _UIScrollViewScrollIndicator * 2 • スクロールインジケーター 3 7
  24. Drawing 中 背景⾊になってしまう ‣PKCanvasView • PKCanvasAttachment の tintColor と連携? •

    backgroundColor = .clear にしてみると....... ‣UIImageView と 背景 UIView • ContentView に Insert • 必要あれば背景 View を追加で Insert 3 9
  25. 画像の上に描き込みができる! ‣ScrollやZoomも滑らか!ズレない! • 描き込み領域は • canvasView.contentSize を設定して調整可能 • ImageSize から必要な余⽩を計算し、contentSize

    にする ‣3種類の画質の画像と合成 • PKDrawing と画像を分けて保存 • 画像サイズに応じて倍率を計算し、Drawingと重ねる 4 0
  26. (再掲) 既存紙⾯画像ページ: ⼀般的なImageViewer ‣UIScrollView の中に Image • ズーム‧スクロールができる • 画像は3段階の画質をサポート

    ‣カスタマイズ • ダブルタップでカスタムジェスチャー • 横書きモードの表⽰‧保存‧共有 • iPad SplitView 4 1 UIScrollView
  27. PKCanvasView で UIScrollView + α (移植)! ‣PKCanvasView の中に Image •

    ズーム‧スクロールができる • 画像は3段階の画質をサポート ‣カスタマイズ (移植) • ダブルタップでカスタムジェスチャー • 横書きモードの表⽰‧保存‧共有 • iPad SplitView 4 2 PKCanvasView
  28. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 4 4
  29. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 4 5
  30. Canvas を Preview / Edit 切り替えする ‣メモアプリ • Preview は1本指でスクロール

    • Edit は、1本指で描画、2本でスクロール ‣描き込みだけ OFF にするには canvasView.isUserInteractionEnabled = false • 全ての Gesture が動かなくなる....... canvasView.drawingGestureRecognizer.isEnabled • Drawing に関する操作だけ Disabled にできる 4 7 Preview Edit ❌ ⭕
  31. 4 9 Draw ON Draw OFF Pencil で描ける! スクロール操作のみできる Edit

    Preview UIScrollView PKCanvasView の描き込みモードを切り替える
  32. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 5 2
  33. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 5 3
  34. 独⾃マーカー⽤の View も ContentView に⼊れる ‣PKCanvasView を UIScrollView として更に使う! •

    ContentView • CanvasBackgroundView • ImageView • PKCanvasAttachmentView • HighlightView • PKTiledCanvasView (hidden) • ...etc. 5 6
  35. 独⾃マーカー⽤の View も ContentView に⼊れる ‣PKCanvasView を UIScrollView として更に使う! •

    ContentView • CanvasBackgroundView • ImageView • PKCanvasAttachmentView • HighlightView • PKTiledCanvasView (hidden) • ...etc. 5 7 台紙となる背景 紙⾯画像 ⼿描き領域 独⾃マーカー
  36. Highlightの仕組み ‣始点と終点をキャプチャ • 間にある番号をすべて塗る 5 9 ① ② ③ ④

    ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ㉑ ㉒ ㉓ ㉔ ㉕ ㉖ ㉗ ㉘ ㉙ ㉚ ㉛ ㉜ ㉝ ㉞ ㉟ ㊱ ㊲ ㊳ ㊴ ㊵ ㊶ ㊷ ㊸ ㊹ ㊺ ㊻ ㊼ ㊽ ㊾ ㊿
  37. ポインタが ① から ⑯ へいったら ‣始点と終点をキャプチャ • 間にある番号をすべて塗る 6 0

    ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ㉑ ㉒ ㉓ ㉔ ㉕ ㉖ ㉗ ㉘ ㉙ ㉚ ㉛ ㉜ ㉝ ㉞ ㉟ ㊱ ㊲ ㊳ ㊴ ㊵ ㊶ ㊷ ㊸ ㊹ ㊺ ㊻ ㊼ ㊽ ㊾ ㊿
  38. ポインタが ㉖ から ㊳ へいったら ‣段落をまたいでもハイライトできる • データ側で順序を計算しておく 6 1

    ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ㉑ ㉒ ㉓ ㉔ ㉕ ㉖ ㉗ ㉘ ㉙ ㉚ ㉛ ㉜ ㉝ ㉞ ㉟ ㊱ ㊲ ㊳ ㊴ ㊵ ㊶ ㊷ ㊸ ㊹ ㊺ ㊻ ㊼ ㊽ ㊾ ㊿
  39. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 6 4
  40. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 6 5
  41. PKToolPicker ‣メモの Tool パレットを利⽤可能! • iPhone / iPad で機能差があることに注意 •

    iPhone は「Undo‧Redo」がついていない • 3本指で左から右にスワイプすると同じジェスチャーがあります 6 7
  42. ほとんどカスタムできない ‣⼀部の設定の ON / OFF のみ • 定規を表⽰するか • 指で描画を許可する設定を出すかどうか

    ‣CustomTool の追加‧削除 • できない、特定のTool だけ表⽰は無理 6 9 マーカーだけ独⾃実装のものにしたいが...
  43. ToolPicker を完全に⾃作すればやれる....... ‣標準 ToolPicker を外から操作する • PKCanvasView.tool / PKToolPicker.selectedTool を上書き

    ‣独⾃ツールも⼊ったツールパレットを作りたい場合 • 完全⾃作して UI を作る • 各種ボタンで Gesture をうまく切り替える • PKCanvasView の描き込み Gesture を ON/OFF • Custom Tool の Gesture を ON/OFF 7 0
  44. UISegmentControl 越しに切り替えてみる 7 1 let control = UISegmentedControl(frame: .null, actions:

    [ .init(title: "ϖϯ", handler: { _ in self.toolPicker.selectedTool = PKInkingTool(.pen) }), .init(title: "ϚʔΧʔ", handler: { _ in self.toolPicker.selectedTool = PKInkingTool(.marker) }), .init(title: "Ԗච", handler: { _ in self.toolPicker.selectedTool = PKInkingTool(.pencil) }), ])
  45. もっともっと拡張したい ‣PencilKit の Drawing 体験を拡張したい • 画像に対して描き込み • 保存して描き込みを無効化 •

    描き込みの表⽰‧⾮表⽰切り替え ‣独⾃実装したペンツールを統合したい • 本⽂ハイライトマーカー ‣ツールパレットのカスタマイズ • ToolPickerをカスタマイズしたい 7 3
  46. 拡張したPKCanvasViewをテストする ‣PKCanvasView の subviews の構造チェック • 想定と変わっていないことを確認 • Private Class

    なので Test のみで厳密にチェックする 7 5 let vc = ArticleCanvasViewController() vc.loadView() vc.viewDidLoad() // Private APIͳͷͰຊମίʔυʹೖΕͳ͍Α͏ʹ஫ҙ XCTAssertEqual(NSStringFromClass(type(of: vc.canvasView.subviews[0])), "UIView") XCTAssertEqual(NSStringFromClass(type(of: vc.canvasView.subviews[1])), "PKTiledCanvasView") XCTAssertEqual(NSStringFromClass(type(of: vc.canvasView.subviews[2])), "PKTiledView") XCTAssertEqual(NSStringFromClass( type(of: vc.canvasView.attachmentView)), "PKCanvasAttachmentView")
  47. 他にもカスタマイズした設定があれば Assert ‣ペンツールの設定 • PKDrawing / PKToolPicker は Equatable に準拠

    • AssertionTest が書きやすい • 初期選択ツールのチェック • ユーザー選択後はそちらが維持されるチェック 7 6
  48. ビジュアルリグレッションテスト ‣ 視覚の回帰テスト • View 単位でデグレを検知 • スクリーンショットの Di ff

    を⽤いた差分検知 など • uber/ios-snapshot-test-case や pointfreeco/swift-snapshot-testing が有名 7 8 A A' A'
  49. ただし CI は注意...... ‣PKCanvasView は Metal でレンダリングする • Machine 環境に依って、レンダリングされないことがあった

    • スペックを上げることで正しく動くことがあった • 直接的な原因はまだ特定できてません 8 0 https://developer.apple.com/jp/metal
  50. Pencil や指の特殊なショートカットを作りたい ‣UIGestureRecognizer で Interaction を区別可能 • allowedTouchType で許可する ‣例えばペンシルでなぞる動作を拾うなら

    • allowedTouchType • [UITouch.TouchType.stylus] • minimumPressDuration / allowableMovement • ⼩さい値に / 広い値に 8 3 https://developer.apple.com/documentation/uikit/uigesturerecognizer/ 1 6 2 4 223 -allowedtouchtypes
  51. Pencil 2 の Double-Tap (消しゴムと切り替え) ‣UIPencilInteraction • Delegate 関数でカスタマイズできる 8

    4 https://developer.apple.com/documentation/uikit/pencil_interactions/handling_double_taps_from_apple_pencil let pencilInteraction = UIPencilInteraction() pencilInteraction.delegate = self view.addInteraction(pencilInteraction) func pencilInteractionDidTap(_ interaction: UIPencilInteraction) { if UIPencilInteraction.preferredTapAction == .switchPrevious { leftRingControl.switchToPreviousTool() } }
  52. 画像に描き込む場合 Light に固定する (といい?) ‣画像に描きこむとダークモードは...... • Drawingが黒と⽩で反転してしまう • Light モードを維持したい

    • systemColor は Light / Dark で少し違ったりも 8 5 https://developer.apple.com/design/human-interface-guidelines/foundations/color
  53. Human Interface Guideline にも ‣コンテンツへの描き込みは⾊を固定しましょう • ⾊合いが変わってしまうため 8 6 https://developer.apple.com/design/human-interface-guidelines/inputs/apple-pencil-and-scribble

    Help people draw on top of existing content. By default, the colors on your PencilKit canvas dynamically adjust to dark mode, so people can create content in either mode and the results will look great in both. However, when people draw on top of existing content like a PDF or a photo, you want to prevent the dynamic adjustment of colors so that the markup remains sharp and visible . PencilKit Custom Drawing - HIG -
  54. 具体的にどうするか? ‣UIUserInterfaceStyle を上書きする • PKCanvasView • overrideUserInterfaceStyle: UI の⾒た⽬ •

    PKToolPicker • overrideUserInterfaceStyle: UI の⾒た⽬ • colorUserInterfaceStyle: Drawing の選択⾊ • 黒で描いた線が⽩になる 8 7 外側だけ Dark も可能
  55. まとめ ‣PencilKit を拡張する • PencilKit の構造を UIScrollView の観点から観察 • 画像や独⾃

    Touch View を内部の View と⼲渉しないように合成する • PencilKit の編集モードを⽤意する • Preview‧Edit‧Highlight • リッチな機能を活かしつつ、追加拡張に成功 • 快適なスクラップ体験の実現! 9 0