【SwiftUI】Apple Watchアプリを開発する方法!iPhoneと連携
この記事からわかること
- Swift UIでApple Watchアプリを開発する方法
- iOS(iPhone)アプリと連携させるには?
- WatchConnectivityフレームワークの使い方
- WCSessionクラスとは?
- データを送信/受信する方法
- シミュレーターで動作確認する方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- watchOS:10.1
- Swift:5.9
- macOS:Sonoma 14.1
※最新のApple Watch(watchOS:10.1)はiOS17にしか対応していないようなので注意が必要です。
Apple Watchアプリの開発
Apple製品の1つであるApple Watchで利用できるアプリを開発・公開したい場合はApple製品用アプリケーションの統合開発環境であるXcodeのインストールと有料のメンバーシップであるADP(Apple Developer Program)への加入が必要になります。
Xcode
XcodeではiPhoneやiPad、Mac、Apple Watchで使用できるアプリを開発に必要な環境を提供してくれます。
おすすめ記事:Xcodeのインストール方法と使い方!終わらない時の対処法!
Apple Developer Program
App Storeにアプリを公開するためには「Apple Developer Program」への加入が必須になります。
おすすめ記事:【iOS】Apple Developer Programとは?登録方法や費用と手順
Apple Watchアプリで実装できる機能
iPhoneとApple Watchでは利用されているOSが異なり、デバイスサイズも大きく変わりUIも流用できないため、ソースコードは別々で実装する必要があります。基本的な開発の流れはiOSアプリと変わらないのでiOSアプリを開発する知見があれば実装方法さえ掴めば開発は可能です。
Apple Watchアプリではヘルスケア情報やiPhoneとの連携、通知機能などさまざまな機能が実装できるようになっています。iPhoneでできることが全てできるわけではないと思うので実装できるかどうかは下調べが重要になってくると思います。
- ヘルスケア情報の取得
- iPhoneとの連携
- Siri
- ローカル通知/リモート通知
- 位置情報
- ジャイロセンサー
- Bluetooth
- NFC
- etc..
Apple Watchアプリのみの開発方法
ここからは実際に簡単なApple Watchアプリを開発して流れを掴んでいきたいと思います。Xcodeを起動させ「platform」に「watchOS」を選択し「App」をクリックします。
続いてアプリ名やチームを選択し、Apple Watchアプリ単体で使うアプリであれば「Watch-only App」を、iPhoneと連携させるApple Watchアプリであれば「Watch App with New Companion iOS App」にチェックを入れて進みます。今回は「Watch-only App」で進めていきます。
※watchOS 7以降はStoryboardを使用したUIの設計は非推奨になったのでSwift UIで開発を行う必要があります。
項目 | 入力値 |
---|---|
Product Name | アプリ名 |
Team | チーム名 |
Organization identifier(組織ID) | アプリ名を元に自動で入力 |
Bundle Identifier(アプリ識別ID) | アプリ名を元に自動で入力 |
プロジェクトが作成されるとXcodeのワークスペースが開きます。Swift UIで実装されたデモアプリが表示されるのでここから自分の作りたいアプリを開発していけばOKです。iOSアプリと開発方法は基本的に変わりませんが、ボタンをタップして画面遷移を実行するアプリを実装してみます。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink {
ChildView()
} label: {
Text("ChildViewへ")
.padding()
}
}
}
}
#Preview {
ContentView()
}
import SwiftUI
struct ChildView: View {
var body: some View {
VStack {
Image (systemName:"globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text ("Hello, world!")
.padding()
}.padding()
}
}
#Preview {
ChildView()
}
ボタンをクリックすると画面が遷移するアプリが実装できました。Apple Watchもシミュレーターでも動作を確認できるので実際にデバイスを持っていなくても開発自体を進めることが可能です。
iPhoneアプリと連携させるアプリの開発方法
続いてiPhone(iOS)アプリと連携して利用できるwatchOSアプリを作成してみます。連携できることを確かめるにはBluetoothでペアリングした状態のiPhoneとApple Watchが必要になります。実機であれば最初にペアリングをしておいてください。実機がない場合はシミュレーターでも動作確認ができるので「こちら」を参考にしてください。
実際に実装していく流れは以下の通りになります。セクションを区切って要件をわかりやすくするために、読みにくくなってしまいましたがご容赦ください。
流れ
- 連携プロジェクトの作成
- Apple WatchとiPhoneの接続を確認する
- Watchからデータを送信する
連携プロジェクトの作成
連携アプリを開発するためにはプロジェクト作成の際に「Watch App with New Companion iOS App」にチェックを入れて進みます。アプリ名を「TestLinkWatch」にしたせいでややこしくなってしまいましたがプロジェクトの中に2つのターゲット「TestLinkWatch(iOSアプリ用)」と「TestLinkWatch Watch(watchOSアプリ用)」があるのを確認できます。
この場合Bundle IDはiOSアプリ側がTestLinkWatch
、watchOS側がTestLinkWatch.watchkitapp
になります。接尾辞に自動でwatchkitapp
が付与されるようです。
Apple WatchとiPhoneの接続を確認する
まずはApple WatchとiPhoneが正常に接続できているかを確かめます。Apple Watchアプリ開発にはWatchConnectivity
フレームワークを利用して機能を実装していきます。まずはiOS側にWatch側と接続するためのコードを実装します。WCSession
クラスが実際に通信を開始するためのオブジェクトになります。
import UIKit
import WatchConnectivity
class WatchConnectViewModel: NSObject {
var session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
if WCSession.isSupported() {
self.session.delegate = self
self.session.activate()
}
}
}
extension WatchConnectViewModel: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print(error.localizedDescription)
} else {
print("セッション:アクティベート")
}
}
func sessionDidBecomeInactive(_ session: WCSession) { }
func sessionDidDeactivate(_ session: WCSession) { }
}
Watch側にも同じようなクラスを作成しておきます。
import WatchConnectivity
class iOSConnectViewModel: NSObject {
var session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
if WCSession.isSupported() {
self.session.delegate = self
self.session.activate()
}
}
}
extension iOSConnectViewModel: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print(error.localizedDescription)
} else {
print("セッション:アクティベート")
}
}
}
作成したそれぞれのクラスは各ContentView
側でインスタンス化しておきます。
struct ContentView: View {
private let viewModel = WatchConnectViewModel()
@State var isConnect = false
var body: some View {
VStack {
Text(isConnect ? "Connect" : "No Connect")
Button {
isConnect = viewModel.session.isReachable
} label: {
Text("Check")
}
}.padding()
}
}
struct ContentView: View {
private let viewModel = iOSConnectViewModel()
@State var isConnect = false
var body: some View {
VStack {
Text(isConnect ? "Connect" : "No Connect")
Button {
isConnect = viewModel.session.isReachable
} label: {
Text("Check")
}
}.padding()
}
}
アプリを起動して、ボタンをそれぞれタップするとペアリングが成功していれば「Connect」、未接続であれば「No Connect」が表示されるようになります。
Watchからデータを送信する
Watch側からデータを送信しiPhone側で表示させるように実装していきます。まずはWatch側からデータを送信する処理を実装します。sendMessage
メソッドは辞書型でデータをiPhone側へ送信します。
func send(lang: String) {
let langDic: [String: String] = ["lang": lang]
self.session.sendMessage(langDic) { _ in }
}
UI側でもデータを送信処理を実装しておきます。
struct ContentView: View {
private let langs = ["Swift", "Kotlin", "Objective-C", "Java", "Dart"]
private let viewModel = iOSConnectViewModel()
var body: some View {
List(langs.indices, id:\.self) { index in
Button {
viewModel.send(lang: langs[index])
} label: {
Text(langs[index])
}
}
}
}
iOS側ではsendMessage
メソッドで送信されたデータを受け取るデリゲートメソッドを実装していきます。またlangs
プロパティを追加しObservableObject
を継承してUIに反映させられるようにしていきます。
class WatchConnectViewModel: NSObject, ObservableObject {
@Published var langs: [String] = []
var session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
if WCSession.isSupported() {
self.session.delegate = self
self.session.activate()
}
}
}
extension WatchConnectViewModel: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print(error.localizedDescription)
} else {
print("セッション:アクティベート")
}
}
func sessionDidBecomeInactive(_ session: WCSession) { }
func sessionDidDeactivate(_ session: WCSession) { }
/// sendMessageメソッドで送信されたデータを受け取るデリゲートメソッド
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
guard let lang = message["lang"] as? String else {
return
}
self.langs.append(lang)
}
}
iOS側のビューで表示させれば完了です。
struct ContentView: View {
@ObservedObject var viewModel = WatchConnectViewModel()
@State var isConnect = false
var body: some View {
VStack {
Text(isConnect ? "Connect" : "No Connect")
Button {
isConnect = viewModel.session.isReachable
} label: {
Text("Check")
}
Text("\(viewModel.langs.count)")
List(viewModel.langs.indices, id:\.self) { index in
Text(viewModel.langs[index])
}
}.padding()
}
}
WatchのボタンをクリックするとiPhoneでタップされた値が表示されていくようになります。
iOS側からデータを送る
Watch側からデータを送信した実装と同じ方法でiOS側からWatch側へデータを送信することも可能です。その場合はWatchConnectViewModel
側にsend
メソッドを実装してiOSConnectViewModel
側にデリゲートメソッドを実装すればOKです。^
func send(lang: String) {
let langDic: [String: String] = ["lang": lang]
self.session.sendMessage(langDic) { _ in }
}
/// sendMessageメソッドで送信されたデータを受け取るデリゲートメソッド
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
guard let lang = message["lang"] as? String else {
return
}
print("iOSから受け取ったよ")
print(lang)
}
シミュレーターでiPhoneとApple Watchをペアリングする
ペアリングさせたシミュレーターを用意するためにはXcodeの「シミュレーター」>「Manage Run Destinations...」をクリックして「 + 」をクリックします。新規でデバイスを追加できるようになるので「Paired Apple Watch」にチェックを入れてiPhoneとApple Watchのシミュレーターを追加するだけです。
ビルドする際も両方ともペアリングシミュレーターを起動させるだけでペアリングされた状態になり、動作確認をすることができるようになります。
おすすめ記事
【Swift/Apple Watch】通信可能(ペアリング)状態を観測/取得する方法!isReachableの変化
ご覧いただきありがとうございました。