iPhoneとAppleWatchの値渡し
目標:iPhoneで選択した値をAppleWatchに表示する、および、AppleWatchで選択した値をiPhoneに表示する。(※ Simulator)
通信できいない状態のときに変更されるかも知れないので、タイムスタンプ(String)を用いて最新の選択を表示できるようにもする。通信の種類が不適切なものもあるが、仕様をシンプルにするため、1種類のみにした。
- AppleWatch Simulator(以下AppleWatch)をiPhone Simulator(以下iPhone)をペアリング
- iPhoneとAppleWatchの値渡しの概要
- iPhone側のStoryBoardとViewControllerの修正
- AppleWatch側のInterfaceとInterfaceControllerの修正
まずは、AppleWatchアプリ作成のプロジェクトを新規作成する。
1. AppleWatch Simulator(以下Watch)をiPhone Simulator(以下iPhone)をペアリング
XCodeの Window > Devices and Simulators で右ペインの PAIRED WATCHES で+ボタンを押してペアリングさせる。
2. iPhoneとAppleWatchの値渡しの概要
値を送りたいタイミングで、次図の上にあるメソッドを使用する。
すぐに値を送るものと、機を見ていいタイミングで値を送るものとがある。
参照頁。このページは大変良い。
それで、次図の下にあるメソッドを構えておくと、値を受け取ることができる。
逆の送受信も同じ。
3. iPhone側のMainStoryBoardとViewControllerの修正
①MainStoryBoardの修正
次のように設定。
・TextField配置。ViewControllerにIBOutletを引っ張る。
・ButtonのA〜Cを配置。すべてのButtonをVirewControllerの同じActionに引っ張る。
② ViewControllerの修正
とりあえず全文。
import UIKit
import WatchConnectivity // (A)
class ViewController: UIViewController ,WCSessionDelegate { // (B)
var context: [String: Any]? = nil // (C)
@IBOutlet weak var field: UITextField!
@IBAction func touchButton(_ sender: UIButton) {
let ABC = sender.titleLabel?.text // (D)
let newContext = ["TIMESTAMP": TimeStamp, "ABC": ABC!]
context = newContext
UserDefaults.standard.set(newContext, forKey: "CONTEXT") // (E)
field.text = ABC!
if WCSession.isSupported(){
try? WCSession.default.updateApplicationContext(newContext) // (F)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
let defaultContext = defaults.dictionary(forKey: "CONTEXT")
if defaultContext == nil {// (G)
let newContext = ["TIMESTAMP": TimeStamp, "ABC": "A"]
defaults.set(newContext, forKey: "CONTEXT")
}
if context == nil{// (H)
self.setContext(UserDefaults.standard.value(forKey: "CONTEXT") as! [String : Any])
}
if WCSession.isSupported(){// (I)
let session = WCSession.default
session.delegate = self
session.activate()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { // (J)
print("ViewController_SessionactivationDidCompleteWith:")
print(activationState)
}
func sessionDidBecomeInactive(_ session: WCSession) { // (K)
print("ViewController_SessionDidBecomeInactive:")
}
func sessionDidDeactivate(_ session: WCSession) { // (L)
print("ViewController_SessionDidDeactivate:")
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
if context == nil { // (M)
self.setContext(applicationContext)
}else {
let timeStamp = context!["TIMESTAMP"] as! String
let applicationTimeStamp = applicationContext["TIMESTAMP"] as! String
if timeStamp < applicationTimeStamp {
self.setContext(applicationContext)
}else if timeStamp > applicationTimeStamp {
try? session.updateApplicationContext(context!)
}
}
}
func setContext(_ context:[String: Any]){ // (N)
self.context = context
let ABC = context["ABC"] as! String
DispatchQueue.main.async { // (O)
self.field.text = ABC
}
UserDefaults.standard.set(context, forKey: "CONTEXT")
}
var TimeStamp: String { // (P)
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMddHHmmss"
return formatter.string(from: date)
}
}
(B) WCSessionクラスのデリゲートを設定。
(C) データを保持するためのプロパティ。
(D) ボタンがタップされたとき、ボタンのタイトルを取得し分岐させる。
(E) データの永続保持のためにUserDefaultsを使用。
(F) AppleWatchに送信。
(G) UserDefaultsにデータがなかったときに初期化。
(H) プロパティに値がセットされていないとき、UserDefaultsから値取得。
(I) iPhoneとの通信がサポートされているかどうか確認し、サポートされていれば、デリゲートをセットし、通信をアクティブにする。
(J)〜(L) WCSessionDelegateの必須メソッド。
(M) WatchからのupdateApplicationContext( )を受け取るメソッド。送られてきたcontextのTimestampを調べ、Watch側の方が新しければ更新、iPhone側が新しければ送り返すように設定。
(N) contextを元に、プロパティとラベルに値を流し込むメソッド。
(O) UIViewの書き換えはメインスレッドでしかできない。衝突が起こらないように注意して使用する。
(P) 現在時刻からタイムスタンプ文字列を返す。
4. AppleWatch側のInterfaceStoryBoardとInterfaceControllerの修正
① RowControllerファイルの作成
UIKitで言うところのUITableViewCellのようなファイル。NSObjectのサブクラス。
File > New > File > WatchKit Class
クラス名はTableRowController、ターゲットがWatchKitExtensionになっていることを確認し作成する。
② InterfaceStoryBoardの修正
次のように設定。
・「TEST_WATCH_2」はLabel。InterfaceControllerにIBOutletを引っ張る。
・「Label」はTableをまず置いて、その上に置いたLabel。TableRowControllerにIBOutletを引っ張る。
・TableのRowControllerのクラスをTableRowControllerにセットし、IdentifierをTableRowにセット。
② InterfaceControllerの修正
とりあえず、全文。
import WatchKit
import Foundation
import WatchConnectivity // (A)
class InterfaceController: WKInterfaceController, WCSessionDelegate { // (B)
@IBOutlet var table: WKInterfaceTable!
@IBOutlet var field: WKInterfaceLabel!
var items = ["A","B","C"] // (C)
var context: [String: Any]? = nil // (D)
override func awake(withContext context: Any?) {
super.awake(withContext: context)
table.setNumberOfRows(items.count, withRowType: "TableRow") //(E)
for (index, item) in items.enumerated(){
let controller = table.rowController(at: index) as! TableRowController
controller.label.setText(item)
}
if WCSession.isSupported(){ // (F)
let session = WCSession.default
session.delegate = self
session.activate()
}
}
override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) { // (G)
let newContext = ["TIMESTAMP": TimeStamp, "ABC": items[rowIndex]]
self.setContext(newContext)
if WCSession.isSupported(){
try? WCSession.default.updateApplicationContext(newContext)
}
}
override func willActivate() {
super.willActivate()
if context == nil {
if WCSession.isSupported(){
let dammyContext = ["TIMESTAMP": "0", "ABC": ""] // (H)
try? WCSession.default.updateApplicationContext(dammyContext)
}else {
let newContext = ["TIMESTAMP": TimeStamp, "ABC": "A"]
setContext(newContext)
}
}
}
override func didDeactivate() {
super.didDeactivate()
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { // (I)
print("InterfaceController_activationDidCompleteWith:")
print(activationState)
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) { // (J)
if context == nil {
self.setContext(applicationContext)
}else {
let timeStamp = context!["TIMESTAMP"] as! String
let applicationTimeStamp = applicationContext["TIMESTAMP"] as! String
if timeStamp < applicationTimeStamp {
self.setContext(applicationContext)
}else if timeStamp > applicationTimeStamp {
try? session.updateApplicationContext(context!)
}
}
}
func setContext(_ context:[String: Any]){ // (K)
self.context = context
let ABC = context["ABC"] as! String
field.setText(ABC)
}
var TimeStamp: String { // (L)
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMddHHmmss"
return formatter.string(from: date)
}
}
(B) WCSessionクラスのデリゲートを設定。
(C) 選択肢配列。
(D) データを保持するためのプロパティ。
(E) 選択肢を表示するTableの初期化。UIKitとは様相が異なるので注意。
(F) iPhoneとの通信がサポートされているかどうか確認し、サポートされていれば、デリゲートをセットし、通信をアクティブにする。
(G) Tableを選択した時に呼ばれる。デリゲートメソッドではない。新しいcontextを作成し、setContextする。
(H) アクティブになったとき、contextが空なら、iPhoneに問い合わせ。通信がすぐにできないときは、通信ができたときに値を取得する仕様。
(I) WCSessionDelegateの必須メソッド。
(J)iPhoneからのupdateApplicationContext( )を受け取るメソッド。送られてきたcontextのTimestampを調べ、iPhone側の方が新しければ更新、Watch側が新しければ送り返すように設定。
(K) contextを元に、プロパティとラベルに値を流し込むメソッド。
(L) 現在時刻からタイムスタンプ文字列を返す。
以上で、Runすると次のような画面が出て、相互のアクションを送り合います。
※諸事情により、まだ実機では試していません。