SwiftUI: Do czego służy @ObservedObject?

Opublikowano: 09.01.2024 - tagi: SwiftUI Swift Widok Stan Wrapper Klasa Obiekt

Czym jest @ObservedObject?

Ten wrapper jest bardzo podobny do @StateObject .

Przyjmuje typy referencyjne jak klasy.

Różnica między nimi jest taka, że @ObservedObject powinieneś użyć, w przypadku wymiany danych między widokami. Kiedy chcesz przekazać z jednego widoku typ referencyjny do innego.

Przykłady

Przykład 1

Model:

class Counter: ObservableObject {
    @Published var value: Int = 0
    
    func increment() {
        value += 1
    }
    
    func decrement() {
        value -= 1
    }
    
    func reset() {
        value = 0
    }
}

Widok (dziecko):

import SwiftUI

struct CounterView: View {
    @ObservedObject var counter: Counter
    
    var body: some View {
        VStack {
            Text("\(counter.value)")
                .font(.largeTitle)
            HStack {
                Button("+") {
                    counter.increment()
                }.padding()
                
                Button("-") {
                    counter.decrement()
                }.padding()
            }
        }
    }
}

Widok (rodzic):

import SwiftUI

struct ContentView: View {
    @StateObject private var counter = Counter()
		
    var body: some View {
        VStack {
            CounterView(counter: counter).padding()
            Button("RESET") {
                counter.reset()
            }
        }
    }
}

#Preview {
    ContentView()
}

Przykład 2: Uwaga na @ObservedObject!

Ponieważ @ObservedObject jest bardzo podobne do @StateObject często są ze sobą mylone. Niepoprawne użycie @ObservedObject może powodować nieoczekiwane wyniki.

Przykład:

import SwiftUI

struct CounterView: View {
    @ObservedObject var counter = Counter()
    
    var body: some View {
        VStack {
            Text("\(counter.value)")
                .font(.largeTitle)
            HStack {
                Button("+") {
                    counter.increment()
                }.padding()
                
                Button("-") {
                    counter.decrement()
                }.padding()
            }
        }
    }
}

W widoku CounterView tworzymy instancję obiektu klasy Counter. Do CounterView nie będzie przekazywany żaden parametr.

Teraz drugi widok:

import SwiftUI

struct ContentView: View {
    @State private var text: String = ""
    
    var body: some View {
        VStack {
            Text("Your text: \(text)")
            TextField("Type something", text: $text)
        }.padding()
				
        CounterView()
    }
}

#Preview {
    ContentView()   
}

Widok ContentView zawiera CounterView nic do niego nie jest przekazywane.

Jeśli użytkownik zmieni wartość licznika, a następnie wpisze jakiś tekst w pole tekstowe, to licznik... zostanie zresetowany do 0.

Dlaczego tak się dzieje?

Po pierwsze widok ContentView zawiera zmienną text. Jeśli wartość tej zmiennej zostanie zmieniona, stan widoku także ulegnie zmianie. To znaczy, że widok zostanie przerysowany.

Przerysowanie powoduje, że dla zmiennej oznaczonej jako @ObservedObject (w tym przypadku w widoku CounterView) poprzednia instancja klasy Counter zostanie zniszczona i stworzona nowa. To właśnie powoduje ten reset.

Można temu zaradzić. Wystarczy, że zamienisz w widoku CounterView linijkę:

@ObservedObject var counter = Counter()

na:

@StateObject var counter = Counter()

lub zaimplementujesz, w taki sposób, jak pokazane jest w pierwszym przykładzie.

Wystarczy trzymać się zasady: jeśli potrzebujesz przekazać do jakiegoś widoku typ referencyjny (na przykład obiekt klasy) użyj @ObservedObject.