SwiftUI App ライフサイクル で Watch Connectivity

## はじめに

Xcode 12 から、プロジェクト作成時に Lifecycle を選択できるようになり、従来の "UIKit App Delegate" だけではなく、"SwiftUI App" を設定できるようになりました。

このようにして作成したプロジェクトには、AppDelegate.swift と SceneDelegate.swift がありません。
これまで、アプリの初期設定等をここで行っていましたが、"SwiftUI App" でアプリを作成した場合は、初期設定をどこでどのように行えば良いのでしょうか?

ここでは、SwiftUI App に iPhone と Apple Watch 間で通信を行うための、Watch Connectvity を使うための設定方法を説明します。
おそらく、Watch Connectvity 以外にも、AppDelegate.swift で初期設定を行なっていたアプリ全般に適用できるテクニックだと思います。

なお、本稿では、SwiftUI ならびに Watch Connectivity についての基本的な説明は割愛します。

## 開発環境等
- MacBook (Retina, 12inch, Early 2015)
- macOS Catalina Version 10.15.7
- Xcode Version 12.2(12B45b)
- iOS 14.2.1(18B121)
- watchOS 7.1(18R590)

## 従来の(App Delegate による)設定方法

"UIKit App Delegate" ライフサイクルでは、AppDelegate.swift でこんなふうに設定します(iPhone側)。

```AppDelegate.swift
import UIKit
import WatchConnectivity

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {

    ...<中略>...
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        // Watch Connectivity 初期設定
        if WCSession.isSupported() {
            let defaultSession = WCSession.default
            defaultSession.delegate = self
            defaultSession.activate()
        }

        return true
    }
```
Lifecycle として "SwiftUI App" を選択して作成したプロジェクトでは、AppDelegate.swift がありません。では、Watch Connectivity 周りの初期設定はどこでどう行えば良いのでしょうか？


## やはり AppDelegate ?

実は SwiftUI App でも AppDelegate を使うことができます。
@UIApplicationDelegateAdaptor() を使います。

※ここでは、SwiftUIWCTest という名前のアプリを作成しているものとします。SwiftUIWCTestApp.swift というファイルが自動生成されますので、そこに以下のように記述します。

```SwiftUIWCTestApp.swift
@main
struct TestApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        // Watch Connectivity 初期設定
        if WCSession.isSupported() {
            let defaultSession = WCSession.default
            defaultSession.delegate = self
            defaultSession.activate()
            }

        return true
    }
}
```

ただ、せっかく SwiftUI で AppDelegate を葬り去ったのに、またまた AppDelegate のお世話になるのはあんまり美しくありません。
加えて、Watch　Connetctivity を使うには、Apple Watch 側でも設定が必要で、そちらではもともと AppDelegate はありません。どうすれば良いのでしょうか？

## SwiftUI App ライフサイクルでの実装

まずは、WCSessionDelegate プロトコルを実装したクラスを用意します(ここでは、クラス名を WCManager とします)。

```WCManager.swift
import Foundation
import WatchConnectivity

class WCManager: NSObject, WCSessionDelegate {
    static var shared = WCManager()

    /// 初期化〜有効化
    func activate() {
        // Watch Connectivity
            if (WCSession.isSupported()) {
                let session = WCSession.default
                session.delegate = self
                session.activate()
            } else {
                print("** \(device):  WC is NOT Supported")
            }
        }
    }
    
    // メッセージ送受信メソッドなどを実装
    ...<中略>...
}

```

このクラスには、他にメッセージ送受のためのメソッドなどもまとめて実装します。
この辺は、SwiftUI App に限った話ではないのでここでは割愛します。

このうえで、SwiftUIWCTestApp の init() で activate すればOKです。


```SwiftUIWCTestApp.swift
@main
struct SwiftUIWCTestApp: App {
    init() {
        // Watch Connectivity 初期設定
        WCManager.shared.activate()
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

Apple Watch 側でも同様の方法で WCSession の初期設定を行います。
WCManager.swift は、iPhone アプリ側と、WatchKit Extension 側の両方からソースコードを共有できるように設定しておきます(Target Membership で設定)。

Apple Watfch側では、SwiftUIWCTest WatchKit Extension グループ下に、同じく SwiftUIWCTestApp.swift というファイルがあるので、SwiftUIWCTestApp の init() で初期設定します。

```SwiftUIWCTestApp.swift
@main
struct SwiftUIWCTestApp: App {

    init() {
        // Watch Connectivity 初期設定
        WCManager.shared.activate()
    }
    
    @SceneBuilder var body: some Scene {
        WindowGroup {
            ContentView()
        }

        WKNotificationScene(controller: NotificationController.self, category: "myCategory")
    }
}
```

## おわりに

Apple Watch 開発に関する情報はまだまだ少ないように思います。本稿で説明したのは簡単なことですが、個人的に試行錯誤して発見したことですので記録しておきます。
どなたかの参考になれば幸いです。

## 参考

- [iOSからWatchOSとのConnectivity with SwiftUI](https://tech.pfq.jp/blog/1434/)
- [\[Xcode 12\] アプリの起動について変更になった部分まとめ](https://dev.classmethod.jp/articles/xcode12_change_appdelegate/)
- [既存iOSプロジェクトをSwiftUI Appへ移行する - アプリ開発者はSwiftUIにおけるAppDelegate/SceneDelegateの扱いをよく理解していない?](https://note.com/notoroid/n/nfdec8ff65538)