Implementing fetchedresultsviewcontroller in Swift 3 - core-data

I'm trying to implement fetchedresultsviewcontroller in Swift 3 and am running into the following error when setting the delegate property of the controller to self:
Cannot assign value of type 'SomeRootViewController' to type
'NSFetchedResultsControllerDelegate?'
SomeRootViewController.swift
#available(iOS 10.0, *)
#objc class SomeRootViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var delegate: SomeRootViewControllerDelegate?
public var context: NSManagedObjectContext!
private let persistentContainer = NSPersistentContainer(name: "Accessory")
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Accessory> = {
// Create Fetch Request
let fetchRequest: NSFetchRequest<Accessory> = Accessory.fetchRequest() as! NSFetchRequest<Accessory>
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "AccessoryAttributes.name", ascending: true)]
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self //<<-- this is where error occurs
return fetchedResultsController
}()
Can somebody explain the problem to me and how I can fix it?

Because you are setting delegate to self, you'll also need to make SomeRootViewController conform to the NSFetchedResultsControllerDelegate protocol, like this:
class SomeRootViewController: NSFetchedResultsControllerDelegate, UIViewController, UITableViewDataSource, UITableViewDelegate {

Related

Problem using #Environment variable in SwiftUI constructor

I'm using a core data child context to separate temporary entities from my main context, but when I pass in the main context as an environment variable, I don't have access to it in order to initialize the child context and get a 'self' used before all stored properties are initialized error
Any suggestions?
struct CreatePlayerView: View {
#State private var newPlayer: PlayerEntity
#Environment(\.managedObjectContext) private var managedObjectContext
private let childManagedObjectContext: NSManagedObjectContext
init() {
childManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
\\ 'self' used before all stored properties are initialized
childManagedObjectContext.parent = managedObjectContext
newPlayer = PlayerEntity(context: childManagedObjectContext)
}
var body: some View {
EditPlayerViewDetail(
player: $newPlayer,
onDone: { player in
try? childManagedObjectContext.save()
}
)
}
}

SwiftUI CoreData MVVM resolutes in error "EXC_BAD_INSTRUCTION...."

I'm trying to use a ViewModel between the ContentView and Core Data in SwiftUI. Xcode builder runs the App but I get an immediate error: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) for var recList.
Can anyone help?
Following a simple example of what I'm doing:
ListViewModel:
class ListViewModel: ObservableObject {
var recRequest: FetchRequest<Newdb>
var recList: FetchedResults<Newdb>{recRequest.wrappedValue} <-------- error appears here
#Published var records = [ViewModel]()
init() {
self.recRequest = FetchRequest(entity: Newdb.entity(), sortDescriptors: [])
fetchEntries()
}
func fetchEntries() {
self.records = recList.map(ViewModel.init)
}
}
ViewModel:
class ViewModel {
var name: String = ""
init(db: Newdb) {
self.name = db.name!
}
}
ContentView:
struct ContentView: View {
#ObservedObject var listViewModel: ListViewModel
init() {
self.listViewModel = ListViewModel()
}
var body: some View {
ForEach(listViewModel.records, id: \.name) { index in
Text(index.name)
}
}
}
two things I noticed; your ListViewModel is an ObservableObject but you do not have any #Published var ...
Also when creating a class such as ListViewModel you cannot use "recRequest" as you do in recList, because it is not created yet. It is created in the init() method not before.
Do your "recList = FetchedResults{recRequest.wrappedValue}" somewhere else, like in the fetchEntries().
From what I can tell, FetchRequest is a property wrapper.
It is supposed to wrap something, e.g.;
#FetchRequest(
entity: User.entity(),
sortDescriptors: []
) var users: FetchedResults<User> // users are 'wrapped' in a FetchRequest instance
It makes sense that wrappedValue is nil because there's nothing to be wrapped in
self.recRequest = FetchRequest(entity: Newdb.entity(), sortDescriptors: [])
You might want to double-check its usage.

Input a dynamic value into #FetchRequest, to fetch a single entity from core data in SwiftUI

I saw same type of error but with different kind of code here, so I think it's better to ask a new question on this context. I have attempted to "find a specific entity" from core data by trying to pass a string variable (which use as a key to find that entity) called title into #FetchRequest. This is part of the code I have used
struct AccountMainPage: View {
//*** User input ***
var title: String
//*** Core data enviroment initialisation ***
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: Accounts.getSpecificAccounts(findTitle: title)) var fetchedAccount: FetchedResults<Accounts>
var body: some View {
//SOME CODE HERE
}
}
The public class Accounts has the extension:
extension Accounts {
static func getSpecificAccounts(findTitle: String) -> NSFetchRequest<Accounts> {
let request: NSFetchRequest<Accounts> = Accounts.fetchRequest() as! NSFetchRequest<Accounts>
let findDescriptor = NSPredicate(format: "title == %#",findTitle)
request.predicate = findDescriptor
return request
}
}
However, the line with #FetchRequest(fetchRequest: Accounts.getSpecificAccounts(findTitle: title)) var fetchedAccount: FetchedResults<Accounts> has a syntax error:
Cannot use instance member 'title' within property initializer; property initializers run before 'self' is available
Is there something wrong with my code?
#FetchRequest is dynamic property which is initialised, as any other property, before your AccountMainPage init is called, so self is not available yet, that is why you cannot use title property which is a member of self, and that is about what compiler error tells.
So here is a possible solution: we initialise fetch request property with stub request and then in init, which is called later, reinitialise it with real fetch request.
Here is an approach demo (all unrelated things cut):
struct ContentView: View {
var title: String
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: Accounts.fetchRequest()) var fetchedAccount: FetchedResults<Accounts>
init(title: String) {
self.title = title
_fetchedAccount = FetchRequest<Accounts>(fetchRequest: Accounts.getSpecificAccounts(findTitle: title))
}
...

NSFetchedResultsControllerDelegate not called when data is not matching predicate anymore

The problem I am trying to solve is the following: a predicate of an NSFetchedResultsController is restricting the results to specific conditions. But when data objects either newly satisfy or dissatisfy conditions, the NSFetchedResultsControllerDelegate is not getting called.
My NSFetchedResultsController:
private var _fetchedResultsController : NSFetchedResultsController<CardIndex>? = nil
var fetchedResultController : NSFetchedResultsController<CardIndex> {
get {
if _fetchedResultsController == nil {
let fetchRequest: NSFetchRequest<CardIndex> = CardIndex.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor.init(key: "index", ascending: true)]
fetchRequest.predicate = NSPredicate.init(format: "consumer == %# AND card.definition != nil AND card.stateInt == 0", consumer)
_fetchedResultsController = NSFetchedResultsController<CardIndex>.init(fetchRequest: fetchRequest, managedObjectContext: CDManager.shared.one.viewContext, sectionNameKeyPath: nil, cacheName: nil)
do {
try _fetchedResultsController?.performFetch()
} catch {
Log(.Warning, "...")
}
_fetchedResultsController?.delegate = self
}
return _fetchedResultsController!
}
}
The data structure that is relevant here (only pseudo code):
class CardIndex {
#NSManaged public var index: Int16
#NSManaged public var consumer: String?
#NSManaged public var card: CardApplication?
}
class CardApplication {
#NSManaged public var title: String?
#NSManaged public var indexes: NSSet?
#NSManaged public var stateInt: NSNumber?
#NSManaged public var definition: CardDefinition?
}
When the NSFetchedResultsController is initially fetching the data, it correctly returns all CardApplications that have a definition set and the stateInt has the value 0. But when I change the value of stateInt during runtime to another value that is not matching the predicate condition anymore, the NSFetchedResultsControllerDelegate is not being called reflecting that change in the data. I made sure that I save the managed object's context after changing the stateInt value and I can also see that the context hasChanges before saving and the CardApplication managed object is also showing the change on property changedValues(). From debugger console before saving the change, where self is the CardApplication object I changed stateInt of:
(lldb) po managedObjectContext!.hasChanges
true
(lldb) po self.isUpdated
true
(lldb) po self.changedValues()
▿ 1 element
▿ 0 : 2 elements
- key : "stateInt"
- value : 1
Why is the delegate not being called?
As your FRC is configured to fetch CardIndex objects, it only observes changes to those objects. It will consequently not respond to changes to the related CardApplication objects. You could either deliberately “dirty” the CardIndex, which will trigger the FRC to re-evaluate the predicate, or abandon the FRC and use your own observer to update your UI.

Resolving 'Failed to call designated initializer on NSManagedObject class'

I'm new to Swift and I'm trying to learn how to use Core Data. But I'm getting this error and I'm not sure what I've done wrong. I've searched online and tried a few things but I can't get it right.
Failed to call designated initializer on NSManagedObject class 'FirstCoreData.Course'
When this line executes:
ncvc.currentCourse = newCourse
In this function:
class TableViewController: UITableViewController, AddCourseViewControllerDelegate {
var managedObjectContext = NSManagedObjectContext.init(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addCourse" {
let ncvc = segue.destinationViewController as! NewCourseViewController
ncvc.delegate = self
let newCourse = NSEntityDescription.insertNewObjectForEntityForName("Course", inManagedObjectContext: self.managedObjectContext) as! Course
ncvc.currentCourse = newCourse
}
}
Class generated by "Create NSManagedObject Subclass..." for Course entity:
import Foundation
import CoreData
class Course: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
And:
import Foundation
import CoreData
extension Course {
#NSManaged var title: String?
#NSManaged var author: String?
#NSManaged var releaseDate: NSDate?
}
The problem lies not in the code in your question, but in the snippet you included as comments to the other answer:
var currentCourse = Course()
This doesn't just declare currentCourse to be of type Course, it also creates an instance of the Course entity using the standard init method. This is expressly not allowed: You must use the designated initialiser: init(entity entity: NSEntityDescription,
insertIntoManagedObjectContext context: NSManagedObjectContext?). This is described in the Apple Documentation here.
I suspect you do not ever use the instance created by the above var definition, so just define it as being of type Course?:
var currentCourse : Course?
Since it is optional, you do not need to set an initial value, though you will need to unwrap the value whenever it is used.
The simplest way is this:
Define in the applicationDelegate a reference for the context
Instantiate the variable by passing the context
In the AppDelegate (outside the brackets):
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
And in the code:
let currentCourse = Course(context:context)
Now you have your entity created. But don't forget to save with:
appDelegate.saveContext()
I had the same issue. And instantiating the object like this worked, for your course it would be something like this:
var currentCourse = Course.init(entity: NSEntityDescription.entityForName("Course", inManagedObjectContext:mox)!, insertIntoManagedObjectContext: mox)
instead of:
var currentCourse = Course()
I used this in Xcode 8.3.2 with Swift 3.1.
NSEntityDescription.insertNewObject(forEntityName: String(describing: type(of: Record())), into: managedObjectContext) as! Record
And got the same error message. But this data was inserted into db. So maybe this doesn't matter.
Your currentCourse should be NSManagedObject class
Please refer this CoreData: error: Failed to call designated initializer on NSManagedObject class

Resources