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()
      }
    }
    
}
1

3 Answers 3

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
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I believe you should have objectWillChange.send() before tasks.forEach ...
yeah, you could put it before. In my basic tests, it seems to work just as well after.
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

Comments

0

Instead of @ObservedObject use @StateObject

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.