title: Publish background context Core Data changes in a SwiftUI view without blocking the UI
body: <p>After running a background-context core data task, Xcode displays the following purple runtime warning when the updates are published in a SwiftUI view:</p>
  <p><code>&quot;[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.&quot;</code></p>
  <p>Besides the <code>ContentView.swift</code> code below, I also added <code>container.viewContext.automaticallyMergesChangesFromParent = true</code> to <code>init</code> in the default <code>Persistence.swift</code> code.</p>
  <p>How can I publish the background changes on the main thread to fix the warning? (iOS 14, Swift 5)</p>
  <p><strong>Edit: I've changed the code below, in response to the first answer, to clarify that I'm looking for a solution that doesn't block the UI when a lot of changes are saved.</strong></p>
  <pre><code>struct PersistenceHelper {
      private let context: NSManagedObjectContext
      
      init(context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
          self.context = context
      }
      
      public func fetchItem() -&gt; [Item] {
          do {
              let request: NSFetchRequest&lt;Item&gt; = Item.fetchRequest()
              var items = try self.context.fetch(request)
              if items.isEmpty { // Create items if none exist
                  for _ in 0 ..&lt; 250_000 {
                      let item = Item(context: context)
                      item.timestamp = Date()
                      item.data = &quot;a&quot;
                  }
                  try! context.save()
                  items = try self.context.fetch(request)
              }
              return items
          } catch { assert(false) }
      }
  
      public func updateItemTimestamp(completionHandler: @escaping () -&gt; ()) {
          PersistenceController.shared.container.performBackgroundTask({ backgroundContext in
              let start = Date(), request: NSFetchRequest&lt;Item&gt; = Item.fetchRequest()
              do {
                  let items = try backgroundContext.fetch(request)
                  for item in items {
                      item.timestamp = Date()
                      item.data = item.data == &quot;a&quot; ? &quot;b&quot; : &quot;a&quot;
                  }
                  try backgroundContext.save() // Purple warning appears here
  
                  let interval = Double(Date().timeIntervalSince(start) * 1000) // Artificial two-second delay so cover view has time to appear
                  if interval &lt; 2000 { sleep(UInt32((2000 - interval) / 1000)) }
                  
                  completionHandler()
              } catch { assert(false) }
          })
      }
  }
  </code></pre>
  <pre><code>// A cover view with an animation that shouldn't be blocked when saving the background context changes
  struct CoverView: View {
      @State private var toggle = true
      var body: some View {
          Circle()
              .offset(x: toggle ? -15 : 15, y: 0)
              .frame(width: 10, height: 10)
              .animation(Animation.easeInOut(duration: 0.25).repeatForever(autoreverses: true))
              .onAppear { toggle.toggle() }
      }
  }
  </code></pre>
  <pre><code>struct ContentView: View {
      @State private var items: [Item] = []
      @State private var showingCoverView = false
      @State private var refresh = UUID()
  
      let persistence = PersistenceHelper()
      let formatter = DateFormatter()
      var didSave = NotificationCenter.default
          .publisher(for: .NSManagedObjectContextDidSave)
          // .receive(on: DispatchQuene.main) // Doesn't help
      
      var body: some View {
          ScrollView {
              LazyVStack {
                  Button(&quot;Update Timestamp&quot;) {
                      showingCoverView = true
                      persistence.updateItemTimestamp(completionHandler: { showingCoverView = false })
                  }
                  ForEach(items, id: \.self) { item in
                      Text(formatter.string(from: item.timestamp!) + &quot; &quot; + (item.data ?? &quot;&quot;))
                  }
              }
          }
          .id(refresh)
          .onAppear {
              formatter.dateFormat = &quot;HH:mm:ss&quot;
              items = persistence.fetchItem()
          }
          .onReceive(didSave) { _ in
              items = persistence.fetchItem()
          }
          .fullScreenCover(isPresented: $showingCoverView) {
              CoverView().onDisappear { refresh = UUID() }
          }
      }
  }
  </code></pre>