Swift 22 SwiftUI状態Property, Observable, State, Environment オブジェクト

前の章にはSwiftUIはテータ主導型でアプリを開発するんだと説明し、そして使用者インタフェイス内のビューは基本データ変更に従う処理コードを作成しなくてもビューが更新されることを説明した。此れはデータと使用者インタフェイス内のビュー間にPublisherとSubscriberを構築してできます。

これを為にSwiftUIは状態プロパティ、Observableオブジェクト, Stateオブジェクト, Environmentオブジェクトを提供して、これらは*使用者インタフェイスの模様と動作を決める状態を*提供します。SwiftUIで使用者インタフェイスレイアウトを構成するビューはコードの内で直接に更新しない。その代わりに、**ビューとバインディングされた状態オブジェクトが時間の経過とともに変更**するとその状態によって自動的にビューが更新されます。

その章にはこの四つのオプションについて説明するしいつ使うべきかについて説明します。以降で出る23章'Swift例題チュートリアル`と27章'ObservableオブジェクトとEnvironmentオブジェクトチュートリアル'では実践的な例を通してどうやって使うのかを見てみます。

# 22.1 状態プロパティ 
>> property:
? a thing or things belonging to someone; possessions collectively
~  Latin proprietas, from proprius ‘one's own, particular’ (see proper).
proper:
? denoting something that is truly what it is said or regarded to be; genuine.
~ from Old French propre, from Latin proprius ‘one's own, special’.

此れはステートに対する一番基本的な形で、ビューレイアウトの現在状態を保存するだけで使用されます。ステートプロパティはStringとかInt値など簡単なテータターフを保存する為に使用されると@Stateプロパティラッパーを使って宣言されます。
![Screenshot 2023-11-09 at 11.05.32 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3513806/5a6b84dc-442f-f2b9-6361-1740cf6760c6.png)

```swift
struct ContentView: View {
    @State private var wifiEnabled = true
    @State private var userName = ""
    var body: some View {
    .
    .
```
ステート値は該当ビューに属するのせいでprivateプロパティで宣言するべきです。

ステートプロパティ値が変更されることはそのプロパティが宣言されたビュー階層構造をも一度レンダリングが必要であるSwiftUIの信号です。つまり、その階層構造中である全てのビューを早く再生性して表示する必要があります。結局、*そのプロパティで依存する全てのビューはどんな方法でも最新値が反映する様に更新されます*。

ステートプロパティを宣言するとレイアウトにあるビューとバインディングすることができます。バインディングされているビューで何らかの変更が起きなら、該当ステートプロパティに自動的に反映されます。例え、トグルビューと上で宣言したブリアンタープのWifiEnabledプロパティかせバインディングされて使用者がトグルビューを操作するとSwiftUIは新しいトグル設定値でステートプロパティを自動で更新するです。


ステートプロパティとバインディングはプロパティ名前の前で'$'を貼ればいい次の例題でTextFieldビューは使用者が入力されたテキストを保存をしよする為にuserNameと言うステートプロパティとバインディングされます。
```swift
struct ContentView: View {
    @State private var wifiEnabled = true
    @State private var userName = ""
    var body: some View {
        VStack{
            TextField("Enter user name", text:$userName)
        }
    }
}
```
使用者がTextFieldで入力されてすると、バインディングは現在のテキストをuserNameプロパティで保存することです。この状態変化が起きる度にビュー階層構造はSwiftUIによって再レンダリングされます。勿論、**ステートプロパティで値を保存する事はかた一方向プロセス**です前でも説明するとステートが変わるとレイアウトにある方のビューも変更されます。次の例題でTextビューは入力された使用者の名前を反映するために更新されなければならないです。この作業はTextビューをためのコンテントでuserNameステイトプロパティを宣言すればいいです。
```swift
var body: some View {
        VStack{
            TextField("Enter user name", text:$userName)
            Text(userName)
        }
    }
```
使用者がテキストを入力するとTextビューは使用者の入力を反映するために自動的で更新される事です。参照する時のuserNameでは「$」を使いません。

同じ様に、前に説明したToggleビューとwifiEnabledステートプロパティ間のバインディングは次の様に実装することができます。
```swift
    var body: some View {
        VStack{
            Toggle(isOn: $wifiEnabled){
                Text("Enabled Wi-Fie")
            }
            TextField("Enter user name", text:$userName)
            Text(userName)
            Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
        }
    }
}
```
# 22.2 Stateバインディング
状態プロパティは宣言されたビューとその下位ビューに対する現在値でするでもどんなビューが一つ以上の下位ビューを持っているか、同じステイトプロパティについて接近しなければならない場合が発生します。例えば、前の例題にあるwifiイメージビューが下位ビューで分離される状況を見てみましよ。
```swift
.
.
 VStack{
            Toggle(isOn: $wifiEnabled){
                Text("Enabled Wi-Fie")
            }
            TextField("Enter user name", text:$userName)
            WifiImageView()
            
        }
    }
    
    struct WifiImageView: View{
        var body: some View{
            Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
        }
    }
```
wifiImageView下位ビューは相変わらずwifiEnabledステートプロパティで接近しなければなければならない。でも分離された下位ビューの要素のImageビューはメインビューの範囲外です。言い直して、WifiImageView立場で見るとWifiImageViewであるwifiEnabledプロパティは正義されてない変数である。
この問題は次の様に@Bindingプロパティラッパーをを利用してプロパティを宣言すればいいです。

# 22.3 Observableオブジェクト
*ステイトプロパティはビューの状態を保存する方法を提供するし*　**該当ビューだけで使用することができます**。つまり、下位ビューがではないかStateバインディングが実装されではない他のビューには接近できません。状態プロパティは一時的なもまののです親ビューが消えたらその状態も消えてしまいます。反面、Observableオブジェクトは複数の他のビューたちが外部で接近できる時継続的なデータを表現するために使用されます。

ObservableオブジェクトはObservalbleOjbectプロトコルを従うクラスとストラクト形を取ります。此れは、データの特性と出典によってアプリケーションによって異なりますが 、一般的には時間によって変更される一つ以上のデータ値を集めて管理する役目を担当します。また、此れは、ターマとかnotificationなどとイベントを処理するために使用される事もできます。

此れは、publised propertyとしデータ値を投稿します。その後で、observerオブジェクトは投稿者をSubscribeして投稿されたプロパティが変更される時に更新をもらえます。前で説明した状態プロパティ様に、投稿されたプロパティのバインディングを通じてObservableオブジェクトで保存されたでいたが変更されたことを反映するためにSwiftUIビューは自動に更新されるだろう。

Combineフレームワークで入れているObservableオブジェクトはPublisherとsubscriberの関係を簡単に構築する様にIOS13で導入されました。
Combineフレームワークは複数のPublisherを一つのストリムで併合することから投稿されたデータをSubscriberの要求に当て変更するまで多様な作業を実行するカスタム投稿者構築フレッホムを提供します。また、最初Publisherと最初subscriberの間の複雑な水準の連鎖データ処理作業も実装できます。でも、一般的には内蔵された投稿者ターフたちの一つレバ十分だと思います。実際にObservableオブジェクトの投稿されたプロパティを実装する一番優しい方法はプロパティを宣言する時に@Publishedプロパティラッパを使う事です。このラッパはラッパプロパティ値が変更される度に全てのsubscriberに更新をお伝えてします。

次のストラクトは宣言は二つの投稿されたプロパティを持つ簡単なObservableオブジェクト宣言を見せます。

SubscriberはObservableオブジェクトをSubscribeために@ObservedObjectまたは@StateObject プロパティラッパを使用します。購読するとそのビュー及び下位ビューがステイトプロパティで使用されたものと同じ方法で投稿されたプロパティに接近ことになります。前のDemoDataクラスのインタフェイスを購読する様に設計された簡単なSwiftUIビューはクラスのインタフェイスを購読する様に設計された簡単なSwiftUIviewは以下の通りです。
```swift
struct ContentView: View {
    
 @ObservedObject var demoData : DemoData = DemoData()
    var body: some View {
        Text("\(demoData.currentUser), you are user number \(demoData.userCount)")
        }
}
struct ContentView_PreViews: PreviewProvider {
    static var previews: some View {
        ContentView(demoData: DemoData())
    }
}
```
# 22.4 Stateオブジェクト
IOS 14で導入されたstate object @StateObjectは @ObservedObjectラッパの大安です。ステイトオブジェクトと観察されるオブジェクト主な違いは観察されるオブジェクト参照はこれを宣言されたビューが所有していないので使用される間にSwiftUIシステムによって破壊されるとか再生性される危険がある事です。

@ObservedObject代わりに@StateObjectを使用すると参照を宣言したビューが参照を所有しているので、該当参照が宣言されたローカルビューとかサーブビューでいよって続けて必要としている間にはSwiftUIによって破壊されません。

@ObservedObjectを使用する特別な必要がなければ、一般的に状態オブジェクトを使用してObservableオブジェクトを購読する方がいいです。構文側面から見ると　@ObservedObjectと@StateObject全ては完全に相互交換できます。

```swift
import SwiftUI


struct ContentView: View {
    
  @StateObject var demoData : DemoData = DemoData()
    
    var body: some View {
        Text("\(demoData.currentUser), you are user number \(demoData.userCount)")
        }
}
```
# 22.5 Environmentオブジェクト
購読されるオブジェクトは特定ステイトがアフリの内のいくつかのSwiftUIビューによって使用される場合に最も適している。しかしどのビューから他のビューでnavigationするに移動されるビューでも同一ななサブスクオブジェクトで接近しなきゃら、移動する時に対象ビューでサブスクされるオブジェクトに対するリファレンスを伝達するべきだ
```swift
struct ContentView: View {
    @StateObject var demoData : DemoData = DemoData()
 
    var body: some View {
      
          
        NavigationLink(destination: secondView(demoData)){Text ("NExtScreen")}
        Text("\(demoData.currentUser), you are user number \(demoData.userCount)")
        }
```
前のコード宣言部でNavigationLinkはSecondViewと言う他の名前の他のビューで移動する為に使用者されるし、demoDataオブジェクトに対するいリファレンスを伝達します。

この方法は複数の状況に使用されることができますが、アフリ内の複数のビューが同一な購読オブジェクトで接近しなきゃならない場合に複雑にな事があります。こんな状況にはEnvironmentオブジェクトを使用する事がもっと合理的である。

EnvironmentオブジェクトはObservableオブジェクトと同じ方法で宣言されます。つまり、必ずObservableObjectプロトコルを従うべきで、適当なプロパティが投稿されるべきですす。だが、大事な違いはこのオブジェクトはSwiftUI環境で保存されてビューからビューまで伝達する必要ないで全てのサーブビューが接近される事です。

次の Observableオブジェクト宣言を見てみよう
```swift
class SpeedSetting: ObservableObject{
    @Published var speed = 0.0
}
```
Environmentオブジェクトを購読しなきゃならないビューは@StateObjectまたは@ObservedObjectラッパ代わりに@EnvironmentObjectプロパティラッパ使用してオブジェクトをレファレンスするだけでいいです。例え、次のビューは全て同一なSpeedSettingデータで接近するべきです。
![Screenshot 2023-11-10 at 4.10.36 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3513806/1329af62-265b-9f1a-bc1f-b36145ff28de.png)
このこの時点で私たちにはSpeedSettingと言うObservableオブジェクトと該当タープのEnvironmentオブジェクトをレファレンスする二つのビューがあります。でも、またObservableオブジェクトのインタフェイスをイニットしなかった。この作業を実行するロジカル位置は前でみる下位ビューの上位ビューです。次の例題で二つのビューContenViewの下位ビューです。
```swift
struct ContentView: View {
    let speedsetting = SpeedSetting()
    
    var body: some View {
        VStack{
            SpeedControlView()
            SpeedDisplayView()
        }
    }
}
```
でも、この時点でアプリを実行すると
![Screenshot 2023-11-10 at 4.36.29 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3513806/dc1717a8-7d29-1d67-e9cf-7aac030cf61d.png)
ここで問題はContentView宣言内で観察できるオブジェクトのインタフェイスを生成したんですけど、ビュー階層構造ではまだ挿入していない事です。此れは、Observableオブジェクトインタフェイスを通じて伝達するenvironmentObject()修正者を使用して次の様にします。
```swift
struct ContentView: View {
    let speedsetting = SpeedSetting()
    
    var body: some View {
        VStack{
            SpeedControlView()
            SpeedDisplayView()
        }
        .environmentObject(speedsetting)
    }
}
```
この様な段階が実行されるとオブジェクトは観察されるオブジェクトと同じ様に動作しますが、ビュー階層構造を通じて伝達されてなくコンテントビューの全てのサーブビューで接近することの点だけ違います。SpeedControlViewのスライダを動けば、現在速度設定を反映する様にSpeedDisplayViewのTextビューが更新されます。これにより二つのビューが同一なEnvironmentオブジェクトに接近することを見せてあげます。

# 要約
SwiftUIは使用者インタフェイスとアフリのロジクでデータバインディングする方法三つ提供します。ステイトプロパティは使用者インタフェイスレイアウト内のビューステートを保存する為に使用されて、現在コンテントビューに対するものです。この値は一時的なものなので該当ビューが消えれば値も消えます。

使用者インタフェイス外であるとアプリ内のSwiftUIビューストラットのサーブビューだけで必要なテータはObservableオブジェクトプロパティを使用しなきゃならないです。この方法使用するとデータを表示するクラスとかストラっとはObservableObjectプロトコルを従う必要があり、ビューとバインダーされるプロパティは@Publishedプロパティラップを使用されて宣言しなきゃならないです。ビュー宣言部でObservableオブジェクトプロパティとバインディングするとプロパティは@ObservedObjectまたは@StateObjectプロパティラップァを使用者するべきです。・ほとんどは@StateObjectが好まれるオプションです。

使用者インタフェイス外にあり複数のビューが接近するべきデータの場合にはEnvironmentオブジェクトが最後の解決築になります。Observableオブジェクトと同じ様に宣言されると、Environmentオブジェクトバインディングには@EnvironmentObjectプロパティラップぁを使用者してSwiftUIビュー中で宣言されます。サーブビューで接近される前にenvironmentObject()修正者を使用してビュー階層構造で挿入する前にEnvrionmentオブジェクトもイニットが必要です。