0

So I am working on a view where I want to load state from an EnvironmentObject which acts something like a database.

I would like to achieve something like this:

class MyDB: ObservableObject {
    func getName(_ id: RecordID) -> String { ... }
    func getChildren(_ id: RecordID) -> [RecordID] { ... }
    var didUpdate: PassthroughSubject...

}

struct TreeView: View {
    let id: RecordID
    @EnvironmentObject var db: DB
    @State var name: String
    @State var children: [RecordID]

    func loadState() {
        self.name = db.getName(id)
        self.children = db. getChildren(id)
    }

    var body: some View {
        Text(self.name)
        List(self.children) { child in
            TreeView(id: child)
        }
        .onReceive(self.db.didUpdate) { _ in
            self.loadState()
        }
    }

}

So basically I would like to just pass the id of the node in the tree view to the child view, and then load the state from this environment object with the loadState function before the view is displayed.

Is there any way to achieve this? For instance, is there some kind of lifecycle function I could implement which will be called after the environment is bound?

Or for example can I implement loadState inside a custom init?

What would be the idiomatic way to handle this?

sak
  • 2,612
  • 24
  • 55
  • It seems strange that you're both giving the view knowledge of your persistence layer and then creating `@State` properties fetched from the DB. Either reference the properties straight from the DB without making them `@State` or inject those state properties when the view is created so it doesn't need knowledge of your persistence. – AdamPro13 Feb 24 '22 at 06:03
  • So the part which is missing from this picture is that I would like to reload the state based on update events which are coming from the db object. I have added an example to the code sample above. – sak Feb 24 '22 at 06:15
  • Your type is MyDB in TreeView right? – Tanvirgeek Feb 24 '22 at 09:14
  • Get rid of the State variables and use the Published wrapper in the ObservableObject and instead of those functions returning the values have then assign the Published variables. – lorem ipsum Feb 24 '22 at 10:27

2 Answers2

0

I have provided an explanation here if you want to check it out.

You will need to pass your MyDB instance using .environmentObject(myDBInstance) on a parent view, so all children views can read from the environment through @EnvironmentObject.

HunterLion
  • 3,496
  • 1
  • 6
  • 18
  • So I understand how to use EnvironmentObject - what I am struggling with is how to load data from the environment object – sak Feb 24 '22 at 06:34
  • Your code should work just fine, but a few improvements: a) add .onAppear { loadState() } after the List; add also .onChange(of: db) { _ in loadState() } - works if MyDB is Equatable, c) wrap your view elements in a VStack (I believe you didn’t put it just for this example) – HunterLion Feb 24 '22 at 06:48
0

Try using a different approach, such as the following code, where children and name are published var of MyDB, and the functions just load the data into those.

// for testing
struct RecordID: Identifiable {
    let id = UUID().uuidString
    var thing: String = ""
}

class MyDB: ObservableObject {
    @Published var didUpdate: Bool = false
    @Published var children: [RecordID] = []
    @Published var name: String = ""
    
    func getName(_ id: RecordID) {
        // ...
        name = "xxx"  // whatever
    }
    
    func getChildren(_ id: RecordID) {
        // ...
        children = [RecordID(), RecordID()] // whatever
    }
}

struct TreeView: View {
    @EnvironmentObject var db: MyDB
    @State var id: RecordID
    
    var body: some View {
        Text(db.name)
        List(db.children) { child in
          //  TreeView(id: child)  // <-- here recursive call, use OutlineGroup instead
          Text("\(child.id)")
        }
        .onReceive(db.$didUpdate) { _ in
            loadState()
        }
    }

    func loadState() {
        db.getName(id)
        db.getChildren(id)
    }
    
}

struct ContentView: View {
    @StateObject var myDB = MyDB()
    let recId = RecordID()
    
    var body: some View {
        TreeView(id: recId).environmentObject(myDB)
    }
}