@Stateは値が変わるとViewを更新する仕組みのことです
以下のようにcount変数に値を入れると自動的にViewが更新されます。
import SwiftUI
struct CounterView3: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
@Stateはプリミティブ型だけではなくObject型でもOKです。
ちなみに@StateがSingletonクラスの値を参照した場合は以下のようになります。
class Singleton {
static let shared = Singleton()
var value: String = "Default Value"
}
struct ContentView3: View {
@State private var singletonValue = Singleton.shared.value
var body: some View {
VStack {
Text("Singleton Value: \(singletonValue)")
Button("Update Singleton Value") {
// Singletonクラスの更新だけではViewは反映されない
Singleton.shared.value = "New Value"
// これも必要
singletonValue = Singleton.shared.value
}
}
}
}
Singletonクラスの値を更新しただけではViewは更新されず、@Stateで定義した変数を更新する必要があります。これは2度手間でSingletonクラスを更新したらViewも更新されて欲しいですよね?
その場合は@Publishedの方を使います。
@Publishedとはアプリの状態に従ってViewを更新する仕組みのことです
以下のサンプルコードを見てください。ボタンをタップするとViewが更新されて数字が増えていきます。
class MyViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@ObservedObject var viewModel = MyViewModel()
var body: some View {
VStack {
Text("count: \(viewModel.count)")
Button(action: {
viewModel.increment()
}) {
Text("ボタン")
}
}
}
}
この
@Published var count: Int = 0
に値を入れるとViewが更新されるというわけです。@PublishはObservableObjectプロトコルに準拠したクラスのフィールドに付与できます。structには付与できません。
@Publishedは値の監視として使うこともできます
sinkを使えばcountの値を監視することができます。以下のサンプルコードではCounterをシングルトンクラスにして、CounterObserverがcountの値が変わる度に通知を受け取ることができます。
class Counter: ObservableObject {
public static let shared = Counter()
@Published var count: Int = 0
private init() {}
func increment() {
count += 1
}
}
class CounterObserver {
private var cancellable: AnyCancellable?
init(){
cancellable = Counter.shared.$count.sink { count in
// countの値が変わる度に通知が来てログ出力される.
print ("\(count)")
}
}
func cancel(){
// 監視をキャンセルする.
cancellable?.cancel()
}
}
監視をやめたい時はsinkの戻り値であるAnyCancellableをcancelすると監視を終了できます。
@ObservedObjectと@StateObjectの違い
以下のようなContentViewが親ViewでStateObjectCounterとObservedObjectCounterが子Viewの関係性であった場合
import SwiftUI
struct ContentView: View {
@State var counter = 0
var body: some View {
VStack(alignment: .leading, spacing: 50) {
HStack {
Text("counter: \(counter)")
Button("+") {
counter += 1
}
}
StateObjectCounter()
ObservedObjectCounter()
}
}
}
final class Counter: ObservableObject {
@Published var number = 0
}
struct StateObjectCounter: View {
@StateObject private var counter = Counter()
var body: some View {
HStack {
Text("StateObjectCounter: \(counter.number)")
Button("+") {
counter.number += 1
}
}
}
}
struct ObservedObjectCounter: View {
@ObservedObject private var counter = Counter2()
var body: some View {
HStack {
Text("OvservedObjectCounter: \(counter.number)")
Button("+") {
counter.number += 1
}
}
}
}
親Viewのincrementを行って再描画をするとObservedObjectCounterのカウンターは0になりStateObjectCounterのカウンターは何も変わりません。つまり親Viewの再描画をした時にアノテーションで指定したクラスのインスタンスが再生成されるかどうか、それが違いです。