Xcode's preview canvas keeps on crashing with no error message when I try to pass in a preview Core Data object like so:
import SwiftUI
import CoreData
struct BookView: View {
let book: Book
var body: some View {
Text("Hello, World!")
}
}
// ^^^ This stuff is fine ^^^
// vvv This stuff is not vvv
struct BookView_Previews: PreviewProvider {
static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
static var previews: some View {
let book = Book(context: moc)
book.title = "Test book"
book.author = "Test author"
book.genre = "Fantasy"
book.rating = 4
book.review = "This was a great book; I really enjoyed it."
return NavigationView {
BookView(book: book)
}
}
}
I'm following a Hacking with Swift tutorial on Core Data and SwiftUI and am at this step.
This appears to be the standard way to add preview objects into the SwiftUI canvas, but I'm unable to get it to work. FYI the app runs fine in the simulator, I'm just trying to get it to also work in the preview canvas. I'm using Xcode 13.2.1 on macOS 12.
Thank you!
Instead of creating a NSManagedObjectContext use
static let context = PersistenceController.preview.container.viewContext
That variable is provided in the standard Xcode project with Core Data.
Also, if you have been using the real store for preview it might be corrupted somehow so you might have to destroy.
Add the code below
do{
try container.persistentStoreCoordinator.destroyPersistentStore(at: container.persistentStoreDescriptions.first!.url!, type: .sqlite, options: nil)
}catch{
print(error)
}
Right under
container = NSPersistentCloudKitContainer(name: "YourAppName")
Before you load the store.
This destroys the store and then it gets recreated when you call loadPersistentStores be sure to remove that piece of code after you clear the preview device so you don't accidentally destroy another store you don't mean to destroy.
Related
I've followed along the Apple Developer Code-Along videos (https://developer.apple.com/news/?id=yv6so7ie) as well as looked at this repo with different Widget types (https://github.com/pawello2222/WidgetExamples) in search on how to populate a dynamic intent with items from Core Data.
My question is: How can I used the saved Core Data objects from the user to have them as "filters" or options in widget settings?
My Core Data model is called Favourite and it is a Class Definition CodeGen file.
I have added the Intent target to my project and can get it to appear in the Widget settings, but when I tap in to "Choose" the list is empty. However, in my Core Data there are 3 saved items.
I have tried doing simple things like print(CoreDM.shared.getAllFavourites()) to see if I am even retrieving them all, but not listing in the settings, but the console prints out:
<NextDeparturesIntent: 0x283490bd0> {
favourite = <null>;
}
At this point I'm stuck on understanding on how I can get my Favourites visible and then usable. It seems everything else is hooked up and working or ready but the retrieval.
I have also tried re-adding into the Info.plist of the intent the IntentSupported of the intent's name: NextDeparturesIntentHandling:but that had no success.
Files
Core Data Model - Favourite - FavouritesCDModel
In my Core Data model I have more options but for this example:
UUID
beginName
finishName
Widget Intent - NextDepartures.intentdefinition
This is what has been set up as followed by Apple's Code Along videos:
app-name-intent - IntentHandler.swift
import Intents
class IntentHandler: INExtension, NextDeparturesIntentHandling {
let coreDM = CoreDataManager.shared
func provideFavouriteOptionsCollection(
for intent: NextDeparturesIntent,
with completion: #escaping (INObjectCollection<FavouriteRoutes>?, Error?) -> Void
) {
dump( coreDM.getAllFavourites() ) // <--- debug line
let favouriteRoutes = coreDM.getAllFavourites().map {
FavouriteRoutes(identifier: $0.uniqueId, display: $0.departingStopName!)
}
let collection = INObjectCollection(items: favouriteRoutes)
completion(collection, nil)
}
override func handler(for intent: INIntent) -> Any {
return self
}
}
CoreDataManager
import CoreData
final class CoreDataManager {
static let shared = CoreDataManager()
private init() {}
private let persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "FavouritesCDModel")
container.loadPersistentStores(completionHandler: { description, error in
if let error = error as NSError? {
fatalError("Core Data Store failed \(error.localizedDescription)")
}
})
return container
}()
var managedObjectContext: NSManagedObjectContext {
persistentContainer.viewContext
}
func getAllFavourites() -> [Favourite] {
let fetchRequest: NSFetchRequest<Favourite> = Favourite.fetchRequest()
do {
return try managedObjectContext.fetch(fetchRequest)
} catch {
return []
}
}
}
I didn't realise that the app, widget, and other targets are all sandboxed.
I incorrectly assumed everything within the same app ecosystem would be allowed access to the same items.
In order to get the above code to work is adding the file to the App Groups and FileManager.
CoreDataManager
Inside the persistentContainer add in the storeURL and descriptions:
let storeURL = FileManager.appGroupContainerURL.appendingPathComponent("COREDATAFILE.sqlite")
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
FileManager+Ext
Create a FileManager extension for the container url:
extension FileManager {
static let appGroupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.domain.app")!
}
Info.plist
Make sure that the Info.plist files have access to the app group
Signing and Capabilities
Make sure you add the App Groups capability to each target that needs it, and add it in App Store Connect
I have a vertical list in the screen to show the images category wise and each category/list contains list of images which is shown horizontally. (Attached image for reference)
Now when I am scrolling horizontally or vertically then application is crashing due to memory leaking. I guess lots of people facing this issue in the ForEach loop.
I have also try with List instead of ForEach and ScrollView for both vertical/horizontal scrolling but unfortunately facing same issue.
Below code is the main view which create the vertical list :
#ObservedObject var mainCatData = DataFetcher.sharedInstance
var body: some View {
NavigationView {
VStack {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 20) {
ForEach(0..<self.mainCatData.arrCatData.count, id: \.self) { index in
self.horizontalImgListView(index: index)
}
}
}
}.padding(.top, 5)
.navigationBarTitle("Navigation Title", displayMode: .inline)
}
}
I am using below code to create the horizontal list inside each category, I have used LazyHStack, ForEach loop and ScrollView
#ViewBuilder
func horizontalImgListView(index : Int) -> some View {
let dataContent = self.mainCatData.arrCatData[index]
VStack {
HStack {
Spacer().frame(width : 20)
Text("Category \(index + 1)").systemFontWithStyle(style: .headline, design: .rounded, weight: .bold)
Spacer()
}
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 20) {
ForEach(0..<dataContent.catData.count, id: \.self) { count in
VStack(spacing : 0) {
VStack(spacing : 0) {
if let arrImgNames = themeContent.catData[count].previewImgName {
// Use dynamic image name and it occurs app crash & memory issue and it reached above 1.0 gb memory
Image(arrImgNames.first!).resizable().aspectRatio(contentMode: .fit)
// If I will use the same image name then there is no memory issue and it consumes only 75 mb
// Image("Category_Image_1").resizable().aspectRatio(contentMode: .fit)
}
}.frame(width: 150, height: 325).cornerRadius(8.0)
}
}
}
}
}
}
Below is the data model which I am using to fetch images from json file and shows it in the list
class DataFetcher: ObservableObject {
static let sharedInstance = DataFetcher()
#Published var arrCatData = [CategoryModel]()
init() {
do {
if let bundlePath = Bundle.main.url(forResource: FileName.CategoryData, withExtension: "json"),
let jsonData = try? Data(contentsOf: bundlePath) {
let decodedData = try JSONDecoder().decode([CategoryModel].self, from: jsonData)
DispatchQueue.main.async { [weak self] in
self?.arrCatData = decodedData
}
}
} catch {
print("Could not load \(FileName.CategoryData).json data : \(error)")
}
}
}
struct CategoryModel : Codable , Identifiable {
let id: Int
let catName: String
var catData: [CategoryContentDataModel]
}
struct CategoryContentDataModel : Codable {
var catId : Int
var previewImgName : [String]
}
Crash logs :
malloc: can't allocate region
:*** mach_vm_map(size=311296, flags: 100) failed (error code=3)
(82620,0x106177880) malloc: *** set a breakpoint in malloc_error_break to debug
2021-07-01 18:33:06.934519+0530 [82620:5793991] [framework] CoreUI: vImageDeepmap2Decode() returned 0.
2021-07-01 18:33:06.934781+0530 [82620:5793991] [framework] CoreUI: CUIUncompressDeepmap2ImageData() fails [version 1].
2021-07-01 18:33:06.934814+0530 [82620:5793991] [framework] CoreUI: Unable to decompress 2.0 stream for CSI image block data. 'deepmap2'
(82620,0x106177880) malloc: can't allocate region
:*** mach_vm_map(size=311296, flags: 100) failed (error code=3)
(82620,0x106177880) malloc: *** set a breakpoint in malloc_error_break to debug
Note: All images of category are loading from the assets only and If I will use the static name of the image in the loop then there is no memory pressure and it will consume only 75 mb.
I think there is a image caching issue. Does I have to manage image caching even if I am loading images from assets?
Can anyone assist me to resolve this issue? Any help will be much appreciated. Thanks!!
I faced the same problem when building the app using the SwiftUI framework. I fetched ~600 items from the server (200 ms), then tried to show it in UI using ForEach. But it took 3 GBs of RAM. After research, I understand that it's not an issue of SwiftUI. Memory issue happens because of the loop (for-loop).
I found the following:
In the pre-ARC Obj-C days of manual memory management, retain() and
release() had to be used to control the memory flow of an iOS app. As
iOS's memory management works based on the retain count of an object,
users could use these methods to signal how many times an object is
being referenced so it can be safely deallocated if this value ever
reaches zero.
The following code stays at a stable memory level even though it's looping millions of times.
for _ in 0...9999999 {
let obj = getGiantSwiftClass()
}
However, it's a different story if your code deals with legacy Obj-C code, especially old Foundation classes in iOS. Consider the following code that loads a big image ton of time:
func run() {
guard let file = Bundle.main.path(forResource: "bigImage", ofType: "png") else {
return
}
for i in 0..<1000000 {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
}
Even though we're in Swift, this will result in the same absurd memory spike shown in the Obj-C example! The Data init is a bridge to the original Obj-C [NSData dataWithContentsOfURL] -- which unfortunately still calls autorelease somewhere inside of it. Just like in Obj-C, you can solve this with the Swift version of #autoreleasepool; autoreleasepool without the #:
autoreleasepool {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
In your case, use autoreleasepool inside ForEach:
ForEach(0..<dataContent.catData.count, id: \.self) { count in
autoreleasepool {
// Code
}
}
References:
https://stackoverflow.com/a/25880106/11009631
https://swiftrocks.com/autoreleasepool-in-swift
Try not using explicit self in your ForEach. I've had some weird leaks in my SwiftUI views and switching to implicit self seemed to get rid of them.
Your main problem is that you're using a ScrollView/VStack vs using a List. List is like UITableView which intelligently only maintains content for cells that are showing. ScrollView doesn't assume anything about the structure and so everything within it is retained. The VStack being lazy only means that it doesn't allocate everything immediately. But as it scrolls to the bottom (or HStack to the side), the memory accumulates because it doesn't release the non visible items
You say you tried List, but what did that code look like? You should have replaced both ScrollView and LazyVStack.
Unfortunately, there is no horizonal list at this moment, so you'll either need to roll your own (perhaps based on UICollectionView), or just minimize the memory footprint of your horizonal rows.
What is the size of your images? Image is smart enough to not need to reload duplicate content: the reason why a single image literal works. But if you're loading different images, they'll all be retained in memory. Having said that, you should be able to load many small preview images. But it sounds like your source images may not be that small.
Try LazyVGrid with only one column instead of using Foreach.
let columns = [GridItem(.flexible(minimum: Device.SCREEN_WIDTH - "Your horizontal padding" , maximum: Device.SCREEN_WIDTH - "Your horizontal padding"))]
ScrollView(.vertical ,showsIndicators: false ){
LazyVGrid(columns: columns,spacing: 25, content: {
ForEach(0..< dataContent.catData.count, id: \.self) { index in
"Your View"
}
}
}
I'm trying to implement CoreData in a Framework. The framework is part of a couple of different apps, so the DB needs to be contained within the Framework (as opposed to having its own class somewhere in the app).
I'm following examples where the ViewController and AppDelegate manage the initialization of the app. Again, framework, so the structure is different. I have 6 classes in my Framework, but only two access the db, so I'm putting it in the one that does the heavy lifting, Game.
In the class definition for Game I have,
// CoreData stuff
let context: NSManagedObjectContext?
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Word")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
I need on a one-time basis to initialize the db with a list of words, so I have a function
public func initCD() {
do {
context = persistentContainer.viewContext
var words = try context?.fetch(Word.fetchRequest())
print("starting count \(words?.count)")
if words?.count == 0 {
print(words?.count)
getTextFile(words?.count)
words = try context?.fetch(Word.fetchRequest())
print(words?.count)
} else {
print("alneady saved \(words?.count) words")
}
} catch {
print("not happening")
}
}
but this chokes when it gets to the reference to Word in both places with Cannot find 'Word' in scope. The function it calls, getTextFile() does the same thing, but I don't doubt solving this once will solve it everywhere....
Is there an obvious bad assumption here? The entity definition looks like this
EDIT:
I found a good guide that made it easier to translate the previous versions based on AppDelegates and having the coredata functionality in the ViewController, to my Framework model. You can find it here: https://medium.com/#yoellev8/sharing-a-core-data-model-with-a-swift-framework-5d191ccec99e
I'll post my working code a little later when I've got it working more completely.
I think a Project Clean and Build should work. Try CMD+Shift+K, then CMD+B. I think the problem is that it can find the object in the files. This should allow you to reference the Word entity without issue.
I am attempting to configure CoreData+CloudKit using NSPersistentCloudKitContainer to automatically sync data to/from CloudKit.
Following the Apple guide, it was trivial enough to set up an Entity, add the appropriate capabilities in Xcode, set up the persistentContainer and saveContext in my AppDelegate.
I'm making fetch() and save() calls through the NSManagedObjectContext, and I'm able to save and fetch records without issue. I can see them in the CloudKit dashboard.
However, if I uninstall the app from the simulator and rebuild/reinstall, my fetchRequest (which has no NSPredicate or sorting, just fetching all records) is always returning an empty list. I'm using the same iCloud account, and I've tried both the public and private database scope. If I create a new record and then retry my fetch request I can retrieve that newly created record, but never any of the old records. I'm 100% certain these records are still in the CloudKit database, as I can see them on the CloudKit Dashboard web app.
I took a look at Apple's CoreDataCloudKitDemo app, and it is able to fetch "Post" entities from the CloudKit database after an uninstall/reinstall, so I know it is possible. However, it is using an NSFetchedResultsController, which won't work for my application (mine is a SpriteKit game).
I attempted copying my CoreData+Cloudkit code into a brand new Xcode project and I can reproduce this issue there. Here's my code for reference:
import UIKit
import CoreData
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var persistentContainer: NSPersistentContainer = {
// Create a container that can load CloudKit-backed stores
let container = NSPersistentCloudKitContainer(name: "coredatacloudkitexample")
// Enable history tracking and remote notifications
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.cloudKitContainerOptions?.databaseScope = .public
container.loadPersistentStores(completionHandler: { (_, error) in
guard let error = error as NSError? else { return }
fatalError("###\(#function): Failed to load persistent stores:\(error)")
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = "nibbler"
// Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
return container
}()
}
// ------
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let viewContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let people: [Person]
do {
people = try viewContext.fetch(fetchRequest)
print("---> fetched People from CoreData: \(people)")
// people.isEmpty is ALWAYS true (empty array) on first install of app, even if records exist in table in CloudKit
if people.isEmpty {
let person = Person(context: viewContext)
person.name = "nibbler"
// save the data through managed object context
do {
try viewContext.save()
print("--> created Person in CoreData: \(person)")
} catch {
print("---> failed to save Person: \(error.localizedDescription)")
}
}
} catch {
print("---> error: \(error)")
}
}
}
What am I missing? Why can I only fetch the records created during this app's install and not prior ones?
UPDATE: It seems that if I wait for a few seconds and re-try my fetch on the first app install that I am able to retrieve the results from the CloudKit database. I can also see a vast number of CoreData+CloudKit log messages in the console upon first launch. Here's what I'm thinking -- even when using NSPersistentCloudKitContainer, a fetch() is reading/writing to the local CoreData store, and then a separate process is running in the background to mirror and merge the local CoreData records with the CloudKit records.
As such, I believe I need to somehow wait/be notified that this sync/merge of local CoreData and CloudKit records has completed on first launch before making my fetch() call, rather than making the fetch() call immediately as the app opens. Any ideas?
You need to use NSFetchedResultsController, why do you think it wouldn't work for your application?
The reason it is necessary is NSFetchedResultsController monitors the viewContext and when the sync process downloads new objects and inserts them into a background context the automaticallyMergesChangesFromParent merges the objects in to the viewContext and advances the generation token. The FRC's delegate methods are called to notify you if objects are inserted, updated or deleted from the fetched objects array which are objects in the context that match the fetch request's entity and predicate.
Here is the thing #professormeowingtons you mention whenever you delete the app on simulator you it won't show the previous records, so my suggestion is to try your app on a real device with an iCloud account already configured, that way you'll be able to add some records to your db then delete the app, reinstall and fetch all the previous records you did enter.
What you can try is this:
Set NSPersistentCloudKitContainerOptions
let id = "iCloud.yourid"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: id)
description?.cloudKitContainerOptions = options
Initialize your CloudKit schema (this is required at least once)
do {
try container.initializeCloudKitSchema()
} catch {
print("ERROR \(error)")
}
Edit:
Can you change lazy var persistentContainer: NSPersistentContainer
to lazy var persistentContainer: NSPersistentCloudKitContainer
An update is requested, since this question has of course been answered for previous versions, the latest search result dated 12/16 generates irrelevant compatibility with previous iOS 9 and 10 projects.
The documentation of course says to select the Use Core Data checkbox when starting a new project, which I did not select, but now think iCloud + Core Data needs to be added to take my app to its next phase -> wherein something like NSFileCoordinator and NSFilePresenter is needed, since in my app UI users are presented with a number of TOPICS, each having three OPTIONS, regarding which users are to choose one option. For each topic the UI then displays the TOTAL NUMBER of users who have chosen each option and the PERCENTAGE of the total for each option.
Right now, the number of choices for each option and the percentage of the total are of course just calculated in my native app -> but actually need to be CALCULATED in something central like the cloud or most likely on a website…but then the website raises the simultaneous read/write problems that NSFileCoordinator and NSFilePresenter have already solved.
So if the iCloud + Core Data system can interject basic arithmetic calculations on the existing Ubiquitous Container numerical value totals - in the cloud upon receiving write numerical value commands from individual users - before sending out the new Ubiquitous Container numerical total and percent values - then I’d much appreciate advise on how fix the errors generated below in trying Create and Initialize the Core Data Stack. Otherwise guess I’ll have to scrape Xcode and go to a hybrid app like PhoneGap if that's the best one for the job.
Hence, referring to the Core Data Programming Guide:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/InitializingtheCoreDataStack.html#//apple_ref/doc/uid/TP40001075-CH4-SW1
and pasting in the following code in the beginning of my existing project, generates
Use of unresolved identifier ‘persistentContainer’… ‘managedObjectContext’
... errors. And the line
init(completionClosure: #escaping () -> ()) {
... generates
Initializers may only be declared within a type
import UIKit
import CoreData
class DataController: NSObject {
var managedObjectContext: NSManagedObjectContext
init(completionClosure: #escaping () -> ()) {
persistentContainer = NSPersistentContainer(name: "DataModel")
persistentContainer.loadPersistentStores() { (description, error) in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
completionClosure()
}
}
}
init(completionClosure: #escaping () -> ()) {
//This resource is the same name as your xcdatamodeld contained in your project
guard let modelURL = Bundle.main.url(forResource: "DataModel", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = psc
let queue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)
queue.async {
guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
fatalError("Unable to resolve document directory")
}
let storeURL = docURL.appendingPathComponent("DataModel.sqlite")
do {
try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil)
//The callback block is expected to complete the User Interface and therefore should be presented back on the main queue so that the user interface does not need to be concerned with which queue this call is coming from.
DispatchQueue.main.sync(execute: completionClosure)
} catch {
fatalError("Error migrating store: \(error)")
}
}
}
// followed by my existing working code:
class ViewController: UIViewController {
go to File > new file... select core Data under iOS and select Data Model
you'll still need some code which xcode auto generates whenever you select core data during project creation.
to get it, just create new project with core data option checked and copy all the code written under ** //Mark: - Core Data Stack** comment in AppDelegate.swift
and add
import CoreData
above
OPTIONAL
And don't forget to change the name of the app after copying the completion block for lazy var persistentContainer. Change the name of your app on this part *NSPersistentContainer(name: "SHOULD-BE-THE-NAME-OF-YOUR-APP") And managedObjectModel function of the code you just copied**
If you're lazy like me, here's all the code you need to copy from the new Core Data project... (why make everyone create a new project?). Change YOUR_APP_NAME_HERE
At the top of your AppDelegate.swift file:
import CoreData
At the bottom of AppDelegate.swift file, before the ending curly bracket:
// MARK: - Core Data stack
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "YOUR_APP_NAME_HERE")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.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)")
}
}
}
I know this is answered, but I believe the actual problem is with Apple's Documentation. If you compare the Objective-C code to the Swift code, you will see that var managedObjectContext: NSManagedObjectContext is not actually defined. You should replace that line with var persistentContainer: NSPersistentContainer. This is the Objective-c interface
#interface MyDataController : NSObject
#property (strong, nonatomic, readonly) NSPersistentContainer *persistentContainer;
- (id)initWithCompletionBlock:(CallbackBlock)callback;
#end
So DataController.swift should be:
class DataController: NSObject {
// Delete this line var managedObjectContext: NSManagedObjectContext
var persistentContainer: NSPersistentContainer
init(completionClosure: #escaping () -> ()) {
persistentContainer = NSPersistentContainer(name: "DataModel")
persistentContainer.loadPersistentStores() { (description, error) in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
completionClosure()
}
}
}
As for the rest of your code, it's not necessary Apple Docs.
Prior to iOS 10 and macOS 10.12, the creation of the Core Data stack was more involved
That section of code is showing you the old way.
Use the following code
lazy var persistantCoordinator :NSPersistentStoreCoordinator = {
let poc = NSPersistentStoreCoordinator(managedObjectModel:managedObjectModel)
let documentFolderUrl = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).last
let path = documentFolderUrl!.appendingPathComponent("Database.sqlite")
let options = [NSMigratePersistentStoresAutomaticallyOption: true,NSInferMappingModelAutomaticallyOption: true]
do{
try poc.addPersistentStore(ofType:NSSQLiteStoreType, configurationName: nil, at: path, options: options)
}catch{
print(error.localizedDescription)
}
return poc
}()
private lazy var managedObjectModel:NSManagedObjectModel = {
let url = Bundle.main.url(forResource:"Database", withExtension:"momd")
return NSManagedObjectModel(contentsOf:url!)!
}()
fileprivate lazy var managedObjectContext:NSManagedObjectContext = {
let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
moc.persistentStoreCoordinator = persistantCoordinator
return moc
}()