その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

watchOS 2 の Core Graphics は何ができて何ができないのか #potatotips

本日開催された、「【第19回】potatotips」にて、watchOS 2 における Core Graphics について発表をさせていただきました。


概要

watchOS 1 では Core Graphics が使えなかったので、次のような UI を実現するにあたって、



Appleのサンプル「Lister」では、360個の連番pngを使用するという相当な力技でやっていたことは記憶に新しいところです。



で、watchOS 2 でついに Core Graphics が使えるようになったわけですが、発表されて1ヶ月経過した今も、(自分の探し方が悪いのか、)10本ぐらいあるAppleのWWDC動画にもサンプルにも、GitHub や StackOverflow にも未だに具体的なコードは見当たりませんでした


単にまだ誰も書いてないというだけかもしれないし、何か重要なクラスが watchOS 上で使えないとかで結局使えないのかもしれない、実際に試してみないとわからない・・・ということで何ができて何ができないのか、検証コードを書いて確認してみました。

ポイント

詳細はスライドを見ていただくとして、ここではポイントだけ。

  • watchOS では UIGraphicsGetCurrentContext() でグラフィックス コンテキストを取得できない
  • ビットマップコンテキストを作成し、そこに描画 → ビットマップコンテキストから UIImage を生成して WKInterfaceImage や WKInterfaceGroup に表示すればOK


(2015.7.21 修正)@hoppenichu 様よりご指摘&Pull Request をいただき、上記に間違いがあることがわかりました。(打ち消し線を入れた箇所)


UIGraphicsBeginImageContext() すれば、 UIGraphicsGetCurrentContext() でグラフィックス コンテキストを取得できます


また僕の当初のサンプルでは UIGraphicsBeginImageContext() したあとさらに CGBitmapContextCreate() でビットマップコンテキストを生成していましたが、Beginの時点で既に生成されているので、このステップは必要ないということになります。

// Create a graphics context
let size = CGSizeMake(100, 100)
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()

// Setup for the path appearance
CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextSetLineWidth(context, 4.0)

// Draw lines
CGContextBeginPath (context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 100, 100);
CGContextMoveToPoint(context, 0, 100);
CGContextAddLineToPoint(context, 100, 0);
CGContextStrokePath(context);

// Convert to UIImage
let cgimage = CGBitmapContextCreateImage(context);
let uiimage = UIImage(CGImage: cgimage!)

// End the graphics context
UIGraphicsEndImageContext()

image.setImage(uiimage)


こちらの commit を見ると、間違いをどう修正していただいたかわかります。(修正ここまで)

うまくいったこと
  • CGPath を用いたパス描画
  • UIBezierPath を用いたパス描画
  • SVG ファイルからのパス描画
  • グラデーション描画

(描画の話なのに、NDAにつきまだスクショ貼れないのが残念。。)

watchOS の Core Graphics でできないこと

グラフィックスコンテキストの問題が解決できたことで、結構いろいろなことができることがわかったわけですが、基本的に CALayer や UIView の API を使う必要がある処理は実現できません。スライドでは例として下記3つを挙げています。

  • スクリーンキャプチャ
    • UIView または CALayer の内容をコンテキストに描画する必要がある
  • キーパスアニメーション
    • AddAnimation: しようにも CALayer オブジェクトにアクセスできない
  • 手書き
    • タッチイベントを取るAPIがまだ開放されていない

キーパスアニメーションができないということは、冒頭に挙げたような Circular Progress 的な UI の実現も結局難しい、ということになりますね。。(アニメーションの各フレームを UIImage として生成することもできますが、非現実的な方法かと)

おわりに

最初 UIGraphicsGetCurrentContext() でグラフィックスコンテキストが取得できず、ググってもあまり情報が見当たらなかったときは「もしかして何もできないのか・・・!?」と思いましたが、上述のようにそこは解決して、パス描画やグラデーション描画などなど、結構いろんなことができました。


ただ最後に書いたように、タッチイベントが取れない、キーパスアニメーションが使えない、という大きな制約もまだあるので、「Apple製アプリではできてサードパーティにはできない」ことというのは色々とありそうです。手書きの筆跡を共有する 「Digital Touch」的なこととか、Circular Progress的なUIを動的に描画するとか。