Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード

2016-06-14

【watchOS 3】API Diffsから見る watchOS 3 の新機能 #WWDC2016

世間から「コレジャナイ」と見放されつつある印象のある Apple Watch / watchOS ですが、APIの面では改善して欲しかった点がちゃんと改善されて、個人的にはまた久々に触ってみたくなっています。


watch.png


以下、「watchOS 3.0 API Diffs」「What’s New in watchOS」等のプレリリースドキュメントを見て気になった新APIを列挙していきます。


WKCrownSequencer / WKCrownDelegate

ついに!やっと!デジタルクラウンの状態が取れるようになったようです。watchOS 2ではスクロール系のUI操作に使えるだけで、操作をフックしたり、値を取ったりできなかったですからね。。

WKCrownDelegate
WKCrownDelegate.crownDidBecomeIdle(_: WKCrownSequencer?)
Added WKCrownDelegate.crownDidRotate(_: WKCrownSequencer?, rotationalDelta: Double)
WKCrownSequencer
WKCrownSequencer.delegate
WKCrownSequencer.focus()
WKCrownSequencer.isIdle
WKCrownSequencer.resignFocus()
WKCrownSequencer.rotationsPerSecond

WKGestureRecognizer

Gesture Recognizer が watchOS でも使えるようになりました。

  • WKLongPressGestureRecognizer
  • WKPanGestureRecognizer
  • WKSwipeGestureRecognizer
  • WKTapGestureRecognizer.

いや、これ自体嬉しいのですが、そもそもの不満として、これまでユーザーがタップした座標を開発者が知るすべがなかったのですが、

WKGestureRecognizer.locationInObject() -> CGPoint

WKPanGestureRecognizer.translationInObject() -> CGPoint

が追加されて、タップした座標とかパンした移動先とかを取れるようになるってことがとにかく重要かと。watchOS 2 の頃はこれができないためにあきらめた機能が色々とあった気がします。今すぐに良い例が思い浮かばないのですが、たとえばタップした位置にCore Graphicsで何かを描画するとか。


バックグラウンドタスク

ついに来たという感じです。watchOS 2 のころ色々と辛かったのが、アプリが最大75秒でdeactivateされるという制約で、せっかく「ウェアラブル」、つまり「つけっぱなしにできる」のに、アプリは動かしっぱなしにはできない、という事態になってました。お客さんから「こういうことやりたい」と相談されたアイデアのうち、この制約のせいで実現できないものがなんと多かったことか。。


で、watchOS 3 では(いくつかのケースで)バックグラウンドで処理を実行できるようになりました。以下の種類があるそうです。

Background App Refresh

Use the `WKApplicationRefreshBackgroundTask` class to update your app’s state in the background. You often use a background app refresh task to drive other tasks. For example, you might use a background app refresh task to start an NSURLSession background transfer or to schedule a background snapshot refresh task.

Background Snapshot Refresh

Use the `WKSnapshotRefreshBackgroundTask` class to update your app’s user interface. You can push, pop, or present other interface controllers, and then update the content of the desired interface controller. The system automatically takes a snapshot of your user interface as soon as this task completes.

Background Watch Connectivity

When you receive background data from the paired iPhone, the system launches your app in the background, instantiates a `WKWatchConnectivityRefreshBackgroundTask` object, and passes the task object to your extension delegate’s handleBackgroundTasks: method.

Background URL Session

When a background transfer requires authorization, or when a background transfer completes (successfully or unsuccessfully), the system creates a background NSURLSession event, instantiates a `WKURLSessionRefreshBackgroundTask` object, and passes the task object to your extension delegate’s handleBackgroundTasks: method

To learn more about handling background tasks in general, see Background Refresh Tasks.


解説を端折ると語弊が生じる気がするので What's New の記述をそのまま引用しました。それぞれの解説だけざっと読むと結構色んなことができるんじゃ、という気がしてきますが、こういうのは実際に試してみないとわからないですね。


WatchConnectivityを用いた本体側(iOS)との通信は、watchOS 2では ウォッチ側はフォアグラウンドじゃないと通信できなかった のでこれは嬉しいアップデート。


Workoutのバックグラウンドサポート

Workoutもバックグランドで動作させられるようになったとのこと。対応方法は Info.plist の WKBackgroundModes キーに `workout-processing` を追加するだけ。ちなみに Xcode 8 の Capabilities からも On/Off できます。(NDA期間中につきスクショは自粛)


AVFoundation

watchOS でも AVFoudation が使えるように!


全部のクラスが移行されるわけではないのですが、オーディオファイル再生クラスである `AVAudioPlayerNode` とか、オーディオ処理諸々(各種エフェクトとか)を司る `AVAudioEngine` (詳しくはこちら)とか、音声合成の `AVSpeechSynthesizer` とか、色々入ってます。ちゃんと動くかは実際に試してみないとわかりませんが。*1


ジャイロスコープの値の取得

API的には2からあったんですが、`gyroAvailable` プロパティで必ず `false` が返ってきて実質的に非サポートでした。Apple Watch 2 とか新しいハードが出たら使えるようになるのかなと思ってましたが、wathcOS 3 で取得できるようになるようです。


ジェスチャー・姿勢認識をやろうと思ったら加速度センサだけじゃなくてジャイロの値も大抵必要になるのでこれも嬉しい更新。


あと、iOS 10 編 で書いた Significant Elevation関連と、CMPedometer関連のAPIが追加されています。


ちなみに最大100Hz周期で取れるそうです。

Device Motion, which fuses outputs from the accelerometer and gyroscope into orientation and motion dynamics updates. Apps can register to receive these updates at rates up to 100Hz.


Core Motion の歩数取得イベントはバックグランドでも取れるらしい。

On supported devices, apps can use CMPedometer APIs to register to receive live pedometer events while running in the foreground or the background.


WKInterfaceInlineMovie

動画再生インターフェース。watchOS 1 の頃、動画再生っぽいことをやるために、動画をアプリ側で動的にフレーム毎に分解して間引いてpngシーケンスアニメーションを生成(いわゆるパラパラ漫画)してウォッチ側で再生する、ということをやってましたが(たぶんみんなこうしてた)、今となっては普通に動画を再生できるようになったようです。


SceneKit / SpriteKit サポート

  • WKInterfaceSCNScene
  • WKInterfaceSKScene

詳細は後で読む。


HomeKit

HomeKitのカメラ・ドアベル(日本で言えば呼び鈴)アクセサリをサポートしたとか。それ用のサービス・キャラクタリスティックが追加されたらしい。


WKInterfaceHMCamera というインターフェースのクラスも追加されていて、これはHomeKitのIPカメラのデータにアクセスするためのインターフェースだそうです。*2


iOS 10 の新機能

量が多いので別記事としてまとめました。


*1:APIとしては存在しても実際はサポートされてない、ということもたまにあります。

*2:そもそも HomeKit 対応のネットワークカメラがまだ日本では販売されてないわけですが。。(されてたらすみません)

2015-09-23

【正式リリース】watchOS 2 の新機能のサンプルコード集『watchOS-2-Sampler』を公開しました

昨日、ついに watchOS 2 が正式リリースされました。開発者待望のネイティブ動作するウォッチアプリ対応バージョンです。このアップデートに伴い、WatchKit には多くの機能が追加され、ClockKit や WatchConnectivity 等の新しいフレームワークも登場しました。さらに、iOSでは従来から使えたフレームワーク群(記事末尾にまとめました)もウォッチ側で使えるようになったことから、これらもwatchOSにとっては新APIであるといえます。


新しいAPIはどう実装するのか、実際に何がどこまでできるのか、快適に動作するのか、といった具体的なところを実際にコードを書いて動かして理解すべく、watchOS 2 の新機能のサンプルコード寄せ集めアプリ


watchOS-2-Sampler


f:id:shu223:20150614131615j:image:w242


をつくりました。ソースコードは GitHub に置いてあります。


実はこのアプリは WWDC15 での発表があったその週につくって公開したのですが、NDA中のためスクショは貼れないし、悪名高い watchOS 2 ベータ版を入れてウォッチが文鎮化するのを恐れ、アップデートを避ける賢明な開発者の方々も多かった(ので本サンプルを試してくれる人も少なかった)ようなので、今回READMEを整備したり未完成のサンプルを除外する等の整理を行い、改めて正式リリースとした次第です。


Contents

今のところ18個のサンプルが入っています。随時追加していきます。機能追加のプルリク大歓迎です!


Animated Properties

`animateWithDuration:animations:` メソッドを利用して、アニメーションで拡大縮小やフェードイン/アウト、移動(実際には Alignment)を行うサンプル。



Table Animations

WKInterfaceTable のセル(row)をアニメーション付きで insert / remove するサンプル。`insertRowsAtIndexes:withRowType:`, `removeRowsAtIndexes:` メソッドを利用。



Picker Styles

WKInterfacePicker の全スタイル(List, Sequence, Stack)を試せるサンプル。



Taptic Engine

WKInterfaceDevice の `playHaptic` メソッドに指定できる全タイプ(WKHapticType)を試せるサンプル。



ちなみに WKHapticType は次のように定義されています。


enum WKHapticType : Int {
    
    case Notification
    case DirectionUp
    case DirectionDown
    case Success
    case Failure
    case Retry
    case Start
    case Stop
    case Click
}

Audio Rec & Play

`presentAudioRecordingControllerWithOutputURL:preset:maximumDuration:actionTitle:completion:` メソッドを利用して音声を録音する UI を表示し、録音したファイルを `presentMediaPlayerControllerWithURL:options:completion:` メソッドを利用して再生するサンプル。



参考記事:no title


Animation with Digital Crown

デジタルクラウンの回転に合わせ、WKInterfacePicker にセットしたアニメーションを表示するサンプル。`setCoordinatedAnimations` メソッドを利用。



Draw Paths

Core Graphics を用いてパスを描画するサンプル。



Gradations

Core Graphics を用いてグラデーションを描画するサンプル。



※Core Graphics を用いたパス描画やグラデーション描画についての詳細は、こちらのスライド・記事もご参照ください。



Heart Rate

HealthKit を用いて心拍数を取得・表示するサンプル。



kitasuke さんより pull request いただきました。

Accelerometer

Core Motion を用いて加速度センサの値を取得するサンプル。



Gyroscope

Core Motion を用いてジャイロスコープの値を取得するサンプル。



※現行 Apple Watch では CMMotionManager の `gyroAvailable` が必ず `false` を返してくるので、今のところ実質的に利用できません。(API的には利用可能であるためサンプルに残してあります)


Device Motion

Core Motion を用いてデバイスのモーションデータを取得するサンプル。



※現行 Apple Watch では CMMotionManager の `deviceMotionAvailable` が必ず `false` を返してくるので、今のところ実質的に利用できません。(API的には利用可能であるためサンプルに残してあります)


Pedometer

CMPedometer を用いて歩数や距離、階の昇り降り情報を取得するサンプル。



Alert

`presentAlertControllerWithTitle:message:preferredStyle:actions:` メソッドで指定できる全アラートスタイル(WKAlertControllerStyle)を試せるサンプル。



ちなみに WKAlertControllerStyle は次のように定義されています。


enum WKAlertControllerStyle : Int {
    
    case Alert
    case SideBySideButtonsAlert
    case ActionSheet
}

Interactive Messaging

iPhone と Apple Watch で相互にメッセージをやりとりするサンプル。Watch Connectivity フレームワークを利用。



Audio File Player

WKAudioFilePlayer を利用してオーディオファイルを再生するサンプル。



※要Bluetoothヘッドセット


Open System URL

WKExtension の `openSystemURL:` メソッドを利用して、電話およびSMSアプリを開くサンプル。



Network Access

NSURLSession を用いてネットワーク経由で画像データを取得するサンプル。KAMEDAkyosuke さんより pull request いただきました。



おわりに

iOS 9 のサンプル集はこちら!





おまけ:watchOS 2 で使えるようになった従来フレームワークのリスト

  • MapKit
  • UIKit
  • WatchKit
  • CoreGraphics
  • ImageIO
  • CFNetwork
  • Contacts
  • CoreData
  • CoreFoundation
  • CoreLocation
  • CoreMotion
  • EventKit
  • Foundation
  • HealthKit
  • HomeKit
  • MobileCoreServices
  • PassKit
  • SystemConfiguration
  • Core OS Layer
  • Security

※このへんからウォッチでの挙動が気になるものを随時追加していく予定


2015-08-15

GitHub経由で海外から仕事が来た話

はじめて海外から(フリーランスとして)仕事をいただく、という貴重な経験ができたので、その経緯などを書いてみたいと思います。


きっかけ

7月末のある日、知らないメールアドレスから英語のメールが来ました。内容を一部だけ抜粋すると、

We are looking for someone to develop a very simple apple watch app and a companion apple phone app.


というわけで、Apple Watch アプリをつくって欲しい、とのこと。内容を読むと加速度センサとジャイロを使いたいそうで、必然的に watchOS 2 案件になりそうです。


メールには明記されてませんでしたが、GitHub で公開している watchOS-2-Sampler を見て連絡くれたのかなと。(※もちろん面識はなく、共通の知り合いもいないので、これ以外にわざわざ海外にいる僕に依頼してくる理由が考えづらい)


f:id:shu223:20150815125651j:image:w500



会社名も書かれていたのでググってみると、イスラエルの会社のようです。メンバーもLinkedInのプロフィールを見ると某世界的大企業の出身で、学位も持ってて発表した論文とかも載ってるので、怪しい感じではない、というかむしろすごいレベル高そう。


実はちょうど(フリーランスとしての今後を考えると)海外からの仕事もやりたいと思っていた & watchOS 2 を案件でやりたかったのもあって、前のめりに食いつきました。

I'm interesting for the work to build an watchOS 2 app.

(2015.8.16追記:はてブコメントで「そこは "I'm interested in" でしょ」ってツッコミを何件かいただきました。。恥ずかしいですがこんな英語でも全然やりとりできたという事実にも価値があると思うのでそのままにしておきます)


受注するまでのやりとり

工数や金額の提示

相手からきた最初のメールに(簡単に)こういうものをつくってほしい、ということが書いてあったので、僕も最初の返信から「こういう内容で、日単価○ドルで、○日でやります」と明記しました。

My freelance fee is $xxx / day. I can develop the watchOS 2 app with following features in 2 days:

  • ・・・・(機能1)
  • ・・・・(機能2)

単価は、普段日本でやってるのと同等の価格を提示しました。イスラエルの相場とかよくわからないので。それと安く済ませるためのオフショア、というよりは watchOS 2 実装がちゃんとできそうな人を海外からわざわざ探してきた、という位置付けなので、安売りする必要もないかなと。(とはいえシリコンバレー相場からすると安い)


また工数の見積もりは、だいたい1日もかからずできそうとは思ったのですが、watchOS 2 はまだベータで不安定なのでそのあたりで色々ハマるかも、というのと、

  • 加速度センサの値をインターバル x [ms] で取得し、それを iPhone 側に n 秒ごとに送る
  • データ(加速度センサ値)のロストは NG

という要件があり、取得インターバルがかなりギリギリ(短い)で、こういうのは実際にやってみるとウォッチ側の処理性能やBLEの通信速度的な部分やでどうのこうのありそうだなーというのも加味して2人日としました。


要件確認

あとちょっと要件的に気になる部分について確認しました。ジャイロの値は今のところフレームワーク的には用意されてるけど、実機でやってみると取れないですよ、とか、前述した加速度センサ値の取得インターバルや、Watch <-> iPhone 間の通信速度に関する懸念とか。

However we can't get gyroscope data from Apple Watch for now. You can try with my OSS (https://github.com/shu223/watchOS-2-Sampler). In addition, ・・・(他の要件についての懸念点)

このへんはいち開発者ではどうにもできない場合もあるので。。


以上が僕からの最初の返信メール。


条件の調整

で、それについての先方の返信があり、会社の詳細・ビジョン・プロダクトの説明とか、もっと具体的な要件とかが書いてありました。その具体的な要件が、最初のメールよりも増えてたので、「それなら4人日でやります」という旨の回答をしました。


そしたら、「なぜ2倍になったんだ?どの要件を減らせばどれぐらい減るの?」という質問が。


どれがどう、というよりベータ版での開発に携わる際の直観的なものしか根拠としてないので、その旨を正直に伝えました。

Both 2 days and 4 days, they depends on my rough feeling. It may takes more or less.

(2日にしろ4日にしろ、なんとなくの感覚なので、もっと伸びるかもしれないし、縮まるかもしれない。・・・と言ってるつもり)


さらに、次のように提案しました。

  • 3日で終わったら、3日分だけ請求します
  • 3日で終わらなかったら、その時点での状況と、あと何がどれぐらいかかりそうか、を報告するので、引き続き作業を進めるか、やめるかを選択してください
    • それ以上進めないことを選択した場合、その時点までの成果を受け取るかどうかを選択してください
      • 成果を受け取ることを選択した場合、その3日分を請求します
      • 成果を受け取らないことを選択した場合、一切請求しません

こちらとしては「やってみないとわからない」ことがほとんどなので、こういうやりとりで平行線をたどるよりは早く手を動かしたいし、最悪お金にならなくても watchOS 2 の知見は得られる(そしてブログとかに書ける)ので数日ぐらいならアリかなと。


だいぶ変則的ですが、先方の返答は、

We would like to hire you to develop these apps (iphone and apple watch).

The conditions you stated sound fair.

フェアだね、よし、任せよう


というわけで無事受注することができました(実際は技術面でもっと細かいやりとりがあったのですが、長くなるので省略)。


ここまでメールは5往復。


開発

その3日間について、いつといつといつでやります、ということは明示してましたが、当日に先方から「ちゃんとやってるか〜?」とか聞いてきたり、スカイプで何か話したりとかはなく、完全に任せてくれてました。ただ向こうも心配はあると思うので、「そろそろ作業始めます」とか、1日目の終わりにもちょっとメール送ったりとかはしました。


本当のところは、3日分の作業を1日で終えて、「もう終わったよ、請求は1日分でいいよ」「まじか!すげーな。次も頼むよ」みたいな展開を狙ってたのですが、watchOS 2 beta のあれやこれやにハマって(watchOS 2 の技術的なことはまた別記事で書こうと思います)、結局2日かかって完了。


作業報告

実装的には要件は満たしたのですが、どう考えても watchOS 2 beta 自体のバグとしか思えない挙動 *1 があり、それにより実機ではある要件を満たせないという部分がありました。そこをどう説明し、納得してもらうか、はけっこう悩みました。


前述したような条件なので、Xcodeプロジェクトをいきなり送ってしまうと相手がその成果に満足しない場合に何ももらえなくなってしまうので、まず、実装自体はできていることを証明するため、シミュレータではきちんと動いている動画を撮影し(シミュレータでは加速度センサ使えないので、そこはダミーで値を生成するモードを実装しておいた)、その上で、実機ではどういう問題が起きているのか、それについてどんな検証を行い、なぜAppleの問題(watchOS の問題)と考えるのか、どんな対策が考えられるかといったことを整理して書きました。


代替実装も行っておき、その動画も撮影して送りました。


プラス、僕としては初めてのお客さんなので、早く最初の入金までこぎつけたく、

  • まだ不足がある、追加要望がある等あれば、8月○日にもう1日作業できます
  • これで十分であれば、Xcodeプロジェクト一式を送り、2日分だけ請求します

と書きました。


そんなこんなで作業報告メールはレポートはこの記事よりも長くなり、2〜3時間はかかったわけですが、先方は「言ってることは筋が通ってる」と実機では要件を満たせていない件について理解してくれ、代替実装案についても同意してくれました。


で、まずこの2日分は入金した上で、プラス今後も作業をお願いしたい、と言ってくれました。


その後、あれこれ送金方法の調整があったのち、すみやかに送金していただけました。


所感

一連の流れから汲んでいただけると思いますが、すごく理解があり、信頼できる良い方たちでした。分野的にも watchOS 2 は興味のあるところなので、ぜひ今後も一緒にやっていきたいと思っています。


ただ、数人日の小さい案件なので、比較的ざっくりした感じで進められたものの、もっと大きい案件だとこうもいかないかもしれません。そういった場合に、

この記事に書いたみたいに、見積もり型ではなく、(ある程度の信頼関係を築いた上で)先方のオフィスまで行って、2週間とか一緒に作業して帰る、みたいなやり方ができたら理想的だなぁと思っているのですが、それも色々な面で難しいところはありそうです。


とにかく今後も海外からもお仕事をいただけるよう、GitHub活動やStackOverflow活動をがんばろう、と思った次第です。


制作実績を3ヶ月ごとにまとめています:[制作実績]記事一覧 - Over&Out その後



2015-08-03

iOS 9、watchOS 2 技術記事のまとめ

今年はWWDC後の勉強会も多く開催され、正式リリースを前にして iOS 9、watchOS 2 の技術情報が既に多く出てきています。あとでキャッチアップしよう、と思ってたらいつの間にかかなりの記事がたまってきたので、ここらへんでいったん整理しておこうと思います。


iOS 9

f:id:shu223:20150804031922j:image:w600


Search API

Search API は既存アプリへのメリットもはっきりしているためか、注目度が高く、発表スライドや詳細な解説記事を多く見かける印象があります。




Universal Links

App Thinning
  • no title
    • 「App Thinning」「App Slicing」「On-Demand Resources」「Bitcode」など、どれが何だか区別がつかなくなる各用語について解説してくれています。

SFSafariViewController

UIWebView や WKWebView と同様に、アプリ内ブラウザとして使えるビューコントローラ。


コンテンツブロッカー(Content Blocker Extension)


UIStackView


  • no title
    • NSLayoutAnchor, UILayoutGuide の話もあり。


LLDB
  • no title
    • LLDBマニアな @dealforest 先生によるLLDB新機能の解説。


Core Location
  • no title
  • no title
    • Core Location は、「このAPIがあればあの機能が実現できる!」みたいな新APIはないのですが、`requestLocation` という一度きりの位置情報取得をするためのAPIが追加されたりとか、バックグラウンドで位置情報取得をするために設定が必要なプロパティが増えたりしたようです。
    • Apple Watch での挙動についても解説されています。


MapKit
  • no title
    • ピンアノテーションに任意の色をセットできるようになったりとか、コールアウトの詳細部分に任意のUIViewサブクラスをセットできるようになったりとか、見た目のカスタマイズ性が向上したようです。
    • Transit 機能の開放、Flyover のタイプ追加等、結構うれしい機能追加があります。

Audio Unit Extension


Metal
  • WWDC 2015 METAL
    • あまり見かけない、貴重な Metal 情報。WWDC 2015 の4つの Metal 関連セッションの内容をかいつまんで紹介してくれています。


Core Image


ReplayKit

ReplayKit は、画面を録画するフレームワーク。ゲーム実況にしろ、ユーザビリティテスト用途にしろ、それをメインプロダクトとしてがっつり取り組んでいる企業がいくつかある分野なので、あまり個人的には直接は利用しなそうと思っていたのですが、下記スライドに載っているサンプルを見ると、導入が簡単そうなので、ちょっと試してみようかと。


  • no title
    • 概要や実装サンプルをかいつまんで解説してくれています


その他
  • no title
    • iOS 9 ではプライバシーへの配慮により、Info.plist に LSApplicationQueriesSchemes キーに対して追加してあるカスタムURLスキームについてのみ、 `canOpenURL:` で正しく結果を返してくれるようになった
    • (Info.plist に追加してないと、対象アプリがインストールされていて遷移可能でも、canOpenURL で `NO` が返ってくる)

  • no title
    • プッシュ通知に関する諸々のまとめ記事。iOS 9 におけるアップデートについても記載されています




  • no title
    • NSMutableDictionary に nil を突っ込むと、今までだと例外になっていたのが、要素の削除、という扱いになったとのこと



  • no title
    • NSPersonNameComponents とか


watchOS 2

f:id:shu223:20150804031921j:image:w600


Watch Connectivity



Complication
  • 簡単!Complication!!
    • Complication の実装手順について、用語説明からダウンロードサンプルまであって、すごく丁寧に解説してくれているスライド


  • no title
    • こちらも具体的なコードが示されていて大変ありがたい解説記事。

HealthKit
  • no title
    • HKWorkoutSession クラスを用いて、ワークアウト機能を利用したアプリを実装するための情報がひと通り書いてある大変ありがたいスライド


Core Graphics


その他

watchOS 1.0のアーキテクチャはいったい何のために用意されたのだろうか。ネイティブ化されるまでの半年間はいったい何のために必要だったのだろうか。Apple Watchに搭載されている一部のApple製のアプリは最初からネイティブで動作しているはずだが、はじめからその方法を解放できなかったのはなぜなのか。

    • という、開発者みんなが思った疑問についての、Bitcode やプロセッサを絡めての考察。




2015-07-14

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

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



概要

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


f:id:shu223:20150214162301p:image:w600


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


f:id:shu223:20150714210052p:image:w425


で、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を動的に描画するとか。



2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2017 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2018 | 02 |