watchOSで〇〇アニメーション

watchOSアプリを開発した際に、いくつかアニメーションをつくりました
その中でうまくいったこと😹いかなかったこと😹があるので、そのまとめになります

## 基本のローディングアニメーション
実装方法は以下の2つ

1. パラパラ画像をつかう
2. APNGをつかう

### パラパラ画像をつかう
Appleのサンプルコード的にも正攻法といって良いでしょう
特徴としては、

* Asset Catalogを利用できるため、解像度の対応がしやすい
* 画像枚数が増えると管理が手間（あくまでAPNGにくらべて）

です

#### シンプルな Duration 指定の場合

```swift

// animatedImage: WKInterfaceImage
animatedImage.setImage(UIImage(named: "Bus"))
animatedImage.startAnimating()
```

`startAnimating`はStoryboard上で設定されたDurationでアニメーションを行います

**Storyboard**
<img width="258" alt="スクリーンショット 2020-02-07 17.18.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/a081c1a2-5490-b140-674f-3177c66e9eab.png">

**Asset Catalog**
<img width="231" alt="スクリーンショット 2020-02-07 17.21.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/30b865da-848d-d706-8a7b-400662890da1.png">

#### Duration + Repeat Count 指定の場合

```swift
animatedImage.setImage(UIImage(named: "Bus"))    
animatedImage.startAnimatingWithImages(in: NSMakeRange(0, 4), duration: 2.0, repeatCount: 3)
```

### APNGをつかう
APNGはアニメーションするPNG画像のことです。

特徴としては、

* リソースが1つになるので、画像パラパラよりも管理が楽
* 画像は、Extension ターゲットのリソースに入れる
* APNGの尺に合わせたDurationにすれば良い
* watchOSはAPNG対応のライブラリがない(*注)ので自前でソースコードを書く必要がある

*注：下書き時点ではなかったのですが、今なら[SDWebImage/SDWebImageSwiftUI](https://github.com/SDWebImage/SDWebImageSwiftUI)が使えるかもしれません:tada:

実装例については、[Apple Watch で GIF/APNG を使う (ライブラリ不使用)](https://qiita.com/yuki0n0/items/ccd8a04a9ac20a98b6e0) が参考になりました（ありがとうございました）
こちらでスムーズにアニメーションができました

## カウントダウンで遷移アニメーション😹
やりたかったのは「３ -> 2 -> 1 -> 画面遷移」でした
前述の「パラパラ画像もしくはAPNGをつかい、画面側でタイマーセットをし一定時間後に画面遷移する」という実装を行いましたが、カウントダウンし切る前に遷移してしまったり・・・とうまくいきませんでした

（こちらについて、シンプルで良い方法があればご教授いただけるとうれしいです😹）

## より動的なアニメーション
たとえば位置情報に基づいてアニメーションを移動・変更したい等のケースでは、[**Sprite Kit**](https://developer.apple.com/documentation/spritekit) を使うことになると思います
ただし、**watchOSで複雑なアニメーションをする際には、パフォーマンスに気を配るとよさそうです**
（**特にApple Watch Series 1などでは、最悪の場合、いつの間にかアプリがパージされます😹**）

### やるといいこと

#### スレッド管理
断然 [ReactiveX/RxSwift](https://github.com/ReactiveX/RxSwif) の利用が楽です

* アニメーションの移動についての計算はバックグラウンド処理
* 描画処理はメインスレッドで実行

という実装にしましょう

#### SKTextureAtlas の利用
公式ドキュメントは [About Texture Atlases](https://developer.apple.com/documentation/spritekit/sktextureatlas/about_texture_atlases)
SKTextureAtlasの利用により、メモリ使用量、描画パフォーマンスが向上するようです

まず、使用するテクスチャについては、Asset Catalog > + > New Sprite Atlas で専用フォルダをつくり、格納しましょう

**Asset Catalog**
<img width="361" alt="スクリーンショット 2020-02-07 18.32.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/2efc04b0-1da5-7ce8-7a62-667cbe70b3f3.png">

テクスチャアトラスからテクスチャを作成

```swift
static let TextureAtlasName = "Sprite Atlasのフォルダ名"

static let TextureAtlas = SKTextureAtlas(named: TextureAtlasName)

lazy var textures: [SKTexture] = {
    var textures = [SKTexture]()
    let numberOfTextures = TextureAtlas.textureNames.count - 1
    for index in 0...numberOfTextures {
        let name = String(format: "%@%d", TextureAtlasName, index)
        textures.append(TextureAtlas.textureNamed(name))
    }
    return textures
}()
```

テクスチャの切替のみ（要はパラパラ）でアニメーションする場合は以下のような実装

```swift
func animate() {
    let node = SKSpriteNode(..省略..) // アニメーション大賞のSKSpriteNodeオブジェクト生成
    let action = SKAction.animate(with: textures, timePerFrame: 0.03)
    let repeatAction = SKAction.repeatForever(action)

    node.run(repeatAction, withKey: key) // node: SKSpriteNode
}
```

#### 画像比率の適用
Sprite Kitを使う際にはAutoLayoutやCore Graphicsのような簡単な配置ができません
そのため、全端末でうつくしく表示するためには、画像比率に対してテクスチャサイズの変更等が必要かもしれません

たとえば、背景画像を画面いっぱいに表示したい場合は以下のように SKScene のサイズを指定します

```InterfaceController.swift
func setupScene() {
    let image: UIImage = BackgroundImage
    let size = contentFrame.size
    let portraitRatio = size.height / image.size.width
    let landscapeRatio = size.width / image.size.height
    let multiplier = min(portraitRatio, landscapeRatio)
    let sceneSize = CGSize(
        width: image.size.width * multiplier,
        height: image.size.height * multiplier)
    let scene = SKScene(size: sceneSize)
    scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    scene.scaleMode = .aspectFit
    sceneInterface.presentScene(scene)
}
```

以降、このScene上で配置するNodeのサイズは、`multiplier`を使った計算が必要になるでしょう

# まとめ
watchOS6より**Watch-only app**の開発が可能になりました。
まだアプリ数は少ないようですが、AppleWatch用のApp Storeもリリースされました。
これからwatchOSアプリ市場が盛り上がることを期待！

以上です🎉🎉🎉
ありがとうございました😽


# Thanks to

[SpriteKit Animations and Texture Atlases in Swift](https://www.raywenderlich.com/144-spritekit-animations-and-texture-atlases-in-swift)
[Apple Watch で GIF/APNG を使う (ライブラリ不使用)](https://qiita.com/yuki0n0/items/ccd8a04a9ac20a98b6e0)
[WatchKit のカスタムUI実現方法のまとめ](https://qiita.com/shu223/items/e704f70abc50da32ba38)