※こちらの記事はPart2です。
Part1はこちら↓
https://qiita.com/nextvision-sugakir/private/d2c9222160284639a156
はじめまして、IT企業に入社して1年目のSEです。
Part1では開発までの道のり、DIYするアプリの概要までをご紹介しました。(Step1~Step3)
Part2では、DIYの実施から完成品の試用までをまとめました。
【Step4】いざ実装
まず仕様をおさらいすると、iPhoneからAppleWatchにメッセージを送信する。AppleWatchはメインのアプリ画面を更新し、画面の上部に新規のメッセージを表示する。
今回作る機能は以下の通りです。
- ボタン、ラベルの実装(AppleWatch側アプリ)
- メッセージの入力機能(iPhone側アプリ)
- データの送受信の機能(両者の疎通確認)
となります。それぞれ書籍等を参考に実装してみます。
まずはiOSのアプリからAppleWatchのアプリに接続したいと思います。既にOS側が提供してくれているWatchConnectivityライブラリをViewControllerにインポートするだけで接続が確立します。
//Connect,AppleWatch
import WatchConnectivity
//Add WCSessionDelegate
class ViewController:UIViewController,WCSessionDelegate{
func session(_ session:WCSession, activationDidCompleteWith activatationState: WCSessionActivationState, error:Error?){
}
func sessionDidBecomeInactive(_ session:WCSession){
}
func sessionDidDeactivate(_ session:WCSession){
}
ボタン、ラベルの実装
XcodeにおけるUIの実装はとても簡単です。iPhoneとAppleWatchに共通しているのですが、配置したいコントロールを選択してドラッグアンドドロップするだけです。あとはプロパティでちょちょいと細かい設定をするだけです。
メッセージの入力機能
メッセージはiPhone側のテキストボックスを用いて送信します。先ほどのUIの実装の要領で、テキストボックスをiPhone側に配置し、ViewControllerでそのテキストボックスにデータの送受信処理を記述します。
データの送受信の機能
iPhoneからメッセージを送るメソッドは以下のように設定しました。ボタンを押下、すなわちメッセージを送信後に、次の入力に向けてテキストボックスを初期化しています。
//Describe send message
@IBOutlet weak var messageBox: UITextField!
@IBAction func sendButton(_ sender: Any) {
//Set message
if (messageBox.text == ""){
return
}
let message = ["send" :messageBox.text]
//Send Apple Watch
WCSession.default.sendMessage(message as [String : Any], replyHandler: {reply in print(reply)}, errorHandler: {error in print(error.localizedDescription)})
messageBox.text = ""
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Start to Session WatchConnectivity
if (WCSession.isSupported()) {
let session = WCSession.default
session.delegate = self
session.activate()
}
messageBox.text = ""
}
受信側のAppleWatchはInterfaceControllerに以下のように設定しました。
//Receive data
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
addCount += 1
let index = 0
tableDataList.insert(message["send"] as! String, at: 0)
table.insertRows(at: [index], withRowType: "Row")
table.setNumberOfRows(tableDataList.count, withRowType: "Row")
tableDataList.enumerated().forEach{index, value in
let row = table.rowController(at: index) as! TableRowController
row.configure(.init(index: index, text: value))
row.delegate = self
}
replyHandler(["watch" : "OK"])
}
【Step5】DIY中に気づいた最後の壁
今回のDIYはiPhoneで送信したメッセージをAppleWatchは通知で受け取ろうとしていました。しかし、DIYを始めてからAppleWatchのとある仕様に気づきました。
それは、AppleWatchの通知(Notification)は、iPhoneがスリープ状態の時のみ通知されるというものでした。
私は根本的なことを見落としていたのです。そもそもAppleWatchはあくまでもiPhoneの拡張機能であり、iPhoneがスリープ状態の時、すなわち携帯を見ていないときに知らせる、という仕様は至極当然のことでした。
しかし今回のDIYでは、ビジネスシーンでの利用を考慮して、iPhoneとAppleWatchの使用者を分ける(以下の図を参照)という発想で進めたかったので、ここで大きな課題となったのです。
【Step6】気づいた問題に対する対策
私はここでも諦めませんでした。今回の表題のアプリは、オフラインで使える、といった仕様です。オフラインで利用するためには、通知の実装を避けなければなりません。まずは今回のDIYで通知を実装出来ないとなった際のデメリットをあげてみました。
- 即時に気づけない
ぱっと思いつくのは、意外にもこれだけでした。とはいっても即時に気づけないのはとても大きな課題です。次に私はこの課題の改善策を考えました。即時に気づけないと具体的に何が困るか、またそれに対する対策をあげていきます。
1. 緊急性の高いメッセージにすぐに対応できない
2. アプリ画面を開いて、常に目視で確認しなければならない
これらの問題を改善するために、以下のような機能を実装しました。メイン画面に新しいメッセージが表示されるタイミングで、バイブレーションを起動します。
// vibration
WKInterfaceDevice.current().play(WKHapticType.notification)
わざわざ通知画面を表示しなくとも、バイブレーションによって気づくことが出来るので、これらの課題はクリアです。
3. メッセージを見落としたまま次のメッセージが送られてきた際に、確認漏れが発生する
最後に3つ目の課題については、ログの機能によって対策しました。
extension InterfaceController: TableRowControllerDeligate {
func tableRowController(_ tableRowController: TableRowController, didTapDeleteButtonAt index: Int){
tableDataList.remove(at: index)
table.removeRows(at: [index])
tableDataList.enumerated().forEach{ index, value in
let row = table.rowController(at: index) as! TableRowController
row.configure(.init(index: index, text: value))
row.delegate = self
}
}
}
protocol TableRowControllerDeligate: AnyObject {
func tableRowController(_ tableRowController: TableRowController, didTapDeleteButtonAt index: Int)
}
final class TableRowController: NSObject {
struct Config {
let index: Int
let text: String
}
weak var delegate: TableRowControllerDeligate?
private var index: Int = 0
@IBOutlet weak var label: WKInterfaceLabel!
func configure(_ config: Config){
label.setText(config.text)
index = config.index
}
@IBAction func buttonAction() {
delegate?.tableRowController(self, didTapDeleteButtonAt: index)
}
}
次々とメッセージが送られてくる場合、逆に通知画面では確認しきれないこともありますし、今回の目的は「携帯を開かなくてもよい」というものなので、通知では把握しきれず、過去のメッセージを見るためにわざわざアプリを開くのは、AppleWatchのメリットを活かせていません。ならば最初からログとしてメイン画面の上部に新しいメッセージを追加することで常に最新の3件は確認することが可能です。(DigitalCrownを回すことでさらに確認できる)
【Step7】デザインを整える
まず、DIYにより完成したアプリがこちら
うーん、iPhoneの画面をもっとアプリっぽくしたい!
ということで背景色等をイジって。。。
それっぽいのが出来ました!
デザインに関しては、用途や使用する人に合わせて簡単に変更できます。これもXcodeのデザインの拡張性の強さですよね。
これらのDIYにより実現できたAppleWatchのDIYを次のStepでシミュレータにて試用してみたいと思います。
【Step8】完成品を使ってみた(内部シミュレータ)
最後に、完成したアプリを動かしてみたいと思います!
まずは、シミュレータで両方のアプリを起動します。今回の仮想端末は、iPhone12とAppleWatch Series7です。
操作手順を順を追って説明していきます。
➀テキストボックスにメッセージを入力して「Send request」ボタンを押す
まとめ
今回は、アプリの構想を練るところから製造、テストまでをご紹介しました。AppleWatchをはじめとしたウェアラブルデバイスは、市場では最近よく見かけるようになったものの、ビジネスシーンではコスト面のこともあり、まだあまり見かけません。しかし、AppleWatchのバイブレーション機能のように、ウェアラブルデバイスは、人間と一体になることで効果を発揮する機能があふれています。この観点で考えると、働き方改革に直結するシステムとかも生み出せそうですね。
今回作ったアプリも、これで終わりにするのではなく、地道に改良を重ねてより良いものにしていきたいと思います!(面白い発見があれば、Part3以降で取り上げるかもです。。。)
いかがだったでしょうか?今後も本取り組みを続けていこうと思うので、実機テストが出来た、あるいはアプリに進展があった際は、都度展開しようと思います。
最後までお読みいただきありがとうございました!
ではさようなら~
おまけ
なぜ配信して実機でテストしないの?
今回のアプリは弊社のMacで作成しました。実機でテストするとなると、私のAppleWatchをMacに接続しなければなりません。業界のルール的に、個人のデバイスを会社のデバイスに接続するのは、セキュリティ面でよくないですよね。。。なので今回は内部のシミュレータ止まりとなったのです。
あ、ちなみに、TestFlight等でアプリを配信して実機テストする際も、配信時にAppleWatchのUUIDを登録しなければいけないので、似たような取り組みをされる方は注意が必要ですね!
もっとも、私がMacを持っていれば話は別なんですけどね。。。