LoginSignup
5
6

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

Posted at

初めに

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

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

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

方法

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

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

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

使用するには?

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

サンプル

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

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

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

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

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

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

@ObservedObject@StateObject

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

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

要は

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

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

サンプル

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

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

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

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

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

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

タブを切り替えてみる

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

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

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

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

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

etc…

最後に

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

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

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

参照

【SwiftUI】TabView の selection: Int を使う際の注意点(@ObservedObject と @StateObject の違い)

【SwiftUI】TabViewの使い方について解説

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6