SwiftUI: Do czego służy @StateObject?
Opublikowano: 06.01.2024 - tagi: SwiftUI Swift Widok Stan Protokół
Czym jest @StateObject?
Klasy w Swift są typami referencyjnymi. Jeśli potrzebujesz w swoim widoku mieć właściwość, która jest obiektem klasy powinieneś oznaczyć go jako @StateObject.
Właściwość oznaczona jako @StateObject sprawia, że ten obiekt będzie obserwowany. A właściwie to stan obiektu. Kiedy stan obiektu się zmieni, widok zostanie zaktualizowany. Dodatkowo używając tego wrappera, informujesz w ten sposób, że widok jest właścicielem tego obiektu.
Używając @StateObject masz pewność obiekt nie zostanie zniszczony, kiedy widok jest aktualizowany.
Obiekt, który jest oznaczony jako @StateObject musi być zgodny z protokołem ObservableObject.
class MyClass: ObservableObject {
...
}
Dzięki temu informujesz SwiftUI o ewentualnej zmianie stanu obiektu tej klasy w wyniku czego widok zostanie przerysowany.
To jeszcze nie wszystko. Potrzebujesz oznaczyć właściwość klasy jako @Published:
class MyClass: ObservableObject {
@Published var value = 0
}
Za pomocą @Published wskazujesz konkretnie, które właściwości klasy mają być monitorowane.
Przykład
Model:
class Counter: ObservableObject {
@Published var value: Int
init(value: Int) {
self.value = value
}
func increment() {
value += 1
}
func decrement() {
value -= 1
}
func reset() {
value = 0
}
}
Widok:
struct ContentView: View {
@StateObject private var counter = Counter(value: 0)
var body: some View {
VStack {
Text("\(counter.value)")
.font(.largeTitle)
HStack {
Button("+") {
counter.increment()
}.padding()
Button("-") {
counter.decrement()
}.padding()
}
Button("RESET") {
counter.reset()
}
}
}
}
#Preview {
ContentView()
}
Tworzenie obiektu w init
Patrząc na powyższy przykład, załóżmy, że potrzebujesz w widoku ContentView stworzyć instancję klasy Counter w funkcji init ContentView. Bo na przykład nie wiesz, jaka ma być wartość początkowa.
Jak można to zrobić?
Może tak?:
struct ContentView: View {
@StateObject private var counter: Counter
init(counter: Int) {
counter = Counter(value: counter)
}
}
#Preview {
ContentView(counter: 7)
}
Niestety nie. Edytor zgłosi błąd.
Można to jednak obejść:
struct ContentView: View {
@StateObject private var counter: Counter
init(counter: Int) {
_counter = StateObject(wrappedValue: Counter(value: counter))
}
}
#Preview {
ContentView(counter: 7)
}