SwiftUIのTabViewの問題に自作TabView(SwiftUI製)で対処する

# 追記(20200623)
XCode12 + iOS14 からTabViewの挙動が変更されて、タブを切り替えても前のViewが保持されるようになりましたのでiOS14以降ならこの記事はあまり意味がなくなりましたw

# SwiftUIのTabViewの問題に自作TabView(SwiftUI製)で対処する

　

SwiftUIのTabViewには、Tabを切り替えると切り替えた先のタブを再生成するためスクロールした位置や状態を保持しておくことができませんでした。

それに対処するために、今回自作TabViewを作成しましたので、その tipsを共有します。

作成したTabViewを見たい方はこちらから！
https://github.com/tsuzukihashi/SwiftUI_CustomTabBar

また、UIKitのUITabBarControllerを Representaぶる方法もあります。そっちの方がiOS的には優しいかもしてません...

こっちのメリットは、タブ切り替えのアニメーションがいじりやすいのと、オールSwiftUIのモチベぐらいでしょうか...

間違いやアドバイスがあればじゃんじゃんコメントください！お願い致します🙇‍♂️

Twitter やっています！
https://twitter.com/tsuzuki817

色々アプリをリリースしているので一度見てみてください🙇‍♂️
https://apps.apple.com/jp/developer/ryo-tsudukihashi/id1320583602?l

## 現状の問題を把握

先ほど説明しましたが、改めてGIFアニメーション載せておきます。
![2tdvx-fuo22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/4a956890-b748-61b7-3014-fd827c8ce633.gif)


## 自作TabViewへの道

大まかな流れは以下のようになっています。

+ ステップ１
  ルートのViewで現在選択しているcurrentPageを保持する 
+ ステップ２
  ルートのViewでタブで遷移したいViewをZStackで並べる
+ ステップ３
  ステップ２で配置したViewのopacityをcurrentPageによって変える
+ ステップ４
  タブもどきに設置したボタンでcurrentPageを切り替える

説明しやすいように、シンプルなアプリを作成します。

##  表示させたいView達

変化が分かりやすいように縦に多くスクロールできるViewを作成しています。

Page2では天下のイラストや様の画像を少しばかりお借りしております。

```swift
import SwiftUI
struct Page1: View {
    var body: some View {
        NavigationView {
            VStack {
                List(0..<30) { i in
                    Text("\(i)列目")
                }
            }.navigationBarTitle("1ページ目")
        }
    }
}

struct Page2: View {
    var body: some View {
        NavigationView {
            ScrollView(.vertical, showsIndicators: false) {
                ForEach(1..<8) { i in
                    Image("irasutoya\(i)")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                }
            }.navigationBarTitle("2ページ目")
        }
    }
}

struct Page3: View {
    var body: some View {
        NavigationView {
            VStack {
                List(0..<30) { i in
                    Text("\(i)列目")
                }
            }.navigationBarTitle("3ページ目")
        }
    }
}

```

## ルートのView

このViewで@State で currentPageを保持しておきます。
そのcurrentPageの値を見てViewのopacityを変更して、表示・非表示を切り替えています。

また、ここでoffsetをいじっているのですが、これはアニメーションのために利用しています。

そのためアニメーションを利用しない方は、なくてもおkです！
（.rotation3DEffectとか使ったらリッチになって面白そうだったのですが、navigationbar が少しおかしくなりました；；)

```swift
import SwiftUI

struct ContentView: View {
    @State  var currentPage = 1
    
    var body: some View {
        ZStack {
            Page1()
                .opacity(currentPage == 1 ? 1 : 0)
                .offset(x : currentPage == 1 ? 0 : 100)
                .animation(Animation.spring())
            
            Page2()
                .opacity(currentPage == 2 ? 1 : 0)
                .offset(x : currentPage == 2 ? 0 : 100)
                .animation(Animation.spring())
            
            Page3()
                .opacity(currentPage == 3 ? 1 : 0)
                .offset(x : currentPage == 3 ? 0 : 100)
                .animation(Animation.spring())
            
            MyTabView(currentPage: $currentPage)
        }
    }
}

```

## 肝心の自作TabView

 @BindingでcurrentPageをルートViewから受け取り、設置するボタンでその値を変更します。

VStack, Spacer() , ZStack, Rectangleを駆使して擬似タブを作成します。

.edgesIgnoringSafeArea(.bottom)を使って、セーフエリアまで覆うのを忘れないようにしておけば、後は結構自由です。

```swift
struct MyTabView: View {
    @Binding var currentPage: Int
    var width = UIScreen.main.bounds.width
    var body: some View {
        VStack {
            Spacer()
            ZStack {
                Rectangle()
                    .foregroundColor(Color.gray)
                    .frame(width: width, height: 88)
                
                HStack(spacing: 88) {
                    Button(action: {
                        self.currentPage = 1
                    }) {
                        VStack {
                            Image(systemName: "flame")
                                .foregroundColor(Color.red)
                                .font(.system(size: 32, weight: .bold, design: .rounded))
                            
                        }
                    }.padding(.bottom, 24)
                    
                    Button(action: {
                        self.currentPage = 2
                    }) {
                        VStack {
                            Image(systemName: "bolt")
                                .foregroundColor(Color.yellow)
                                .font(.system(size: 32, weight: .bold, design: .rounded))
                            
                        }
                    }.padding(.bottom, 24)
                    
                    Button(action: {
                        self.currentPage = 3
                    }) {
                        VStack {
                            Image(systemName: "leaf.arrow.circlepath")
                                .foregroundColor(Color.green)
                                .font(.system(size: 32, weight: .bold, design: .rounded))
                            
                        }
                    }.padding(.bottom, 24)
                    
                }
            }
        }.edgesIgnoringSafeArea(.bottom)
    }
}
```

## できたもの
![6t67u-qqz8z.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/e69124f8-c6e3-280c-fe2a-ed54b2183dea.gif)

タブを切り替えてもViewは作り直されず、遷移前の状態を保持することができました！

## 最終的な着地点

以上で実装は終わりです！

SwiftUIは色々不便なことも多いですが、楽しいですね！

そのうちTabViewの問題もApple様が直してくれそうですが、一時的な場しのぎとしてどうでしょうか

最後にもう一度作成したリポジトリです!
https://github.com/tsuzukihashi/SwiftUI_CustomTabBar

閲覧ありがとうございましたmm