1

I have a List with several items, that open a DetailView which in turn holds a viewmodel. The viewmodel is supposed to have a service class that gets initialized when the detail view appears and should be deinitialized when navigating back.

However, the first problem is that in my example below, all 3 ViewModel instances are created at the same time (when ContentView is displayed) and never get released from memory (deinit is never called).

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView()) {
                    Text("Link")
                }
                NavigationLink(destination: DetailView()) {
                    Text("Link")
                }
                NavigationLink(destination: DetailView()) {
                    Text("Link")
                }
            }
        }
    }
}

struct DetailView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        Text("Hello \(viewModel.name)")
    }
}

class ViewModel: ObservableObject {

    @Published var name = "John"

    private let heavyClient = someHeavyService()

    init() { print("INIT VM") }

    deinit { print("DEINIT VM") }
}

This is probably just how SwiftUI works, but I have a hard time thinking of a way to handle class objects that are part of a detail view's state, but are not supposed to instantiate until the detail view actually appears. An example would be video conferencing app, with rooms, where the room client that establishes connections etc. should only get initialized when actually entering the room and deinitialize when leaving the room.

I'd appreciate any suggestions on how to mange this. should I initialize the heavyClient at onAppear or something similar?

Martin
  • 1,112
  • 1
  • 11
  • 31

1 Answers1

1

The problem is that DetailView() is getting initialized as part of the navigation link. One possible solution could be the LazyView from this post.

Implemented like so:

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}

And then wrap the DetailView() in the LazyView():

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: LazyView(DetailView())) {
                    Text("Link")
                }
                NavigationLink(destination: LazyView(DetailView())) {
                    Text("Link")
                }
                NavigationLink(destination: LazyView(DetailView())) {
                    Text("Link")
                }
            }
        }
    }
}

The only issue with this workaround is that there seems to always be one instance of ViewModel sitting around, though it's a large improvement.

Samuel-IH
  • 703
  • 4
  • 7