Kevin
Kevin

Reputation: 1223

SwiftUI Observable Object inside Environment Object does not update view

I recently came across an issue where I didn't find a way to work around it. If I place an observable object inside an environment object, my views don't update when a variable is changing. This is my setup:

inside SceneDelegate I decalre the parent view with environmentObject:

 let parent = ParentView().environmentObject(Data())

This is the environment Object:

class Data: ObservableObject {
    enum Page {
        case pageOne
    }
    var page: Page = .pageOne
    var person = Person()
}

now all my views are linked to the parent view:

struct ParentView: View {

     @EnvironmentObject var data: Data

     var body: some View {
         VStack {
            if self.data.page == .pageOne {
                PageOne()
            } else {
                Text("No pages")
            }
        }
    }
}

I have a class (I called it "Person"):

 import Foundation
 import SwiftUI
 import Combine

 class Person: ObservableObject {

     let objectWillChange = PassthroughSubject<Person,Never>()

     @Published var age = 30 { didSet { objectWillChange.send(self)} }

     func birthday() {
         self.age += 1
         print(age)
     }

 }

And this is where my problem occurs:

 struct PageOne: View {

     @EnvironmentObject var data: Data

     var body: some View {
         Text("\(data.person.age)").onTapGesture {
             self.data.person.birthday()
         }
     }
 }

When I tap the text, the age of the person is increasing but the view does not update. I think I don't even understand why this is happening. I thought I knew how it works but apparently I don't.

Upvotes: 4

Views: 2072

Answers (1)

pawello2222
pawello2222

Reputation: 54426

It looks like in your case Person doesn't have to be a class.

You can try this instead:

class Data: ObservableObject {
    enum Page {
        case pageOne
    }

    var page: Page = .pageOne

    @Published var person = Person() {
        didSet {
            objectWillChange.send()
        }
    }
}
struct Person {
    var age = 30

    mutating func birthday() {
        self.age += 1
        print(age)
    }
}

If you need Person to remain a class you can make it @Published:

class Data: ObservableObject {
    enum Page {
        case pageOne
    }

    var page: Page = .pageOne
    @Published var person = Person() // <- add `@Published`
}

and remove unnecessary objectWillChange code from Person:

class Person: ObservableObject {
    @Published var age = 30

    func birthday() {
        self.age += 1
        print(age)
    }
}

Then use Person as an @ObservedObject in the PageOne view:

struct ContentView: View {
    @EnvironmentObject var data: Data

    var body: some View {
        VStack {
            if self.data.page == .pageOne {
                PageOne(person: data.person) // pass `person` object
            } else {
                Text("No pages")
            }
        }
    }
}

struct PageOne: View {
    @ObservedObject var person: Person // observe `person` directly

    var body: some View {
        Text("\(person.age)").onTapGesture {
            self.person.birthday()
        }
    }
}

Upvotes: 2

Related Questions