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)    
}