【Swift】タブを切り替えてもデータを保持したい

## 初めに
スマホ下のタブをポチポチ切り替えるたびに
画面描画に必要なデータを用意するのって非効率だと思いません？

今回は、一度表示させたデータを一時的に保持し、
2回目以降同じ画面を表示させた時に、
保持しといたデータを再利用したいと思います。

寄り道多めですが、ご了承ください m(_ _)m

## 方法

- `ObservableObject`プロトコルに準拠した`class`を定義する
- `@StateObject`を使ってデータを保持する
- 保持したデータを使って画面を描画する

## `ObservableObject`プロトコルに準拠した`class`???

要はクラスのプロパティの変更を検知して、ビューを自動的に更新できます。

使用するには？

- **`ObservableObject`をクラス宣言で指定する**
- **検知したいプロパティに`Published`を付ける**

### サンプル

```swift
class Counter: ObservableObject {
    @Published var count: Int = 0
}
```

シンプルですね。(^^)

せっかくクラスを定義したので
**ボタンを押すとカウントが増える** ロジックを書いてみましょう。

```swift
import SwiftUI

// ObservableObjectプロトコルに準拠したclass
class Counter: ObservableObject {
    @Published var count: Int = 0
}

struct ObservableView: View {
    // 定義したクラスを読み込む
    @StateObject private var counter = Counter()

    var body: some View {
        VStack {
            Text("Count: \\(counter.count)")
                .font(.largeTitle)
                .padding()

            Button(action: {
                counter.count += 1
            }) {
                Text("Increment")
                    .font(.headline)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
    }
}

```

### デモ

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2508607/84628f74-2b84-76e2-9477-b6e2ce8afa1c.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2508607/84628f74-2b84-76e2-9477-b6e2ce8afa1c.gif)

1画面で変更を検知するだけなら`ObservableObject`を使う必要もないです。
親のビューで`@State`を使ってデータを持っておけばOKです。

しかし、実際のアプリはもっと複雑で、
複数の画面でのデータの持ち方を考えなければなりません。

## `@ObservedObject` と `@StateObject`

 `@StateObject` を調べているとハッピーセットのように`@ObservedObject` の記事もヒットします。この両者の違いについて整理したいと思います。

私もここの理解不足で苦しみました ＿|￣|○

**要は**

**`@ObservedObject` は親ビューのプロパティに更新があるとリセットされるのに対して、`@StateObject`は値を保持したままになります。**

実際にサンプルを使って見てみましょう。

### サンプル

```swift
import SwiftUI

// ObservableObjectプロトコルに準拠したclass
class ViewModel: ObservableObject {
    @Published var counter = 0
}

struct ParentView: View {
    @State var counter = 0

    var body: some View {
        VStack {
            VStack {
                Button(action: {
                    counter += 1
                }) {
                    Text("親ボタン")
                        .font(.headline)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
                Text("親ViewのCount：\(counter)")
            }
            .padding(.bottom, 10)

            // 子を呼び出し
            ChildView()
        }
    }
}

struct ChildView: View {
    // 定義したクラスを読み込む
    @ObservedObject var observedViewModel = ViewModel()
    @StateObject var stateViewModel = ViewModel()

    var body: some View {
        VStack {
            VStack {
                Button(action: {
                    observedViewModel.counter += 1

                }) {
                    Text("@ObservedObjectボタン")
                        .font(.headline)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
                Text("@ObservedObjectのCount：\(observedViewModel.counter)")
            }
            .padding(.bottom, 10)

            VStack {
                Button(action: {
                    stateViewModel.counter += 1
                }) {
                    Text("stateViewModelボタン")
                        .font(.headline)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
                Text("@StateObjectのCount：\(stateViewModel.counter)")
            }
        }
    }
}
```

### デモ
![画面収録-2023-05-23-17.24.12_out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2508607/c1c7cd38-4ddc-5b92-8908-262550f1c1c7.gif)


デモの通り **`@ObservedObject` は親ビューのプロパティに更新があるとリセットされるのに対して、`@StateObject`は値を保持できます。**

どちらかを採用するかは使いどころによります。

データの保持が必要ない場合は `@ObservedObject` を使う。
親ビューが変更されても、子ビューのデータを破棄したくない場合は`@StateObject`を使う

といった感じでしょうか。

このデータ保持の仕組みを使って、タブを切り替えてもデータが保持されるように作ってみましょう。

## タブを切り替えてみる

タブの生成には `TabView`を使用しています。

```swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView{
            FirstView()
                .tabItem { Text("ファーストビュー") }
            SecondView()
                .tabItem { Text("セカンドビュー") }
        }
    }
}

// ObservableObjectプロトコルに準拠したclass
class firstViewModel: ObservableObject {
    @Published var counter = 0
}

struct FirstView: View {
    @StateObject var stateViewModel = firstViewModel()
    var body: some View {
        VStack {
            Text("ファーストビュー")
            Button(action: {
                stateViewModel.counter += 1
            }) {
                Text("カウント")
                    .font(.headline)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            Text("Count：\(stateViewModel.counter)")
        }
    }
}

// ObservableObjectプロトコルに準拠したclass
class secondViewModel: ObservableObject {
    @Published var counter = 0
}

struct SecondView: View {
    @StateObject var stateViewModel = secondViewModel()
    var body: some View {
        VStack {
            Text("セカンドビュー")
            Button(action: {
                stateViewModel.counter += 1
            }) {
                Text("カウント")
                    .font(.headline)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            Text("Count：\(stateViewModel.counter)")
        }
    }
}
```

### デモ

![画面収録-2023-05-21-11.51.04_out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2508607/5c927f6f-3b1a-d6bd-35c5-4b4839516b05.gif)


カウント数がビューを切り替えても保持されている様子がわかります。

今回はシンプルにカウント数を保持するロジックを書いてみましたが、
もっと便利に使えそうな気がします。

- スクロール数を保持
- API通信で一度取得したデータを保持して使い回す

etc…

## 最後に

ここまで見ていただきありがとうございます。m(_ _)m

`@ObservedObject` やら `@StateObject`　やら`@State` といろんな概念が出てくるので`Swift` って大変です。(^^;;

めげずに少しづつ使いこなせるようになりたいです。

## 参照

[【SwiftUI】TabView の selection: Int を使う際の注意点（@ObservedObject と @StateObject の違い）](https://www.yururiwork.net/archives/1582)

[【SwiftUI】TabViewの使い方について解説](https://ios-docs.dev/swiftui-tabview/)