Apple Watch対応しました!
以前記事にしましたが、iPhone向けのアプリとして、卓球の得点計算アプリ「卓球カウンター」をApp Storeにリリースしました。
卓球しながら得点をアプリでカウントするのは、2人しかメンツがいない場合などでは、とても面倒なものです。Apple Watchなら、得失点のたびにボタンを押すのもそこまで苦ではないかなと思い対応してみました。
卓球カウンターアプリ Apple Watch動作デモ
動画では、Apple Watchからの操作でiPhoneの画面が更新されていませんが、通信遅延のせいでした。もう少し待っていれば最新の状態に更新されます。スペックの問題か、Apple WatchからiPhoneへの通信が特に遅いです。逆は動画にある通り、比較的スムーズに反映されます。
実装について
iPhoneとApple Watch間のやりとりは、WCSessionを作成し、データの通信をしてお互いの情報を更新していきます。今回はすでに実装してあったiPhone側にデータ本体を保持し、Apple Watchは各イベントで最新の情報をiPhoneから取得するようにしています。Apple Watch側のイベント、例えば得点加算ボタンを押した時は、iPhoneにどちらのボタンを押したかという情報だけを送信し、iPhoneの得点ボタンを押した時に呼ばれる処理と同じ処理を呼び出します。iPhoneの得点ボタンでは、得点加算後に自身のViewとApple Watchに現在の最新情報を送信する処理が含まれているため、Apple Watch側に加算後の値が表示されるようになります。
iPhoneアプリはMVP(のような)実装の仕方をしたため、メインのPresenterに通信部分を実装しました。(PresenterはModel更新メソッドを読んで情報を更新した後、Viewの更新メソッド呼び出し更新する役割を持ちます)。Apple Watchからのイベントを受けた後にViewを更新することができる必要があります。
セッションを利用するには、WCSessionDelegateを継承する必要があります。
iPhoneの送受信するクラスと、Apple Watchの送受信クラスの両方で継承します。
class CounterViewControllerPresenter : NSObject, WCSessionDelegate, CounterViewControllerPresenterProtocol {
private let counterView : CounterViewControllerProtocol
private let speechModel : SpeechModelProtocol
private let counterModel : CounterModelProtocol
let wcSession = WCSession.default
送信部分
sendMessageでデータを送信します。以下はiPhoneがApple Watchに対して、表示に必要な3項目を送信している箇所です。(もう少し通信内容はしっかり設計したほうがよかった気がしますが・・)
func sendMessage(counterObj: CounterObj) {
if WCSession.isSupported() {
initWCSession()
let message = [ "kind" : 3, "counterL" : counterObj.counterL, "counterR" : counterObj.counterR, "isServe" : counterObj.isServe] as [String : Any]
wcSession.sendMessage(message, replyHandler: { replyDict in
}, errorHandler: { error in
print(error)
})
}
}
受信部分
sessionにデータが渡ってきたら、以下の関数が呼ばれます。通信のプロトコルを自身で定義して実装していきます。こちらはApple Watch側の受信部分です。先ほどの送信部分のコードで送った値をこちらで受け取り、reloadDataで自身のViewを更新しています。
func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
// iPhoneからのデータを受け取る
guard let kind = message["kind"] as? Int else {
return
}
switch kind {
case 3:
let counterL : Int? = message["counterL"] as? Int
let counterR : Int? = message["counterR"] as? Int
let isLeft : Bool? = message["isServe"] as? Bool
reloadData(counterL: counterL!, counterR: counterR!, isServe: isLeft!)
default:
print("default")
}
}
また、以下はiPhone側の受信部分です。Apple Watchから加算イベントです(dataの中のbool値が左右どちらの加算かを意味しています)。incrementCounterL/RはiPhone側の加算ボタンを押した場合も呼ばれる共通処理です。同じイベントを呼ぶので共通化されていたほうがメンテナンスしやすいですね。fromWatchという引数は加算後のiPhone側の音声再生や勝利ダイアログ表示などをしないように制御するためにフラグです。
case 2:
let isLeft : Bool? = message["data"] as? Bool
if (isLeft!) {
incrementCounterL(fromWatch: true)
} else {
incrementCounterR(fromWatch: true)
}
result["success"] = true
return
もう少し先へ
横画面にも対応し、一通りやりたいことはやってしまったので、このアプリの開発は一旦収束となると思います。ただちょっと加算処理をもっと別の先進的なやり方でできないかを検討中で、準備が出来次第実装してみたいと思っています。半分ネタですが、自動加算方面の話です。進捗があったらこちらに報告していきたいと思います。。。
(旧ブログから移行)