記事

Apple Watch App 開発|watchOS 5でゼロから作成する手順とコツ

watchOS 5を使い、初めてのApple Watchアプリ開発に挑戦する方必見。基本から実践まで丁寧に解説し、スムーズに完成まで導く具体的な手順を紹介します。

Apple Watch App 開発|watchOS 5でゼロから作成する手順とコツ

本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。

記事一覧


Apple Watchアプリを作ってみよう!(Swift)

watchOS 5 ハンズオンで学ぶ Apple Watch アプリ開発:ゼロから始める

[最新] Apple Watch Series 6 開封&2年間使用体験レビュー >>>クリックして移動

前書き:

前回のApple Watch 入手開箱文から約3ヶ月が経ち、最近ようやくApple Watchアプリの開発に取り組む機会ができました。

[結婚吧 — 最大婚礼準備App](https://itunes.apple.com/tw/app/%E7%B5%90%E5%A9%9A%E5%90%A7-%E4%B8%8D%E6%89%BE%E6%9C%80%E8%B2%B4-%E5%8F%AA%E6%89%BE%E6%9C%80%E5%B0%8D/id1356057329#?platform=appleWatch){:target="_blank"}

結婚吧 — 最大婚礼準備App

補足して3ヶ月使用した感想:

  1. e-sim(LTE)はまだ使うタイミングが思いつかず、申請も使用もしていません。
  2. よく使う機能:近づくだけでMacのロック解除、手を上げて通知確認、Apple Pay。
  3. 健康リマインダー:3ヶ月経ってだんだん怠けてきて、通知は見るものの、リング達成してもあまり感じません。
  4. サードパーティ製アプリの対応は依然として非常に悪いです。
  5. 時計の文字盤は気分に合わせて自由に変更でき、新鮮さが増します。
  6. より詳細な運動記録:例えば少し遠くまで夕食を買いに行くと、時計が自動で検知して運動記録を取るか尋ねてきます。

3ヶ月間使用した後、全体的には元の開封レビューに書かれている通り、まるで複数の生活の小さなアシスタントのように、細かい問題を解決してくれます。

サードパーティ製アプリの対応は依然として非常に悪い

Apple Watchアプリを実際に開発する前は、なぜApple Watchのアプリはどれも簡素で、使える程度にしかなっていないのか疑問でした。LINE(メッセージが同期されず、更新もされていない)、Messenger(使えるだけ)も含めてです。実際にApple Watchアプリを開発してみて、初めて開発者の苦労がわかりました…。

まずは、Apple Watchアプリの位置づけを理解し、シンプルにすること

Apple Watchの位置づけ 「iPhoneの代わりではなく、補助である」 公式の紹介や公式App、watchOSのAPIもこの方針に沿っています。そのため、サードパーティのAppはシンプルで機能が少ないと感じることがあります(すみません、欲張りすぎましたOrz)

我們的A アプリを例にすると、店舗検索、コラム閲覧、掲示板、オンライン問い合わせなどの機能があります。オンライン問い合わせは、リアルタイムかつ迅速な返信が必要なため、Apple Watchに移す価値のある項目です。迅速な返信は注文獲得のチャンスを高めます。一方、店舗検索、コラム閲覧、掲示板は比較的複雑な機能であり、腕時計の画面では表示できる情報が少なく、リアルタイム性も必要ないため、Apple Watchで実装してもあまり意味がありません。

核心のコンセプトは「補助を主とする」ため、すべての機能をApple Watchに移す必要はありません。そもそもユーザーが腕時計だけをつけてスマホを持っていない時間は非常に少なく、そのような場合でもユーザーのニーズは重要な機能だけに限られます(例えば、コラム記事の閲覧のように、すぐに腕時計で見る必要がないものは含まれません)。

さあ、始めましょう!

これも私にとって初めてのApple Watchアプリ開発なので、内容が十分に深くないかもしれません。どうぞご指導ください!!

本記事はiOSアプリ/UIKitの基礎知識がある開発者向けです

本記事で使用:iOS ≥ 9、watchOS ≥ 5

iOSプロジェクトにwatchOSターゲットを新規作成する:

File -> New -> Target -> watchOS -> WatchKit App

File -> New -> Target -> watchOS -> WatchKit App

Apple Watch Appは単独でインストールできず、必ずiOS Appに依存します

新規作成後、ディレクトリはこのようになります:

二つのTarget項目があり、どちらも欠かせません:

  1. WatchKit App: リソースとUI表示を担当
    /Interface.storyboard:iOSと同様に、システムがデフォルトで作成したビューコントローラが含まれる
    /Assets.xcassets:iOSと同様に、使用するリソースを格納
    /info.plist:iOSと同様に、WatchKit Appに関する設定

  2. WatchKit Extension: プログラムの呼び出しとロジック処理を担当(*.swift)
    /InterfaceController.swift:デフォルトのビューコントローラープログラム
    /ExtensionDelegate.swift:SwiftのAppDelegateに類似し、Apple Watch Appの起動入口
    /NotificationController.swift:Apple Watch App上のプッシュ通知表示を処理
    /Assets.xcassets:ここでは使用せず、WatchKit AppのAssets.xcassetsに統一して配置
    /info.plist:iOSと同様に、WatchKit Extensionの関連設定
    /PushNotificationPayload.apns:プッシュ通知データで、シミュレーターでプッシュ通知機能のテストに使用可能

詳細は後ほど説明しますので、まずは目次とドキュメントの内容・機能をざっと把握してください。

ビューコントローラー:

Apple WatchではビューコントローラーはViewControllerではなくInterfaceControllerと呼ばれます。WatchKit App/Interface.storyboard内でInterface Controller Sceneを見つけることができ、その制御用のコードはWatchKit Extension/InterfaceController.swiftに配置されます(iOSと同じ概念です)。

SceneはデフォルトでNotification Controller Sceneと一緒に並んでいます(私は少し上に移動して分けます)

SceneはデフォルトでNotification Controller Sceneと一緒に配置されます(私は少し上に移動して分けます)

右側でInterfaceControllerのタイトル表示テキストを設定できます。

タイトルの色はInterface Builder DocumentのGlobal hint設定を使用しており、アプリ全体のスタイルカラーが統一されます。

コンポーネントライブラリ:

複雑なコンポーネントは少なく、コンポーネントの機能もシンプルでわかりやすい

複雑なコンポーネントはあまりなく、コンポーネントの機能もシンプルでわかりやすいです。

UI レイアウト:

万丈の高楼はViewから始まる。レイアウト部分はUIKit(iOS)のAuto Layoutや制約、レイヤーを使わず、すべてパラメータでレイアウト設定を行うため、よりシンプルで強力です(UIKitのUIStackViewに似ています)。

すべてのレイアウトはGroupで構成されており、UIKitのUIStackViewに似ていますが、より多くのレイアウトパラメータを設定できます

Groupのパラメータ設定

Groupのパラメータ設定

  1. Layout:内包された子Viewのレイアウト方式を設定(水平、垂直、レイヤースタック)

  2. Insets:Groupの上下左右の間隔を設定する

  3. Spacing:内部に包まれた子View同士の間隔を設定する

  4. Radius:Groupの角丸を設定します。そうです!WatchKitには角丸設定のパラメータが標準で備わっています。

  5. Alignment/Horizontal:水平の配置方法(左、中央、右)を設定し、隣接するビューや外側のラップビューの設定と連動します

  6. Alignment/Vertical:垂直方向の配置(上、中、下)を設定します。隣接するビューや外側のラップビューの設定と連動します。

  7. Size/Width:Groupのサイズを設定します。3つのモードがあります。「Fixed:固定幅を指定」、「Size To Fit Content:子Viewのサイズに合わせて幅を決定」、「Relative to Container:親Viewのサイズを基準に幅を設定(%や+ -の補正値も設定可能)」

  8. Size/Height:Size/Widthと同様に、高さを設定する項目です

フォント/フォントサイズの設定:

システムのText Stylesを直接適用するか、Customを使用できます(ただし、Customではフォントサイズを設定できませんでした)。そのため、私はSystemを使って 各表示ラベルのフォントサイズをカスタマイズしています。

実践で学ぶ:Lineのレイアウトを例に

排版部分はiOSほど複雑ではないので、サンプルを使って直接説明します。すぐに使い始められます。Lineのホーム画面のレイアウトを例にします:

WatchKit App/Interface.storyboardでInterface Controller Sceneを見つける:

  1. ページ全体は、iOSアプリ開発で使用するUITableViewに相当しますが、Apple Watchアプリでは操作が簡略化され、「WKInterfaceTable」と呼ばれています。
    まずはInterface Controller SceneにTableをドラッグします。

UIKitのUITableViewと同様に、テーブル本体とセル(Apple WatchではRowと呼ばれます)があります。使い方は大幅に簡略化されており、このインターフェース上で直接セルのデザインやレイアウトが可能です!

  1. レイアウト構造の分析とRow表示スタイルの設計:

左側に角丸の全幅イメージを配置し、その上にラベルを重ねます。右側は上下に均等に分割された2つの領域を配置し、上部にラベル、下部にもラベルを配置します。

2–1: 左右二つのブロック構造を作成する

2つのGroupを1つのGroupにドラッグし、それぞれのSizeパラメータを設定します:

左側の緑色部分:

Layout設定Overlap,裡面子View要做未読メッセージLabelのレイヤー重ね表示

Layout設定でOverlapし、その中の子Viewに未読メッセージLabelのレイヤースタック表示を行う

固定幅高さ40の正方形を設定

幅と高さを40に固定した正方形を設定する

右側の赤い部分:

Layout設定Vertical,裡面子View要做上下兩個顯示

LayoutをVerticalに設定し、その中の子Viewを上下に2つ表示する

幅の設定は外側を参照し、割合は100%、左の緑色部分40を差し引く

幅は外側を参照し、比率は100%、左側の緑部分40を差し引く。

左右コンテナ内のレイアウト:

左側部分:Imageをドラッグし、その後Labelを包むGroupをドラッグして右下に揃える(Groupに背景色を設定し、間隔と角丸を設定)

右側部分:Labelを2つ配置し、1つは左上揃え、もう1つは左下揃えに設定してください。

Rowに名前を付ける(UIKitのUITableViewでCellにidentifierを設定するのと同様):

選定Row->Identifier->カスタム名を入力

Row->Identifier->カスタム名を入力

Rowの表示スタイルは一種類だけではありませんね?

非常に簡単で、Table内にRowをドラッグして配置し(実際に表示するRowのスタイルはプログラムで制御)、Identifierを入力して命名するだけです。

ここでデータがない場合の表示用にもう一つのRowを追加

ここにデータがない場合のメッセージを表示するためのRowを追加します

レイアウトに関する情報

watchKitのhiddenはスペースを取らないため、インタラクティブな用途に使えます(ログインしていればTableを表示し、ログインしていなければ案内用のLabelを表示)。

レイアウトはここまでで一旦区切ります。個人のデザインに合わせて調整してください。操作は簡単なので、何度か繰り返し配置や整列パラメータを試して慣れましょう!

プログラム制御部分:

Rowに続いて、Rowを参照操作するためのクラスを作成する必要があります:

1
2
class ContactRow:NSObject {
}

1
2
3
4
5
6
7
8
class ContactRow:NSObject {
    var id:String?
    @IBOutlet var unReadGroup: WKInterfaceGroup!  // 未読グループ
    @IBOutlet var unReadLabel: WKInterfaceLabel!  // 未読ラベル
    @IBOutlet weak var imageView: WKInterfaceImage!  // 画像ビュー
    @IBOutlet weak var nameLabel: WKInterfaceLabel!  // 名前ラベル
    @IBOutlet weak var timeLabel: WKInterfaceLabel!  // 時間ラベル
}

Outletを接続し、変数を保存する

Table部分も同様にOutletをControllerに接続します:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class InterfaceController: WKInterfaceController {

    @IBOutlet weak var Table: WKInterfaceTable!
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        
        // インターフェースオブジェクトをここで設定します。
    }
    
    override func willActivate() {
        // このメソッドはウォッチのビューコントローラーがユーザーに表示される直前に呼ばれます
        super.willActivate()
    }
    
    struct ContactStruct {
        var name:String
        var image:String
        var time:String
    }
    
    func loadData() {
        // APIコールバックを取得...
        //postData {
        let data:[ContactStruct] = [] // APIから返されたデータ...
        
        self.Table.setNumberOfRows(data.count, withRowType: "ContactRow")
        // 複数のROWを表示する場合は以下を使用:
            //self.Table.setRowTypes(["ContactRow","ContactRow2","ContactRow3"])
        //
        for item in data.enumerated() {
            if let row = self.Table.rowController(at: item.offset) as? ContactRow {
                row.nameLabel.setText(item.element.name)
                // ラベルや画像に値を割り当てる......
            }
        }
        
        //}
    }
    
    override func didDeactivate() {
        // このメソッドはウォッチのビューコントローラーが表示されなくなった時に呼ばれます
        super.didDeactivate()
        loadData()
    }
    
    // Row選択時の処理:
    override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
        guard let row = table.rowController(at: rowIndex) as? ContactRow,let id = row.id else {
            return
        }
        self.pushController(withName: "showDetail", context: id)
    }
}

Tableの操作は大幅に簡略化されており、delegateやdatasourceは不要です。データの設定はsetNumberOfRows/setRowTypesで行数と行の種類を指定し、rowController(at:)を使って各行のデータ内容を設定するだけです!

TableのRow選択イベントも override func table( _ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) を実装するだけで操作可能です!(Tableはこのイベントのみです)

ページ遷移の方法は?

まずInterface ControllerにIdentifierを設定

まずはInterface ControllerにIdentifierを設定します

watchKitには2つのページ遷移モードがあります:

  1. iOS UIKitのpushに似ています
    self.pushController(withName: Interface Controller Identifier , context: Any? )

push方式で左上に戻るボタン

push方式は左上に戻るボタンがあります

前のページに戻る(iOS UIKitと同様):self.pop()

ルートページに戻る:self.popToRootController()

新しいページを開く:self.presentController( )

  1. タブ表示方法 WKInterfaceController.reloadRootControllers(withNames: [ インターフェースコントローラ識別子 ], contexts: [ Any? ] )

またはStoryboard上で、最初のページのInterface ControllerからControlキーを押しながらクリックしてドラッグし、次のページに「next page」を選択することもできます。

タブ表示は左右にページを切り替え可能

タブ表示は左右にスワイプしてページを切り替えられます。

2つのページ遷移方法は混用できません。

ページ遷移のパラメータ?

iOSのようにカスタムdelegateやsegueでパラメータを渡す必要はなく、watchKitのページ遷移でパラメータを渡す場合は、上記メソッドの contexts にパラメータを入れるだけです。

接続パラメータは InterfaceController の awake(withContext context: Any?) で受け取る

例えば、AページからBページにid:Intを渡して遷移する場合:

Aページ:

1
self.pushController(withName: "showDetail", context: 100)

Bページ:

1
2
3
4
5
6
7
8
9
override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        guard let id = context as? Int else {
           print("パラメータエラー!")
           self.popToRootController()
           return
        }
        // インターフェースオブジェクトの設定をここで行う。
}

プログラム制御コンポーネント部分

iOS UIKitと比べてかなり簡素化されており、iOS開発経験があればすぐに慣れるでしょう!
例えば、labelはsetText()に変わります。
p.s. しかもgetTextメソッドがなく、extensionの変数を使うか外部変数に保存するしかありません。

iPhoneとの同期/データ転送

もしiOSの関連Extensionを開発したことがあれば、無意識にApp Groupsを使ってUserDefaultsを共有する方法を使うでしょう。私も最初はその方法でやってみて、ずっとデータが渡らなくて悩みましたが、調べてみるとwatchOS 2以降はこの方法がサポートされていないことが分かりました…。

新しいWatchConnectivity方式を使ってiPhoneとApple Watch間で通信を行う(ソケットのような概念)、iOS側とwatchOS側の両方で実装が必要です。以下はシングルトンパターンでの実装例です:

モバイル端末:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import WatchConnectivity

class WatchSessionManager: NSObject, WCSessionDelegate {
    @available(iOS 9.3, *)
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        // iPhone側のセッション起動完了
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        // iPhone側がWatchから送られたUserInfoを受信
    }
    
    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
        // iPhone側がWatchからのMessageを受信
    }
    
    // 他にもdidReceiveMessageData,didReceiveFileがあり、いずれもWatchからのデータ受信を処理
    // データの送受信ニーズに応じて使い分ける
    
    func sendUserInfo() {
        guard let validSession = self.validSession,validSession.isReachable else {
            return
        }
        
        if userDefaultsTransfer?.isTransferring == true {
            userDefaultsTransfer?.cancel()
        }
        
        var list:[String:Any] = [:]
        // UserDefaultsの内容をlistに入れる....
        
        self.userDefaultsTransfer = validSession.transferUserInfo(list)
    }
    
    func sessionReachabilityDidChange(_ session: WCSession) {
        // Watchアプリの接続状態が変わったとき(Watchアプリ起動時/終了時)
        sendUserInfo()
        // 状態変化時にWatchアプリ起動ならUserDefaultsを同期
    }
    
    func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
        // UserDefaultsの同期完了(transferUserInfo)
    }
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        
    }
    
    static let sharedManager = WatchSessionManager()
    private override init() {
        super.init()
    }
    
    private let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil
    private var validSession: WCSession? {
        if let session = session, session.isPaired && session.isWatchAppInstalled {
            return session
        }
        // 有効で接続中かつWatchアプリが起動中のセッションを返す
        return nil
    }
    
    func startSession() {
        session?.delegate = self
        session?.activate()
    }
}

WatchConnectivity iPhone側のコード

iOS/AppDelegate.swiftのapplication( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?)内にWatchSessionManager.sharedManager.startSession()を追加し、スマホアプリ起動後にセッションを接続するようにします。

ウォッチ側:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import WatchConnectivity

class WatchSessionManager: NSObject, WCSessionDelegate {
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
    }
    
    func sessionReachabilityDidChange(_ session: WCSession) {
        guard session.isReachable else {
            return
        }
        
    }
    
    func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
        
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        DispatchQueue.main.async {
            //UserDefaults:
            //print(userInfo)
        }
    }
    
    static let sharedManager = WatchSessionManager()
    private override init() {
        super.init()
    }
    
    private let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil
    
    func startSession() {
        session?.delegate = self
        session?.activate()
    }
}

WatchConnectivity ウォッチ側のコード

そして、WatchOS Extension/ExtensionDelegate.swift の applicationDidFinishLaunching( ) 内に WatchSessionManager.sharedManager.startSession( ) を追加し、ウォッチアプリ起動後にセッションを接続します。

WatchConnectivity データ転送方法

データ送信:sendMessage, sendMessageData, transferUserInfo, transferFile
データ受信:didReceiveMessageData, didReceive, didReceiveMessage
両端の送受信メソッドは同じです

時計から携帯へのデータ送信は問題なくできますが、携帯から時計へのデータ送信は時計のアプリが開いている時に限られます。

watchOSプッシュ通知処理

プロジェクトディレクトリの下にあるPushNotificationPayload.apnsはここで役立ちます。これはシミュレーター上でプッシュ通知をテストするためのもので、シミュレーターにWatch Appターゲットをデプロイし、インストールしてアプリを起動すると、このファイルの内容を使ったプッシュ通知を受け取れます。これにより、開発者はプッシュ通知機能を簡単にテストできます。

如要修改/啟用/停用 PushNotificationPayload.apns,請選擇Target後Edit Scheme

PushNotificationPayload.apnsを変更/有効化/無効化するには、ターゲットを選択してからEdit Schemeを選んでください。

watchOS プッシュ通知の処理:

iOSと同様にUNUserNotificationCenterDelegateを実装し、watchOSでも同じメソッドをwatchOS Extension/ExtensionDelegate.swift内で実装します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import WatchKit
import UserNotifications
import WatchConnectivity

class ExtensionDelegate: NSObject, WKExtensionDelegate, UNUserNotificationCenterDelegate {

    func applicationDidFinishLaunching() {
        
        WatchSessionManager.sharedManager.startSession() //前述のWatchConnectivity接続開始
      
        UNUserNotificationCenter.current().delegate = self //UNUserNotificationCenterのデリゲートを設定
        // アプリケーションの最終初期化処理を行う
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.sound, .alert])
        // iOSと同様に、アプリがフォアグラウンドの時でも通知を表示するための処理
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // 通知をタップした時
        guard let info = response.notification.request.content.userInfo["aps"] as? NSDictionary,let alert = info["alert"] as? Dictionary<String,String>,let data = info["data"] as? Dictionary<String,String> else {
            completionHandler()
            return
        }
        
        // response.actionIdentifierでタップイベントのIdentifierを取得可能
        // デフォルトのタップイベント:UNNotificationDefaultActionIdentifier
        
        if alert["type"] == "new_ask") {
            WKExtension.shared().rootInterfaceController?.pushController(withName: "showDetail", context: 100)
            // 現在のroot interface controllerを取得し、pushする
        } else {
           // その他の処理....
           //WKExtension.shared().rootInterfaceController?.presentController(withName: "", context: nil)
            
        }
        
        completionHandler()
    }
}

ExtensionDelegate.swift

watchOS の通知表示は、以下の3種類に分かれます:

  1. static:デフォルトのプッシュ通知の表示方法

スマホのプッシュ通知と連携し、iOS側でUNUserNotificationCenter.setNotificationCategoriesを実装して通知下部にボタンを追加;Apple Watchでもデフォルトで同様に表示される

携帯のプッシュ通知と連動して、ここではiOS側でUNUserNotificationCenter.setNotificationCategoriesを実装し、通知の下にボタンを追加しています;Apple Watch側でもデフォルトで同様に表示されます。

  1. dynamic:プッシュ通知の表示スタイルを動的に処理(内容の再構成、画像の表示)

  2. interactive:watchOS 5以降でサポートされ、dynamicに加えてボタン操作にも対応

Interface.storyboardのStatic Notification Interface Controller Sceneでプッシュ通知の処理方法を設定可能

Interface.storyboardのStatic Notification Interface Controller Sceneでプッシュ通知の処理方法を設定可能です

staticは特に説明することはありません。デフォルトの表示方法を使います。ここではdynamicを紹介します。「Has Dynamic Interface」にチェックを入れると「Dynamic Interface」が表示され、ここでカスタムの通知表示方法をデザインできます(Buttonは使用できません):

私のカスタム通知表示デザイン

私のカスタムプッシュ通知の表示デザイン

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import WatchKit
import Foundation
import UserNotifications

class NotificationController: WKUserNotificationInterfaceController {

    @IBOutlet var imageView: WKInterfaceImage!
    @IBOutlet var titleLabel: WKInterfaceLabel!
    @IBOutlet var contentLabel: WKInterfaceLabel!
    
    override init() {
        // ここで変数を初期化します。
        super.init()
        self.setTitle("結婚吧") // 右上のタイトルを設定
        // インターフェースオブジェクトをここで設定します。
    }

    override func willActivate() {
        // このメソッドはウォッチのビューコントローラがユーザーに表示される直前に呼ばれます
        super.willActivate()
    }

    override func didDeactivate() {
        // このメソッドはウォッチのビューコントローラが表示されなくなった時に呼ばれます
        super.didDeactivate()
    }
    
    override func didReceive(_ notification: UNNotification) {
        
        if #available(watchOSApplicationExtension 5.0, *) {
            self.notificationActions = []
            // iOS側で実装したUNUserNotificationCenter.setNotificationCategoriesによる通知下部のボタンをクリア
        }
        
        guard let info = notification.request.content.userInfo["aps"] as? NSDictionary,let alert = info["alert"] as? Dictionary<String,String> else {
            return
        }
        // プッシュ通知の情報
        
        self.titleLabel.setText(alert["title"])
        self.contentLabel.setText(alert["body"])
        
        if #available(watchOSApplicationExtension 5.0, *) {
            if alert["type"] == "new_msg" {
              // 新しいメッセージ通知の場合、通知下に返信ボタンを追加
              self.notificationActions = [UNNotificationAction(identifier: "replyAction",title: "返信", options: [.foreground])]
            } else {
              // その他の場合は閲覧ボタンを追加
              self.notificationActions = [UNNotificationAction(identifier: "openAction",title: "閲覧", options: [.foreground])]
            }
        }
        
        
        // このメソッドは通知を表示する必要があるときに呼ばれます。
        // 動的通知インターフェースを使う場合に実装してください。
        // 動的通知インターフェースをできるだけ速く設定してください。
        
    }
}

プログラム部分も同様にアウトレットをコントローラーに接続し、機能を実装します。

次にinteractiveについて説明します。dynamicと同様ですが、Buttonを追加でき、dynamicと同じClassでプログラムを制御できます。私はinteractiveを使っていません。なぜなら、ボタンはプログラムでself.notificationActionsを使って追加しているからです。違いは以下の通りです:

左はinteractiveを使用、右はself.notificationActionsを使用

左はinteractiveを使用し、右はself.notificationActionsを使用しています

両方の方法ともwatchOS 5以上が必要です。

self.notificationActionsでボタンを追加した場合、ボタンのイベント処理はExtensionDelegate内の userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) で行い、identifierでアクションを識別します。

メニュー機能?

コンポーネントライブラリからMenuをドラッグし、メニュー項目Menu Itemを追加し、IBActionをコードにドラッグ

コンポーネントライブラリからMenuをドラッグし、次にメニュー項目のMenu Itemをドラッグして、IBActionをコードに接続します。

ページを強く押すと以下が表示されます:

内容入力?

内蔵のpresentTextInputControllerメソッドを使うだけでOK!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@IBAction func replyBtnClick() {
    guard let target = target else {
        return
    }
    
    self.presentTextInputController(withSuggestions: ["後で返信します","ありがとうございます","ご連絡お待ちしております","はい","OK!"], allowedInputMode: WKTextInputMode.plain) { (results) in
        
        guard let results = results else {
            return
        }
        // 入力値がある場合
        
        let txts = results.filter({ (txt) -> Bool in
            if let txt = txt as? String, txt != "" {
                return true
            } else {
                return false
            }
        }).map({ (txt) -> String in
            return txt as? String ?? ""
        })
        // 入力の前処理
        
        
        txts.forEach({ (txt) in
            print(txt)
        })
    }
}

まとめ

ご覧いただきありがとうございます!お疲れ様です!

ここまでで記事は一段落です。UIレイアウト、プログラム、プッシュ通知、インターフェース応用について大まかに触れました。iOS開発経験があれば習得は非常に早く、ほぼ同じで多くの方法が簡略化されていて使いやすいですが、できることは確かに減っています(例えば、現時点ではTableの「もっと読み込む」機能の実装方法がわかっていません)。現状できることは少ないですが、今後公式がより多くのAPIを開発者に開放してくれることを期待しています❤️❤️❤️

MurMur:

Apple Watch App ターゲットの手首へのデプロイは本当に遅い — [Narcos](https://www.netflix.com/tw/title/80025172){:target="_blank"}

Apple Watch App Target を手首のデバイスにデプロイするのは本当に遅い — Narcos

ご質問やご意見がありましたら、こちらからご連絡ください

PostZMediumToMarkdown によって Medium から変換されました。


🍺 Buy me a beer on PayPal

👉👉👉 Follow Me On Medium! (1,053+ Followers) 👈👈👈

本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

Improve this page on Github.

本記事は著者により CC BY 4.0 に基づき公開されています。

© ZhgChgLi. All rights reserved.
閲覧数: 802,415+, 最終更新日時: 2026-01-15 11:14:58 +08:00

本サイトは Chirpy テーマを使用し、Jekyll 上で構築されています。
Medium の記事は ZMediumToMarkdown により変換されています。