【SwiftUI】TabViewに立体感を出してみる

## はじめに
今回は、SwiftUIのTabViewに少し手を加えてちょっとおしゃれに移動するようにしたいと思います。


## 1.作ったもの
こちらです。画面遷移の際にViewがズームアウト→移動→ズームインで元に戻る。
といった動作をします。
※gifなのでカクカクしてますが、本来はスムーズに動作します。

このようにすることで移動動作に立体感がでて、背景色を統一することでシームレスに移動している感じが出ます。

![001.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233342/9705ebf0-36d1-1cf7-4b78-5278538e8d23.gif)


## 2.ベースの実装
### まずはベースとなるPageViewを作る
ベースは```.tabViewStyle(PageTabViewStyle())```で指定している通りPageViewとなります。

今回はサンプルとして四角形３つをPageViewで切り替えるViewとして定義しています。

現在のViewを示すtag番号をselectionで定義しており、
BACKボタン、NEXTボタンで更新して遷移するようにしています。




```swift:ContentView.swift
struct ContentView: View {
    @State private var selection : Int = 0
    
    var body: some View {
        VStack {
            TabView(selection: self.$selection) {
Rectangle()
                    .fill(Color(.white))
                    .border(Color(.black))
                    .tag(0)
                    .scaleEffect(self.ratio)
                
                Rectangle()
                    .fill(Color(.red))
                    .border(Color(.black))
                    .tag(1)
                    .scaleEffect(self.ratio)
                
                Rectangle()
                    .fill(Color(.blue))
                    .border(Color(.black))
                    .tag(2)
                    .scaleEffect(self.ratio)
            }
            .tabViewStyle(PageTabViewStyle())
            
            HStack {
                Button("BACK") {
                    if self.selection > 0 {
                            self.selection -= 1
                    }
                }
                
                Button("GO") {
                    if self.selection < 3 {
                            self.selection += 1
                    }
                }
            }
        }
        
    }
    
}
```

### 動作を見てみる
ご覧の通りベースはできました。ここからズームインアウトのアニメーションをつけていきます。
※解説用にデザインを簡略化して載せています。
![002.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233342/b27347ce-e0b6-c663-155f-f85e585a3c5f.gif)


## 3.ズームインアウト動作を実装する

### やりかた
さて、本題ですがViewの大きさを```.scaleEffect()```の値を操作することでズームインアウトのアニメーションを実現しています。

今回の流れとしては
　GOボタンを押す
　→scaleEffect(0.5)にしてViewを小さくする。
　→selectionを更新してViewを移動
　→secaleEffect(1.0)にして倍率を戻す。
となります。

![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233342/98f5516e-3e4f-f697-955c-9536a456cfa2.png)

### 実装

#### Viewの倍率を状態変数で定義する。

Viewの倍率を状態変数ratioとして定義します。
そして各Rectangle()のモディファイアに```.scaleEffect(self.ratio)```を設定します。
こうすることで```self.ratio```を変えることでスケールを自由に変えることができます。

また、```.transition(.slide)```と```.animation(.easeInOut)```を設定することで
遷移するときのアニメーションを設定しています。


```Swift
struct ContentView: View {
    @State private var ratio : CGFloat = 1.0
    
    var body: some View {
        VStack {
            TabView(selection: self.$selection) {
                Rectangle()
                    .scaleEffect(self.ratio)
                
                Rectangle()
                    .scaleEffect(self.ratio)
                
                Rectangle()
                    .scaleEffect(self.ratio)
            }
            .tabViewStyle(PageTabViewStyle())
            .transition(.slide)
            .animation(.easeInOut)
    }
}
```

#### ボタンのアクションに組み込む

先ほど定義して```self.ratio```をボタンを押した時に0.5、
１秒後にselectionを進めてその１秒後に```self.ratio```を1.0に戻しています。

”1秒後に”の部分の実装はTimerを使っています。
（Timerについては[こちら](https://qiita.com/w_kent/items/5fa337b1f22a8d7783b8)に記載していますので興味のある方はご覧ください。）

```Swift
              Button("BACK") {
                    if self.selection > 0 {
                        self.ratio = 0.5
                        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) {timer in
                            self.selection -= 1
                            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
                                self.ratio = 1.0
                            }
                        }
                    }
                }
                
                Button("GO") {
                    if self.selection < 3 {
                        self.ratio = 0.5
                        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) {timer in
                            self.selection += 1
                            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
                                self.ratio = 1.0
                            }
                        }
                    }
                }
```

#### 動作を見てみる
いい感じに完成しました！

あとはRectangleの部分を自分の好きなViewにすることでカスタマイズができるかと思います。
![004.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233342/ff6b97bf-f656-b53d-f784-e9ef37ea1c5b.gif)


## おわりに

作ってみて思いましたが、Viewの遷移に毎回２秒かかるのは結構ストレスなので、
メイン画面のメニューとしては使えないかなと。

アプリのチュートリアル画面とかで順番に見せるとかであれば使えそうです。

また、一番最初のデモで見せたように枠線をなくして背景色を全部同じにすると、
シームレスに移動しているように見えます。

そしてスケールする対象を変えるとこのようにボタンだけ正面に残しておく
といったこともできますので自由にカスタマイズしてみてください。

![005.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233342/18870d48-206f-d8c2-bec8-0c59a768e603.gif)


## おまけ
今回使用したコードの全文をこちらです。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var selection : Int = 0
    @State private var ratio : CGFloat = 1.0
    
    var body: some View {
        VStack {
            TabView(selection: self.$selection) {
                Rectangle()
                    .fill(Color(.white))
                    .border(Color(.black))
                    .tag(0)
                    .scaleEffect(self.ratio)
                
                Rectangle()
                    .fill(Color(.red))
                    .border(Color(.black))
                    .tag(1)
                    .scaleEffect(self.ratio)
                
                Rectangle()
                    .fill(Color(.blue))
                    .border(Color(.black))
                    .tag(2)
                    .scaleEffect(self.ratio)
            }
            .tabViewStyle(PageTabViewStyle())
            .transition(.slide)
            .animation(.easeInOut)

            HStack {
                Button("BACK") {
                    if self.selection > 0 {
                        self.ratio = 0.5
                        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) {timer in
                            self.selection -= 1
                            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
                                self.ratio = 1.0
                            }
                        }
                    }
                }
                
                Button("GO") {
                    if self.selection < 3 {
                        self.ratio = 0.5
                        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) {timer in
                            self.selection += 1
                            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
                                self.ratio = 1.0
                            }
                        }
                    }
                }
            }
        }        
    }   
}

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