skip to Main Content

I have the following code:

struct ContentView: View {
    @Environment(.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>

    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        Text("Item at (item.timestamp!, formatter: itemFormatter)")
                    } label: {
//                        Text(item.timestamp!, formatter: itemFormatter)
                        ItemCellView(model: ItemCellViewModel(item: item))
                    }
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
            Text("Select an item")
        }
    }

    private func addItem() {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()

            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error (nsError), (nsError.userInfo)")
            }
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)

            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error (nsError), (nsError.userInfo)")
            }
        }
    }
}

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .medium
    return formatter
}()

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}


struct ItemCellView: View {
    
    @StateObject var model:ItemCellViewModel
    
    var body: some View {
        
        Text(model.item.timestamp!, formatter: itemFormatter)
            .foregroundColor(.blue)
    }
    
}

class ItemCellViewModel: ObservableObject {
    
    @Published var item:Item
    
    init(item:Item) {
        self.item = item
    }
    
    deinit {
        print("ItemCellViewModel EDINIT (self)")
    }
    
}

It draws this:

enter image description here

PROBLEM:

ItemCellViewModel deinit is NOT called after I swipe to delete the item.
Can someone tell me why the ItemCellViewModel sticks around even after the ItemCellView is gone?

This is a simplified version of a codebase I am working in. I need that model to go away when the view is "deleted" by the user. Why is SwiftUI keeping ItemCellViewModel around??

2

Answers


  1. View is not deleted in a fact (just removed from visible area) because List caches some number of views (visible area + ~2) and StateObject is persistent storage of view which keeps its state. So observed behavior is by-design.

    Login or Signup to reply.
  2. Get rid of the view model object, in SwiftUI we use value types and the View struct is the view model that SwiftUI uses to create and update UIKit/AppKit views on our behalf. Learn this in SwiftUI Essentials WWDC 2019. Also you can’t nest an ObservableObject inside an ObservableObject either. To fix this change the ItemCellView to this:

    struct ItemCellView: View {
        
        @ObservedObject var item: Item
        
        var body: some View {
            
            Text(item.timestamp!, formatter: itemFormatter)
                .foregroundColor(.blue)
        }
        
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search