2

I have a list view of Task items. Every 10 seconds a timer triggers the function randomTasks that is responsible to modify the title and the description of each tasks in the list but the ListView doesn’t reflect the changes.I tried to use objectWillChange.send() but it doesn’t work.

I’ve noticed that when a task is modified with a different id the ListView updates and I can see the changes but when i’m in the detail view whenever randomTaskTitle is triggered the view is dismissed automatically.

class Task {
    var id: String = UUID().uuidString
    var title: String
    var description: String

    init(title: String, description: String) {
        self.title = title
        self.description = description
    }
}

import SwiftUI

struct TasksView: View {
    
    @EnvironmentObject var viewModel: TasksViewModel

        let timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect()
    
    var body: some View {
        
        NavigationView {
          List {
            ForEach(viewModel.tasks, id: \.id) { task in          
               NavigationLink(
                  destination: DetailView(task: task)
               ) {
                  VStack {
                    Text(task.title)
                    Text(task.description)
                  }
               }
          }
        }
        .onReceive(timer) { time in
            Task {
                await viewModel.randomTasks()
            }
        }
        
    }
}

struct TasksViewNew_Previews: PreviewProvider {
    static var previews: some View {
        TasksView()
    }
}
import Foundation

@MainActor
class TasksViewModel: ObservableObject {

    @Published var tasks = [Task]()

    init() {
      self.tasks = [Task(title: "TaskA", description: "DescriptionA"),
                    Task(title: "TaskB", description: "DescriptionB"),
                    Task(title: "TaskC", description: "DescriptionC"),
                    Task(title: "TaskD", description: "DescriptionD"),]
    }
    
    func randomTasks() {
      tasks.forEach { task in
        task.title = randomTitleTask()
        task.description = randomDescriptionTask()
      }
    }
    
}
Francois
  • 93
  • 5
  • 1
    Does this answer your question? [Why an ObservedObject array is not updated in my SwiftUI application?](https://stackoverflow.com/questions/57459727/why-an-observedobject-array-is-not-updated-in-my-swiftui-application) – Phil Dukhov Sep 06 '21 at 10:12

3 Answers3

2

try something simple like this, that works well for me:

@MainActor
class TasksViewModel: ObservableObject {
    
    @Published var tasks = [Task]()
    
    init() {
        self.tasks = [Task(title: "TaskA", description: "DescriptionA"),
                      Task(title: "TaskB", description: "DescriptionB"),
                      Task(title: "TaskC", description: "DescriptionC"),
                      Task(title: "TaskD", description: "DescriptionD"),]
    }
    
    func randomTasks() {
        tasks.forEach { task in
            task.title = randomTitleTask()
            task.description = randomDescriptionTask()
        }
        objectWillChange.send()   // <--- here
    }
}

struct TasksView: View {
    @EnvironmentObject var viewModel: TasksViewModel
    let timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect()
    
    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.tasks, id: \.id) { task in
                    NavigationLink(destination: DetailView(task: task)) {
                        VStack {
                            Text(task.title)
                            Text(task.description)
                        }
                    }
                }
            }
            .onReceive(timer) { time in
                // no need for Task here since randomTasks is not an async
                viewModel.randomTasks()  // <--- here
            }
        }
    }
}
1

The @Published array from classes does not know when your objects have changed. It can only keep track of when you add/remove them.

  1. You can tell your ObservableObject that something has changed explicitly by calling objectWillChange.send() before your modifications
objectWillChange.send()
tasks.forEach { task in
    task.title = UUID().uuidString
    task.description = UUID().uuidString
}
  1. Convert your class into a struct, and change items by index, like
tasks.indices { i in
    tasks[i].title = UUID().uuidString
    tasks[i].description = UUID().uuidString
}
  1. Check out ObservableArray from this answer
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
0

Instead of @ObservedObject use @StateObject