EXC_BAD_ACCESS when using NSPredicate in Swift - swift

I'm trying to fetch all my items from Core Data that doesn't have "X" in its allergens attribute.
func doesNotContain(attribute: String = "allergens", text: String) {
let request: NSFetchRequest<Dinner> = Dinner.fetchRequest()
let predicate = NSPredicate(format:"NOT \(attribute) CONTAINS %#", text)
request.predicate = predicate
do { items = try context.fetch(request) }
catch let error { print("Error: \(error)") }
}
For some reason this crashes with "EXC_BAD_ACCESS"
But it works perfectly fine when I'm trying to fetch with a predicate using:
func containsSearchWithNSPredicate(attribute: String, text: String) {
let request: NSFetchRequest<Dinner> = Dinner.fetchRequest()
let predicate = NSPredicate(format: "\(attribute) CONTAINS[cd] %#", text)
request.predicate = predicate
do { items = try context.fetch(request) }
catch let error { print("Error: \(error)") }
}
The attribute here is set to "name" when the function is called, and it's set to "allergens" in the first example
I've made sure that the attribute "allergens" is not nil, and I've also tried using %d instead of %#. The allergens attribute is an array, and that's why I'm using NOT CONTAINS

Turns out that NSPredicate doesn't work with attributes marked as "Transformable" with a custom class of [String].
I made it work by instead of having an array, I made it to a String, separating each individual allergen with ";" to make it possible to divide into substrings later.
allergen example:
dinner.allergens = "Gluten;Dairy"
The NSPredicate:
let predicate = NSPredicate(format:"NOT (%K CONTAINS[cd] %#)",attribute, text)
The fetch request will now get all entities that does not have an allergen attribute containing text

Related

Core Data Predicate Using KeyPath on a Collection

I have the following code which returns all movies matching the actor name.
static func byActorName(name: String) -> [Movie] {
let request: NSFetchRequest<Movie> = Movie.fetchRequest()
request.predicate = NSPredicate(format: "actors.name CONTAINS %#", name)
do {
return try viewContext.fetch(request)
} catch {
print(error)
return []
}
}
The code above works, but how can I convert it to a modern Swift KeyPath approach?
I implemented the following code which works but I don't like the idea of writing %K.name syntax.
static func byActorName(name: String) -> [Movie] {
let request: NSFetchRequest<Movie> = Movie.fetchRequest()
request.predicate = NSPredicate(format: "%K.name CONTAINS %#", #keyPath(Movie.actors), name)
do {
return try viewContext.fetch(request)
} catch {
print(error)
return []
}
}
Any recommendations?
Solution:
request.predicate = NSPredicate(format: "%K.%K CONTAINS %#", #keyPath(Movie.actors), #keyPath(Actor.name), name)

how to modify my Core Data attributes of an entity but on all the elements of my database

hello community I am a novice and this is my first question.
how to change all the attributes of an entity and be able to change all my Core Data elements,
because I can only change the first attribute of an entity but not all my data records.
Here in this function I can only change the name
and then I get this following error has the line:
let objectUpdate = test[0] : Thread 1: Fatal error: Index out of range
func updateData() {
var newName = ""
var newPrenom = ""
newName = name.text!
newPrenom = prenom.text!
let managedContext = AppDelegate.viewContext
let fetchRequest : NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name = %#", newName)
do {
fetchRequest.predicate = NSPredicate(format: "prenom = %#", newPrenom)
let test = try! managedContext.fetch(fetchRequest) as! [NSManagedObject]
let objectUpdate = test[0]
objectUpdate.setValue(newName,forKey: "name")
objectUpdate.setValue(newPrenom, forKey: "prenom")
do {
try managedContext.save()
}
catch {
print(error)
}
} catch {
print(error)
}
}
There are a number of ways we can avoid this error.
Unwrapping optional .first value
Swift's Collection gives us safe way to get first item, simply by accessing the first property on a given collection. It will return an Optional<Element> value so we need to unwrap it first either by using if let of guard let
if let object = test.first {
// do something with object
}
or
guard let object = test.first else { return }
// do something with object
Checking if value at index exists
It's often a good idea to check for a specific index within the indices property before accessing the value behind it.
if test.indices.contains(0) {
let object = test[0]
// do something with object
}
These hints should prevent your code from crashing again.
Other Suggestions
This is not really safe or clean:
var newName = ""
var newPrenom = ""
newName = name.text!
newPrenom = prenom.text!
We can make it much cleaner and most importantly safer by using a guard statement
guard let newName = name.text, let newPrenom = prenom.text else { return }
Two important things happened here:
No more force-unwrapping the optional values of text [which could cause a crash]
The properties are now immutable, meaning we can be sure that what we are saving to the CoreDate is what was retreived at the beginning of the function
Since the line:
let test = try! managedContext.fetch(fetchRequest) as! [NSManagedObject]
is already wrapped in the do-catch clause, you can safely remove forced try! and replace it with try.
let test = try managedContext.fetch(fetchRequest) as! [NSManagedObject]
Let's use types! On this line you create a NSFetchRequest object for some entity named "Person".
let fetchRequest : NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Person")
I am guessing CoreData have generated for you a NSManagedObject subclass, named Person. If this is true, you could rewrite it like this:
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
With the previous tip implemented, we can now get rid of as! [NSManagedObject] from this line:
let test = try managedContext.fetch(fetchRequest) as! [NSManagedObject]
Since the NSFetchRequest object is now nicely typed, we can take advantage of it by rewriting it like this:
let test: [Person] = try managedContext.fetch(fetchRequest)
So we are using proper types now? cool! Lets now improve this:
objectUpdate.setValue(newName,forKey: "name")
objectUpdate.setValue(newPrenom, forKey: "prenom")
by rewriting this and using properties on Person object
objectUpdate.name = newName
objectUpdate.prenom = newPrenom
No need for introducing second level of do-catch clause, since we are already in one!
do {
try managedContext.save()
}
catch {
print(error)
}
you can easily replace it with just the save() call, like this:
try managedContext.save()
Are you sure these predicates are what you want?
fetchRequest.predicate = NSPredicate(format: "name = %#", newName)
fetchRequest.predicate = NSPredicate(format: "prenom = %#", newPrenom)
What I can read from them is that you are fetching Person object where the name is newName and prenom is newPrenom and then you update it with the same exact values? Are you using some kind of identification of users? like id: Int or id: UUID? It would make much more sense to write something like this
let id: Int = // ID of the user you are currently editing
fetchRequest.predicate = NSPredicate(format: "id == \(id)")
if you are not using any id's, you could try storing the initial values of name and prenom
// in cell declaration - set when you configure your cell
var initialName: String?
var initialPrenom: String?
// then in your function:
fetchRequest.predicate = NSPredicate(format: "name = %#", initialName)
fetchRequest.predicate = NSPredicate(format: "prenom = %#", initialPrenom)
But I just noticed you also override you first predicate with the second one. You need to use NSCompoundPredicate
fetchRequest.predicate = NSCompoundPredicate(
type: .and, subpredicates: [
NSPredicate(format: "name = %#", initialName),
NSPredicate(format: "prenom = %#", initialPrenom)
]
)
Suggested version
func updateData() {
guard let newName = name.text, let newPrenom = prenom.text else { return }
let managedContext = AppDelegate.viewContext
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
fetchRequest.predicate = NSCompoundPredicate(
type: .and, subpredicates: [
NSPredicate(format: "name = %#", initialName),
NSPredicate(format: "prenom = %#", initialPrenom)
]
)
do {
let objects: [Person] = try managedContext.fetch(fetchRequest)
guard let object = objects.first else { return }
object.name = newName
object.prenom = newPrenom
try managedContext.save()
} catch {
print(error)
}
}
If the index 0 is out of range, it means that the array is empty. Before accessing it, add
if test.isEmpty{
return //the fetch request didn't return any values
}

How to access and modify Core Data

I have a Core Data entity with two attributes (title and time), both String. How can I access the time attribute by knowing the title and how can I modify the time property later?
I can access the title with a predicate but I don't know how to get and modify the title
func getTime(title: String, entityName: String) {
let request: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: entityName)
request.predicate = NSPredicate(format: "title == %#", title)
}
Your function is not returning anything.
You still need to do a request, not only declaring it.
do {
let fetchedTimes = try context.executeFetchRequest(request) as! [Time]
return fetchedTimes
} catch {
fatalError("Failed to fetch times: \(error)")
}
Change function:
func getTime(title: String, entityName: String) -> [time] {

Is it possible to return only the desired objects in Core Data

I have two entities
Book
- attributes: name, desc
- relationship: book with type To Many to BookLang (inverse: bookLang)
BookLang
- attributes: nameLang, descLang, lang
- relationship: bookLang with type To One to Book (inverse: book)
In BookLang there is two objects for fr,de language
If I use
let lang = "fr"
let pre = NSPredicate(format: "book.lang == %#", lang);
Then I have to loop through the fetchResult and then loop through the book NSSet
for b in bookSet {
let lan = b.valueForKey("lang") as! String
if(lan == lang){
let name = b.valueForKey("nameLang") as! String
println(name) // the book title in french
break;
}
}
How I can get in the book NSSet only the object I want
I try with
let subPre = NSPredicate(format: "SUBQUERY(BookLang, $s, $s.lang == %#)", lang);
with error
Unable to parse the format string "SUBQUERY(BookLang, $s, $s.lang == %#)"'
Keep it simple. Relationships, loops, subqueries - all unnecessary!
How about a simple string attribute lang?
let frenchBooks = allBooks.filteredSetUsingPredicate(NSPredicate(format: "lang = %#", "fr"))
Or, in true Swift fashion
let frenchBooks = allBooks.filter() { $0.lang = "fr" }
Why are you creating a predicate without using?
To get all the books that respect a condition you can use a fetch request against your core data context.
let fetchRequest = NSFetchRequest(entityName: "Book") //supposing that you managed object subclass is named Book
fetchRequest.predicate = NSPredicate(format: "lang == %#", lang);
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [Book] {
//DO what you want
}
This is just a snippet you should complete with error and the else statement

Core data's managed object property as argument

AnyThe first statement doesn't return any values, while second one works correctly. Is it possible to specify property name as one of the arguments to avoid hard coding?
let predicate = NSPredicate(format: "%# == %#", "id","553178666d61d70c24fe4221")
let predicate = NSPredicate(format: "id == %#", "553178666d61d70c24fe4221")
That's a complete solution thanks to #FreeNickname
class func searchForObject(propertyName:NSString,property:NSString,className:String,single:Bool )->AnyObject?{
let fetchRequest = NSFetchRequest(entityName: className)
let predicate = NSPredicate(format: "%K == %#", argumentArray: [propertyName, property])
var appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
fetchRequest.predicate = predicate
// Execute the fetch request, and cast the results to an array of LogItem objects
var error:NSError?
if let fetchResults = appDelegate.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) as [AnyObject]? {
if let e = error{
println(e.debugDescription)
}
if single {
var object: AnyObject? = fetchResults.first
return object;
}
else{
return fetchResults
}
}
return nil;
}
Try %K instead of %# for the attribute name.
Like so:
let predicate = NSPredicate(format: "%K == %#", "id","553178666d61d70c24fe4221")
Source: Predicates Syntax (developer.apple.com)
From the docs:
The format string supports printf-style format specifiers such as %x (see Formatting String Objects). Two important format specifiers are %# and %K.
%# is a var arg substitution for an object value—often a string, number, or date.
%K is a var arg substitution for a key path.