Apple Watch開発コトハジメまとめ：ボタンを押すと「にゃあ」という猫 on Apple Watch


## この記事でできるもの

ボタンを押すと「にゃあ」という猫 on Apple Watch

Youtube動画↓
[![ねこの動画](http://img.youtube.com/vi/O1OQfEkCBAI/0.jpg)](http://www.youtube.com/watch?v=O1OQfEkCBAI)

[GitHub](https://github.com/keix1/CatOnWatch)

## Step1. Hello, Watch!

1. [Apple Watchアップデート](https://support.apple.com/ja-jp/HT204641https://support.apple.com/ja-jp/HT204641)
    - まずはOSアップデートから
    - これしないとインストールできませんでした
    - iOSからできなかったのでWatch本体の設定からインストールしました

2. アプリのbuild & install
    - [Hello World 簡単なWatchアプリ作成](https://i-app-tec.com/watch/hello-world.html) がわかりやすかったです
    - Watchアプリをインストールするときはビルド対象をWatchKit Appにします
        - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142423/967199d6-d396-41e1-c28b-ad578f5dacec.png)
    - 以下が出るときはXcode再起動
        - ```Another launch session is already waiting to debug the app extension with```

## Step2. SwiftUIの勉強

1. [SwiftUI Tutorial](https://developer.apple.com/videos/play/wwdc2019/204/)
    - watchOSもSwift。まずはSwiftの書き方から
    - 動画もあるみたいですが、僕は[Webページの方](https://developer.apple.com/tutorials/swiftui/creating-and-combining-views)見ながらやりました

2. [SwiftUI Tutorial](https://developer.apple.com/tutorials/swiftui/creating-and-combining-views)の4わかりづらく下記に進みました

## Step3. 猫の画像をApple Watchに貼る

1. [SwiftUI - Circular Image Example.](https://iosdevcenters.blogspot.com/2019/09/swiftui-circular-image-example.html)には画像のサイズ調整までできるいい感じのコードがありました
    - Image("neko")でneko.jpgを表示します

2. WatchKit ExtentionのAssets.xcasset以下に`neko.jpg`をドラッグアンドドロップ
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142423/e7b93a67-c488-4f65-e8e8-677985b4bb68.png)
3. 表示されました
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142423/f13d9c9c-737c-f7f2-3214-4e912c8ee107.png)

## Step4. 画像をボタンにする

#### 1. SwiftUIでボタンを作成

```swift

VStack {
    Button(action: {
        
        self.buttonState.toggle()

    }) {
        Text("Button")
            .font(.headline)
    }

    if buttonState {
        Text("unnyyyyyyaaaaa")
            .font(.headline)
    }
}
```

#### 2. 画像をボタンにする

- [stackoverflow](https://stackoverflow.com/questions/56820779/how-can-i-create-a-button-with-image-in-swiftui)にあるように下記のように設定したら動くようになりました
    - オリジナルの画像をレンダリングするようにしないといけないみたいです

```swift
Image("neko").renderingMode(.original)
```

#### 3. 画像ボタンのコード

```swift

VStack {
    Button(action: {
        
        self.buttonState.toggle()
        
    }) {
        VStack {
            Image("neko")
            .renderingMode(.original)
            .resizable()
            .frame(width: 100, height: 100)
            .clipShape(Circle())
            .overlay(
                Circle().stroke(Color.gray, lineWidth: 4))
                .shadow(radius: 10)
        }
    }

    if buttonState {
        Text("unnyyyyyyaaaaa")
            .font(.headline)
    }
}
```

#### 4. ボタンを押すとテキストが変わります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142423/98488c18-c7d8-bd6c-e552-1858657f106d.png)


## Step5. ボタンを押すと「にゃあ」というようにする

- [GitHubにあった水飲みアプリ](https://github.com/caiobzen/water-reminder-swiftui)を参考にしました。
- ```@ObservedObject```を使います
    - [StateとObservableを使ってSwiftUIのビューを変更する方法](https://creatorhiro.com/archives/1193)がわかりやすかったです
    - 以下引用

> ObservedObjectとは
> ~省略~
> 
> 実装したクラスやモデルが持つプロパティを監視する
> 監視しているプロパティが更新されると変更が通知される
> ObservableObjectを適用したクラスの一部実装にPublished属性を利用することができる

- `@State`は変数、`@ObservedObject`はクラスの変更を非同期にやってくれる、みたいな感じみたいです

1. 再生する音源を用意します。今回は適当に拾ってきた`cat.mp3`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142423/4f57201b-6f89-9a55-f5a7-c3de612130e8.png)

2. Xcodeに必要な設定をします

- Link Binary With LibrariesにAVFoundation.frameworkを追加
- Copy Bundle Resourcesにcat.mp3を追加
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142423/35826959-23ff-2fd2-8d10-771f56986d52.png)
    
#### 1. 音の再生を管理するclassを作成します

```swift:SoundPlayModel.swift

import Foundation
import AVFoundation

class SoundPlayModel: ObservableObject {
    var audioPlayer = AVAudioPlayer()

    init() {
        let sound = Bundle.main.path(forResource: "cat", ofType: "mp3")
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: sound!))
        }
        catch {
            print(error)
        }
    }
    
    func play() {
        self.audioPlayer.play()
    }
}
```

#### 2. ControllerからContentViewに渡します

```swift:HostingController.swift

class HostingController: WKHostingController<ContentView> {
    
    override var body: ContentView {
        return ContentView(soundPlayModel: SoundPlayModel())
    }

```

#### 3. ObservedObjectでSoundPlayModelを宣言して受け取ります

```swift:ContentView.swift

struct ContentView: View {
    @ObservedObject var soundPlayModel: SoundPlayModel

# ~省略~ 

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(soundPlayModel: SoundPlayModel())
    }
}

```

#### 4. 最終的なコード

```swift:ContentView.swift

import SwiftUI
import Combine
import AVFoundation

struct ContentView: View {
    @ObservedObject var soundPlayModel: SoundPlayModel
    @State private var buttonState = false

    var body: some View {
        
        VStack {
            Button(action: {
                
                self.buttonState.toggle()
                
                if self.buttonState {
                    self.soundPlayModel.play()
                }
                
            }) {
                VStack {
                    Image("neko")
                    .renderingMode(.original)
                    .resizable()
                    .frame(width: 100, height: 100)
                    .clipShape(Circle())
                    .overlay(
                        Circle().stroke(Color.gray, lineWidth: 4))
                        .shadow(radius: 10)
                }
            }

            if buttonState {
                Text("uunnyyyyyyaaaaa")
                    .font(.headline)
            }
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(soundPlayModel: SoundPlayModel())
    }
}

```

#### 5. 完成！

Youtube動画↓
[![ねこの動画](http://img.youtube.com/vi/O1OQfEkCBAI/0.jpg)](http://www.youtube.com/watch?v=O1OQfEkCBAI)