✏️

[SwiftUI]状態変数の肥大化、頼む痩せてくれ

2024/03/22に公開

導入部

現在iOSアプリの開発に取り組んでいるのですが困った…本当に困った…
各Viewに@State,@Bindingが多すぎて可読性が著しく低いよ〜〜〜!
これじゃあ、あとからコードを読む人が頭を抱えちゃうよ…
ということで、膨らみすぎてしまったStateをどう減らすかについて自分なりにざっと調べながらまとめていきます。

状態変数(@State)とは?

一言で表すと
データ(変数)とViewを紐付けて、変数の値を変えることで自動的にViewも更新されるという連動的な仕組み
です。便利ですね〜〜。最初知ったときはびっくりしました。。
でも気をつけてください。便利だ便利だと使いまくってたら変数が死ぬほど太ります。

https://qiita.com/Auk10/items/ce314fa974a8cdd7d699
こちらの記事が非常にわかりやすかったです

解決策

さあ、ではまとめていきます

状態の整理

例えば、以下のように状態を管理しているとします。

@State private var username: String = ""
@State private var password: String = ""
@State private var email: String = ""
@State private var phoneNumber: String = ""

この場合、これらの変数はユーザー情報という括りで一つのデータ構造にまとめることができます。

struct UserData {
    var username: String = ""
    var password: String = ""
    var email: String = ""
    var phoneNumber: String = ""
}

@State private var userData = UserData()

このように、関連する情報を一つにまとめて整理すると、チェックもしやすいですね。

@ObservedObject, @EnvironmentObject

https://qiita.com/Pusan/items/d79eef243e2ded33780e
複数のビューで同じ状態を使用する場合、@State,@Bindingの代わりに@ObservedObject,@EnvironmentObjectを使用して状態を共有できます。
例えば、ユーザー情報を表示するビューと、ユーザー情報を更新するフォームがある場合を考えてみましょう。

class UserDataModel: ObservableObject {
    @Published var username: String = ""
    @Published var email: String = ""
}

struct UserProfileView: View {
    @ObservedObject var userDataModel: UserDataModel
    
    var body: some View {
        VStack {
            Text("Username: \(userDataModel.username)")
            Text("Email: \(userDataModel.email)")
        }
    }
}

struct UserProfileEditView: View {
    @ObservedObject var userDataModel: UserDataModel
    
    var body: some View {
        TextField("Username", text: $userDataModel.username)
        TextField("Email", text: $userDataModel.email)
    }
}

う〜ん、気持ち良い〜
observedObjectは親viewから渡されるデータを保持して、environmentObjectはアプリ全体から渡されるデータを保持しています。
使い分けができれば、強力な武器ですね。

ViewModel

もうそもそもビューモデルが状態を管理して、ビューはそれを介して状態を取得および更新させます。

class UserViewModel: ObservableObject {
    @Published var username: String = ""
    @Published var email: String = ""
    
    func updateUser() {
        // ユーザー情報を更新する処理
    }
}

struct UserView: View {
    @ObservedObject var viewModel: UserViewModel
    
    var body: some View {
        VStack {
            TextField("Username", text: $viewModel.username)
            TextField("Email", text: $viewModel.email)
            
            Button("Update") {
                viewModel.updateUser()
            }
        }
    }
}

役割的にわざわざviewModelをclassで作る必要はあるのか…とも思いましたが、使う場面があるかもしれないので書いておきます!

appleの純正フレームワーク:Combine, observation

そして、これらは少し趣旨と離れてしまうかもしれませんが、データ追跡フレームワークである彼らです。
Combineは非常に高機能で、データ追跡だけでなく複雑なデータ加工や、非同期処理などまで幅広い役割を持っています。(使ったことはない)
仕事のしすぎなCombineに取って代わるのが、データ監視だけに注力してシンプルにしたものがObservationらしいです。(ふ〜ん)
手隙で少し戯れてみようと思います。
以下は公式ドキュメントです。

Combine(iOS13.0+)

https://developer.apple.com/documentation/combine

Observation(iOS17.0+)

https://developer.apple.com/documentation/observation

クールな男は急に去ります
では

Discussion