Apple Pencil対応の勘所を話します

6ca998e3a07596f141597da0d2983ecc?s=47 Shetomi
September 19, 2020

Apple Pencil対応の勘所を話します

iOSDC 2020にて発表したスライドです。
(2020/09/20 10:50〜 Track C)

### 詳細
https://fortee.jp/iosdc-japan-2020/proposal/348c2d74-7855-4d8b-8457-db9df25f9a7c

Apple pencil、活用できてますか?

Apple pencilは、2015年末にiPad Proとともに発売され、2018年末には第二世代が発売されました。長らくApple純正アプリ以外は対応に苦慮してきたと思われますが、WWDC2019でPencilKitが発表されたことにより、簡単にApple pencil対応が可能になりました。

ライブラリ自体は使いやすいものですが、具体的なノウハウについては情報が少なくて、実際につくってみると試行錯誤することになりました。そこで本トークで、開発ノウハウを共有したいと思います。

このトークでは、PencilKitを利用したノートアプリ(「Like a Paper」)を個人開発した経験から、Apple pencil対応の勘所を話します。具体的には、PencilKitでできること/できないこと、 `PKDrawing` から `UIImage` への変換、ダークモード対応の罠、 `PKToolPicker` のカスタマイズなどを扱います。

6ca998e3a07596f141597da0d2983ecc?s=128

Shetomi

September 19, 2020
Tweet

Transcript

  1. 4IFUPNJ5SBDL$ "QQMF1FODJMରԠͷ צॴΛ࿩͠·͢

  2. 4IFUPNJ ࣯ ͠ͱΈ  ʙۚ༥ܥγεςϜΤϯδχΞ ʙJ04ΤϯδχΞ Ԡԉ͍ͯ͠Δٿஂ͸౦๺ָఱΰʔϧσϯΠʔάϧε ˏTU2JJUB͸ͯͳOPUF ˏTJUXJUUFS(JUIVC

  3.  -JLF1BQFS͸ͲΜͳΞϓϦ͔  ͳ͔ͥͭͬͨ͘  1FODJM,JUͱ͸  1FODJM,JUͰͰ͖Δ͜ͱͰ͖ͳ͍͜ͱ  1,$BOWBT7JFXͱ1,%SBXJOH

     ࡾߦͰಋೖͰ͖Δ1,$BOWBT7JFX  1,%SBXJOHͷѻ͍ํ  1,5PPM1JDLFSͷσβΠϯ͸͋·ΓΧελϚΠζ͕ޮ͔ͳ͍  μʔΫϞʔυରԠͷ᠘  ແݶεΫϩʔϧ࢓༷  ʢ͓·͚ʣ"QQMF1FODJMରԠͷྺ࢙
  4. -JLF1BQFS͸ͲΜͳΞϓϦ͔

  5. ˡ"QQ4UPSFϦϯΫ͸ͪ͜Β͔Β

  6. w ʮࢴͷΑ͏ʹඳ͚Δʯ͕ίϯηϓτͷϊʔτΞϓϦ w "QQMF1FODJM͔ΒͷೖྗΛલఏͱ͍ͯ͠Δ w ॻ͍ͨϊʔτ͸ڞ༗ػೳͰଞΞϓϦͱͷ࿈ܞ͕Մೳ

  7. ʙ  %-

  8. None
  9. w μ΢ϯϩʔυͷׂ̕Ҏ্͕ӳޠݍ ɹʢ͋Γ͕͍͚ͨͲɺΈΜͳͲ͔ͬΒݟ͚ͭͯΜͩʁʣ w ೔ຊͩͱ%-͘Β͍͔͠ͳ͍ˠӳޠରԠ͸͠·͠ΐ͏ʂʂʂʂ

  10. ͳ͔ͥͭͬͨ͘

  11. None
  12. None
  13. ͋Εʁ ͳΜͰԶɺ J1BE1SP΋"QQMF1FODJM΋͋Μͷʹ ࢴʹखॻ͖ͯ͠Μͩʜʜʁ

  14. Կ͔Λࢥ͍ͭ͘ˠࢴʹॻ͘ ˢͱͯ΋ࣗવ

  15. Կ͔Λࢥ͍ͭ͘ˠJ1BEΛखʹͱΔ ˠ&WFSOPUFʢ͋Δ͍͸"QQMFͷϝϞΞϓϦʁʣΛ։͘ ˠϊʔτϒοΫΛબͿ ˠ৽ن࡞੒ΛબͿ ˠखॻ͖Ϟʔυʹ͢Δ ˠඳ͖࢝ΊΔ ˠʢλΠτϧΛܾΊΔɺͲͷϊʔτϒοΫʹೖΕΔ͔ʣ ˢͱͯ΋ΊΜͲ͍͘͞ʂʂʂʂʂʂʂʂʂʂʂʂʂ ʢͪͳΈʹݱࡏ͸&WFSOPUF͔Β/PUJPOʹ৐Γ͔͑ͯ·͢ʣ

  16. ϋʔυ΢ΣΞͷ໰୊Ͱ͸ͳ͘ɺ ιϑτ΢ΣΞͷ໰୊Ͱ͸ʁ

  17. ࣗ෼ͷཉ͍͠ΞϓϦ͸ ࣗ෼Ͱͭ͘Ζ͏ʂ

  18. Ͱ΋"QQMF1FODJMରԠͬͯ Ͳ͏ͨ͠Β͑͑Μ΍ʜʜ

  19. 1FODJM,JU

  20. 1FODJM,JUͱ͸

  21. w 88%$Ͱొ৔ͨ͠ϑϨʔϜϫʔΫ w "QQMF1FODJMPSࢦͰͷखॻ͖ೖྗΛ૝ఆͯͭ͘͠ΒΕ͍ͯΔ w ؆୯ʹ"QQMF७ਖ਼ͷϝϞΞϓϦ-JLFͳڍಈ͕࣮ݱͰ͖Δ w J04͔Β৽ػೳ͕௥Ճ͞ΕɺࠓҰ൪஫໨ΛूΊΔϥΠϒϥϦ ɹʢ৽ػೳʣɹ˞ຊηογϣϯͰ͸࣌ؒͷؔ܎Ͱѻ͑ͣ ɹ4DSJCCMFςΩετϑΟʔϧυʹखॻ͖ͰจࣈΛॻ͘ͱɺςΩετσʔλͱͯ͠ೝࣝ͞ΕΔ

    ɹ1,4USPLFखॻ͖ͷيಓ͕औಘͰ͖Δ
  22. ࠓҰ൪஫໨ΛूΊΔϥΠϒϥϦ

  23. 1FODJM,JUͰ Ͱ͖Δ͜ͱͰ͖ͳ͍͜ͱ

  24. Ͱ͖Δ͜ͱ w "QQMF७ਖ਼ͷϝϞΞϓϦͷ6*Λ࣮ݱ͢Δ w $BOWBT w 5PPM1JDLFSVOEPSFEP ϖϯফ͠ΰϜఆن ৭มߋ w

    1,%SBXJOHͱ6**NBHF%BUBܕͷ૬ޓม׵Λ͢Δ w ࢦͱ"QQMF1FODJMͷλονΛࣝผ͢Δ ɹʢͨͩ͠BMMPXT'JOHFS%SBXJOH͕J04͔Β%FQSFDBUFEʹͳ͍ͬͯΔʣ w %FMFHBUFϝιουܦ༝ͰESBXJOHͷมߋɺ 5PPM1JDLFSͷ࢖༻ͷTUBSUFOEΛݕ஌͢Δ
  25. Ͱ͖ͳ͍͜ͱ w 6*ͷΧελϚΠζ w 5PPM1JDLFS͸ Ͱݻఆ w "QQMF७ਖ਼ϝϞʹͳ͍ػೳͷ௥Ճ w ͨͱ͑͹ʮృΓ௵͕ͭ͘͠Γ͍ͨʯͱࢥͬͯ΋ɺ

    ɹ֦ுੑ͸ͳ͍ͷͰɺ׬શʹผݸͰͭ͘Δඞཁ͕͋Δ
  26. 1,$BOWBT7JFXͱ1,%SBXJOH

  27. 1,$BOWBT7JFX 1,%SBXJOH 1,%SBXJOH 1,%SBXJOH 7JFX .PEFM ϝΠϯ .drawingʹࢦఆ

  28. 1,$BOWBT7JFX 1,%SBXJOH 7JFX .PEFM πʔϧ 1,5PPM1JDLFS 1,%SBXJOH 1,%SBXJOH 1,*OLJOH5PPM PKToolPickerObserver

    (protocol) 1,&SBTFS5PPM 1,-BTTP5PPM 1,5PPM .selectedTool ˞1,$BOWBT7JFXͷAUPPMAʹ௚ࢦఆ΋Մ
  29. ࡾߦͰಋೖͰ͖Δ1,$BOWBT7JFX

  30. let canvas = PKCanvasView(frame: view.frame) view.addSubview(canvas) canvas.tool = PKInkingTool(.pen, color:

    .black, width: 30)
  31. ͨͬͨ3ߦͷίʔυͰ PencilKit Λಋೖͯ͠ Apple Pencil ରԠ(https://qiita.com/niwasawa/items/d8e239cd23666c750a2f)

  32. &BTZUPVTF

  33. 1,%SBXJOHͷѻ͍ํ

  34. 1,%SBXJOHͷجຊ w ಺෦ߏ଄͸Ṗ w 1,$BOWBT7JFXͷΠϯϓοτΞ΢τϓοτ͸શͯ1,%SBXJOHܕܦ༝ͱͳΔ w 6**NBHFʹϝιουҰൃͰม׵Մೳ ɹˠނʹଞΞϓϦͱͷڞ༗͸ҙ֎ͱָͩͬͨ w EBUB3FQSFTFOUBUJPO

    Ͱ%BUBܕʹҰൃͰม׵Մೳ w CPVOETΛ͍࣋ͬͯͯɺ$BOWBT7JFXͷαΠζͱ͸ผݸʹࣗ෼ͷαΠζΛ࣋ͭ
  35. %BUBͰͦͷ··อଘ͢Δͷ͸ѱख w 1,%SBXJOH͕ෳ਺ʹͳΔͱɺ%BUBͩͱϝλσʔλΛѻ͍੾Εͳ͍ ʢFYʣॱংɺඳ͍ͨ೔࣌ɺछྨͳͲ w ެࣜαϯϓϧͩͱҰݸט·͍ͤͯͨͷͰɺͦΕʹͳΒͬͨ struct DataModel: Codable {

    /// Names of the drawing assets to be used to initialize the data model the first time. static let defaultDrawingNames: [String] = ["Notes"] /// The width used for drawing canvases. static let canvasWidth: CGFloat = 768 /// The drawings that make up the current data model. var drawings: [PKDrawing] = [] var signature = PKDrawing() }
  36. J04ΞϓϦؒͳΒ1,%SBXJOH౉ͤΔʁ ʢະ֬ೝʣ w ݁ہڞ༗͢Δࡍ͸6**NBHFͰ࿈ܞ͍ͯ͠Δ w ΋͠ڞ༗ઌͷΞϓϦ΋1FODJM,JU࢖͍ͬͯͨΒɺ ɹ1,%SBXJOHΛ౉ͯ͠ɺڞ༗ઌͷΞϓϦͰฤूΛܧଓͰ͖Δʁ w 6*"DUJWJUZ7JFX$POUSPMMFSͷBDUJWJUZ*UFN͸"OZͳͷͰɺ ɹޓ͍ͷΞϓϦͷܕೝ͕ࣝ߹͍ͬͯΕ͹ड͚౉͠Ͱ͖ͦ͏ͳؾ͕͢Δ

    w ΋͠ࢼͨ͠ํ͕͍ͨΒ৘ใ͍ͩ͘͞ʂ
  37. 1,5PPM1JDLFSͷσβΠϯ͸ ͋·ΓΧελϚΠζ͕ޮ͔ͳ͍

  38. 1,5PPM1JDLFSͷઃఆྫ private func addPalette() { if let window = UIApplication.shared.windows.first,

    let toolPicker = PKToolPicker.shared(for: window) { self.toolPicker = toolPicker self.toolPicker.addObserver(canvasView) self.toolPicker.addObserver(self) canvasView.becomeFirstResponder() self.toolPicker.selectedTool = PKInkingTool(.pen, color: .black, width: 1) } }
  39. μʔΫϞʔυରԠͷ᠘

  40. None
  41. None
  42. w 1FODJM,JU͸J04͔Βొ৔ͨ͠ϑϨʔϜϫʔΫ w J04ͱ͍͑͹μʔΫϞʔυॳొ৔ w 1FODJM,JUࣗମ͕μʔΫϞʔυΛߟྀͯ͠ઃܭ͞Ε͍ͯΔ w ނʹಛʹࢦఆ͠ͳͯ͘΋ͳΜ্͔ख͍͜ͱ΍ͬͯ͘ΕΔ

  43. ͔͠͠໌ࣔతʹࢦఆ͍ͯ͠ͳ͍ͨΊɺ ։ൃऀͷҙਤ͠ͳ͍ڍಈʹͳΔʢͳͬͨʣ

  44. ʂʁ

  45. Կ͕ى͖ͨʁ w $PMMFDUJPO7JFXʹԼهͷΑ͏ʹϊʔτΛද͍ࣔͯͨ͠ let drawing = drawings[indexPath.row] let image =

    drawing.image(from: drawing.bounds, scale: 1.0) w දࣔޙʹϥΠτϞʔυμʔΫϞʔυͷ੾Γସ͕͑ى͜Δͱɺ ɹ৭൓స͕ߦ͑ͣɺݟ͑ͳ͘ͳͬͯ͠·͏
  46. override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle

    { reload() } } w ରࡦˣ w ͳ͓ڞ༗ػೳͰόά͕·ͩ࢒͍ͬͯΔʜʜ w 1FODJM,JUʹμʔΫϞʔυରԠ͓೚ͤͰ͖ΔΑ͏Ͱ ɹ࣮͸΍Βͳ͖Ό͍͚ͳ͍͜ͱ͕͋Δ
  47. ແݶεΫϩʔϧ࢓༷

  48. ஌ͬͯ·ͨ͠ʁ w "QQMFͷ७ਖ਼ϝϞ͸ॎํ޲ʹແݶεΫϩʔϧͰ͖Δ

  49. 1,$BOWBTΛແݶεΫϩʔϧՄʹ͢Δ canvasView.alwaysBounceVertical = true

  50. // MARK: Canvas View Delegate /// Delegate method: Note that

    the drawing has changed. func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) { hasModifiedDrawing = true updateContentSizeForDrawing() } /// Helper method to set a suitable content size for the canvas view. func updateContentSizeForDrawing() { // Update the content size to match the drawing. let drawing = canvasView.drawing let contentHeight: CGFloat // Adjust the content size to always be bigger than the drawing height. if !drawing.bounds.isNull { contentHeight = max(canvasView.bounds.height, (drawing.bounds.maxY + DrawingViewController.canvasOverscrollHeight) * canvasView.zoomScale) } else { contentHeight = canvasView.bounds.height } canvasView.contentSize = CGSize(width: DataModel.canvasWidth * canvasView.zoomScale, height: contentHeight) } ˞88%$ͷαϯϓϧΑΓ
  51. ʢ͓·͚ʣ "QQMF1FODJMରԠͷྺ࢙

  52. ˞1FODJM,JUొ৔Ҏલ ʙJ046*,JUͷ6*5PVDIͰ৭ʑؤு͍ͬͯͨʁ J04ʙ6*1FODJM*OUFSBDUJPO &WFSOPUFͷखॻ͖7JFXˠ ʢ4LJUDI-JLFͳ΍ͭʣ ͸͓ͦΒ͘6*,JUͰؤுͬͨʁ

  53. ·ͱΊ

  54. w 1FODJM,JUΛ࢖͏ͱ؆୯ʹ"QQMF1FODJMରԠ͕Ͱ͖Δ w ͨͩ͠"QQMF७ਖ਼ϝϞΞϓϦͷ࢓༷͔ΒΧελϚΠζੑ͸͞΄Ͳͳ͍ w μʔΫϞʔυʹউखʹରԠͯ͘͠ΕΔͷ͕ศར w ७ਖ਼ϝϞΞϓϦͷઃܭ΋Ԟ͕ਂ͍

  55. ࢀߟɿ ɾ*OUSPEVDJOH1FODJM,JU 88%$ IUUQTEFWFMPQFSBQQMFDPNWJEFPTQMBZXXED ɾ"QQMF1FODJMͰͷϝϞΛؾܰʹ͢ΔΞϓϦʮ-JLFB1BQFSʯΛݸਓ։ൃͨ͠ͷͰେมͩͬͨ͜ͱΛڞ༗ ͠·͢ʢ࣯ͷ2JJUBهࣄʣIUUQTRJJUBDPNTUJUFNTGDCBD Ͳ͏ͯ͠΋1FODJM,JU࢖͍ͨ͘ͳ͍Կ͔͕͋Δਓʢ಺༰ະ֬ೝʣɿ ɾ-FWFSBHJOH5PVDI*OQVUGPS%SBXJOH"QQTɹIUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOVJLJU UPVDIFT@QSFTTFT@BOE@HFTUVSFTMFWFSBHJOH@UPVDI@JOQVU@GPS@ESBXJOH@BQQT