If you've built an iOS Share Extension that writes to a shared Core Data store, you've probably hit this wall: the data saves fine, but your main app's SwiftUI list doesn't update until you kill and relaunch it.
I hit this while building a bookmarking app during a live stream. I was using Claude Code to build the whole thing. Got the Share Extension working, got it saving to Core Data through an App Group, deployed to my phone, shared a YouTube link, and... the app just sat there showing the old data. Kill the app, relaunch, there it is. Cool. Very helpful.
I spent the next few hours figuring out why, and I'm writing it up so you don't have to.
The Setup
Nothing exotic going on here. A main app and a Share Extension both access the same Core Data SQLite store through an App Group container:
Main App ←→ App Group Container (ShareSaver.sqlite) ←→ Share Extension
Both targets share a PersistenceController that points the persistent store at the App Group path:
class PersistenceController
static let shared = PersistenceController()
let container: NSPersistentContainer
private static let appGroupID = "group.com.example.ShareSaver"
init(inMemory: Bool = false)
container = NSPersistentContainer(name: "ShareSaver")
if let storeURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: Self.appGroupID)?
.appendingPathComponent("ShareSaver.sqlite")
container.persistentStoreDescriptions.first!.url = storeURL
container.loadPersistentStores _, error in
if let error fatalError("Store failed: \(error)")
container.viewContext.automaticallyMergesChangesFromParent = true
The Share Extension saves items using a background context:
private func saveItem(text: String)
let context = PersistenceController.shared.container.newBackgroundContext()
context.performAndWait
let item = SavedItem(context: context)
item.id = UUID()
item.text = text
item.createdAt = Date()
try? context.save()
This all works fine. The data lands in SQLite. Kill the app, relaunch, the data shows up, so it's definitely being persisted. The problem is getting the main app to see that data without a relaunch.
Quick sidebar on why I went with Core Data instead of SwiftData: I was using Claude Code to build this project, and AI coding tools just aren't trained well enough on SwiftData yet. There's not enough content out there for them to do a great job with it. Plus SwiftData hasn't gone through the battle testing that Core Data has. So I stuck with what I know works. If you're doing agent-assisted coding, I'd honestly recommend the same. At least for now.
The @FetchRequest Approach
Here's what you'd write first, and what every Core Data + SwiftUI tutorial shows:
struct ContentView: View
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.scenePhase) private var scenePhase
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \SavedItem.createdAt, ascending: false)]
)
private var items: FetchedResults
var body: some View
List
ForEach(items) item in
Text(item.text ?? "")
.onChange(of: scenePhase)
if scenePhase == .active
viewContext.refreshAllObjects()
You share a link, switch back to the app, and... nothing. The list is stale. Only a full kill-and-relaunch shows the new item.
I already had a bad feeling about this when I saw @FetchRequest being used. I'll be honest; I don't love property wrappers. I think they partially ruined Swift. But what can you do ??♂️
Why @FetchRequest Breaks Across Processes
Your Share Extension runs in a completely separate process. The extension has its own PersistenceController instance, its own NSPersistentContainer, and its own managed object context. When it calls context.save(), it writes directly to the SQLite file. The main app's viewContext has no idea that happened.
@FetchRequest creates an NSFetchedResultsController under the hood that listens for NSManagedObjectContextObjectsDidChange notifications on the viewContext. Objects inserted, updated, or deleted within that context trigger the notification and SwiftUI re-renders. Cross-process writes don't trigger any of that. The Share Extension writes to SQLite, and the main app's viewContext is sitting there working with a stale in-memory cache.
refreshAllObjects() doesn't help either. It re-faults every managed object currently registered in the context, but it can't discover new objects. If the Share Extension inserted a row that this context has never seen, refreshAllObjects() has nothing to refresh. The context doesn't even know the row exists.
The Debugging Spiral
Tags:
#0
Want to run a more efficient business?
Mewayz gives you CRM, HR, Accounting, Projects & eCommerce — all in one workspace. 14-day free trial, no credit card needed.
Try Mewayz Free →