Swift / Core Data / Bool or ObjCBool - core-data

I'm trying to save my model with fields of type bool. When I try to add the "true" value an error occurs.
I also tried to change the field type to Objective C Bool (ObjCBool) unsuccessfully, someone had this problem too?
import UIKit
import CoreData
#objc(Test)
class Test: NSManagedObject {
#NSManaged var title: String
#NSManaged var field: ObjCBool // Or Bool
}
// the error occurs before saving at this point
test.field = true

solution with numberWithBool:
import UIKit
import CoreData
#objc(Test)
class Test: NSManagedObject {
#NSManaged var title: String
#NSManaged var field: NSNumber
}
test.field = NSNumber.numberWithBool(false)

Related

Crash when delete a record from coredata in SwiftUI

I make a list for audio items from coredata. after deleting, crash reported as "EXC_BREAKPOINT (code=1, subcode=0x1b8fb693c)", why?
When using
ForEach(items, id: \.self)
, it works. But My Audio has id property and follow Identifiable protocol.
UPDATE: I found adding a if{} clause will fix crash, but why? Breakpoint at "static UUID.unconditionallyBridgeFromObjectiveC(:) ()".
struct Test1View: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(fetchRequest: Audio.fetchAllAudios()) var items: FetchedResults<Audio>
var body: some View {
List {
ForEach(items) { item in
if true { // <- this if clause fix crash, but why?
HStack {
Text("\(item.name)")
}
}
}.onDelete(perform: { indexSet in
let index = indexSet.first!
let item = self.items[index]
self.context.delete(item)
try? self.context.save()
})
}
}
}
code as following:
class Audio: NSManagedObject, Identifiable {
#NSManaged public var id: UUID
#NSManaged public var name: String
#NSManaged public var createAt: Date
}
struct Test1View: View {
#Environment(\.managedObjectContext) var context
var fetchRequest: FetchRequest<Audio> = FetchRequest<Audio>(entity: Audio.entity(), sortDescriptors: [NSSortDescriptor(key: "createAt", ascending: false)])
var items: FetchedResults<Audio> { fetchRequest.wrappedValue }
var body: some View {
List {
ForEach(items) { item in
HStack {
Text("\(item.name)")
}
}.onDelete(perform: { indexSet in
let index = indexSet.first!
let item = self.items[index]
self.context.delete(item)
try? self.context.save()
})
}
}
}
I had the same problem as you.
Probably it is a bad idea to use your own id property to make it Identifiable because Core Data is setting all the properties to nil when the object is deleted, even if you declared it differently in your Swift CoreData class.
When deleting your entity, the id property gets invalidated and the objects .isFault property is set to true, but the SwiftUI ForEach still holds some reference to this ID object (=your UUID) to be able to calculate the "before" and "after" state of the list and somehow tries to access it, leading to the crash.
Therefore the following recommendations:
Protect the detail view (in the ForEach loop by checking isFault:
if entity.isFault {
EmptyView()
}
else {
// your regular view body
}
Expect your id property to be nil, either by defining it accordingly in your core data model as optional
#NSManaged public var id: UUID?
or by not relying on the Identifiable protocol in the SwiftUI ForEach loop:
ForEach(entities, id: \.self) { entity in ... }
or
ForEach(entities, id: \.objectID) { entity in ... }
Conclusion: you really do not need to make all your CoreData properties Swift Optionals. It's simply important that your id property referenced in the ForEach loop handles the deletion (=setting its value to nil) gracefully.
I found the reason of crash, must provide optional, because of OC/swift object conversion:
convert
class Audio: NSManagedObject, Identifiable {
#NSManaged public var id: UUID
#NSManaged public var name: String
#NSManaged public var createAt: Date
}
to
class Audio: NSManagedObject, Identifiable {
#NSManaged public var id: UUID?
#NSManaged public var name: String?
#NSManaged public var createAt: Date?
}
I had the same issue over the weekend. It looks like SwiftUI want's to unwrap the value i read from CoreData and as the value is already deleted it crashes.
In my case i did solve it with nil coalescing on all values i use from CoreData.
You can try to provide a default value on your item.name with
ForEach(items) { item in
HStack {
Text("\(item.name ?? "")")
}
}

Setting Bool attribute of an NSManagedObject throws "Uncaught exception in iOS 10"

I am upgrading my app to iOS 10 / swift 3
So constructing an NSManagedObject with a dictionary (values received from remote db) this way...
let writerDictionary = [
...: writer.id,
...: writer.name,
...: writer.picture,
...: writer.publicationID,
...: writer.language,
...: writer.country
]
let newWriter = Writer(dictionary: writerDictionary, context: SHARED_CONTEXT)
isFavorite is an NSManaged property that I set manually to true with newWriter.isFavorite = true right after constructing the newWriter object before saving the context -
This line crashes with Uncaught Exception in iOS 10 - Previously there was no problem whatsoever with iOS 9
I have also tried newWriter.setValue(true, forKey: "isFavorite") - While it does not crash the app, the bool value isFavorite remains false unchanged.
Any ideas? This is my Writer subclass of NSManagedObject
import UIKit
import CoreData
#objc(Writer)
class Writer: NSManagedObject {
// Attributes also in the database
#NSManaged var id: NSNumber
#NSManaged var name: String
#NSManaged var picture: String?
#NSManaged var publicationID: NSNumber
#NSManaged var language: String
#NSManaged var country: String
#NSManaged var lastArticle: Date
// Attributes only in the iOS app
#NSManaged var isFavorite: Bool // Initial value is false
#NSManaged var hasNewArticles: Bool
// Relationship objects
#NSManaged var publication: Publication
#NSManaged var articles: [Article]
var writerImage: UIImage? {
get {
return ImageCache.sharedCache.imageWithIdentifier("writers-" + String(id))
}
set {
ImageCache.sharedCache.storeImage(newValue, withIdentifier: "writers-" + String(id))
}
}
override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
init(dictionary: [String : AnyObject], context: NSManagedObjectContext) {
let entity = NSEntityDescription.entity(forEntityName: "Writer", in: context)!
super.init(entity: entity, insertInto: context)
id = ...
name = ...
picture = ...
publicationID = ...
language = ...
country = ...
lastArticle = ...
}
override func prepareForDeletion() {
if let _ = writerImage {
writerImage = nil
}
}
}
Solution
The current version of Swift 3 beta seems to have some flaw about treating properties prefixed with "is".
Adjusting the name of the property like this, solved it...
#NSManaged #objc(isFavorite) var isFavorite: Bool
Fixed in Xcode 8 beta 3 compiler.
From the release notes:
Earlier Swift 3 betas implicitly renamed the property, getter, and
setter for #objc properties of type Bool whose names started with
“is”, to match the Objective-C naming conventions. (For example, a
Swift boolean property named isMagical generated an Objective-C
declaration of the form #property (nonatomic, getter=isMagical) BOOL
magical.) This turned out to cause more trouble than it was worth and
has been removed. (26847223)

NSManagedObject description in Swift

Something weird is going on with NSManagedObject.description() it prints nothing but a blank line.
import Foundation
import CoreData
#objc(MyEntity)
class MyEntity: NSManagedObject {
#NSManaged var title: String
}
Then I create an Object and set its title. When I call println("\(myObject)") it will print a blank line instead of <xSomEtHinG : MyEntity>
(the object es creates and persists ok. println("\(myObject.title)") works like expected)
Any ideas?
You can create Extension and override description property as you want.
extension MyEntity {
override public var description: String {
return "Title= \(title)"
}
}

How to use Core Data Integer 64 with Swift Int64?

I have a CoreData attribute on an entity on which I want to store integer values larger than Int32.max and UInt32.max. The value is used as an index, so lookup performance matters. So I've opted to use Integer 64 as datatype in CoreData.
Now I'm struggling on how to store an Int64 on my entity instance. See also the following different approaches I've tried.
Use NSNumber:
import Foundation
import CoreData
class Node : NSManagedObject {
#NSManaged var id : NSNumber
}
node.id = Int64(1)
> 'Int64' is not convertible to 'NSNumber'
Use NSInteger:
import Foundation
import CoreData
class Node : NSManagedObject {
#NSManaged var id : NSInteger
}
node.id = Int64(1)
> 'Int64' is not convertible to 'NSInteger'
Use Int64:
import Foundation
import CoreData
class Node : NSManagedObject {
#NSManaged var id : Int64
}
node.id = Int64(1)
> EXC_BAD_ACCESS (code=1, address=...)
How should the attribute be defined / assigned in order to use 64 bit integers?
You can define the "Integer 64" attribute as NSNumber in the managed object subclass:
#NSManaged var id : NSNumber
Setting a value:
let value:Int64 = 20000000000000000
node.id = NSNumber(longLong: value)
Retrieving the value:
let value:Int64 = node.id.longLongValue
Note that long long is a 64-bit integer on both the 32-bit and the 64-bit architecture.
Defining the property as
#NSManaged var id : Int64
// ...
node.id = Int64(...)
should also work, because Core Data supports scalar accessor methods for primitive
data types. The EXC_BAD_ACCESS exception when assigning a value looks to me
like a bug in the Swift compiler or runtime. A similar problem for a Boolean property
is reported here
EXC_BAD_ACCESS error when trying to change Bool property
where a NSNumber property is reported to work, but the scalar Bool property
causes the same exception.

How to define CoreData relationship in Swift?

In CoreData, I have defined an unordered to-many relationship from Node to Tag. I've created an Swift entity like this:
import CoreData
class Node : NSManagedObject {
#NSManaged var tags : Array<Tag>
}
Now I want to add a Tag to an instance of Node, like this:
var node = NSEntityDescription.insertNewObjectForEntityForName("Node", inManagedObjectContext: managedObjectContext) as Node
node.tags.append(tag)
However, this fails with the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-many relationship: property = "tags"; desired type = NSSet; given type = _TtCSs22ContiguousArrayStorage000000000B3440D4; value = (
"<_TtC8MotorNav3Tag: 0xb3437b0> (entity: Tag; id: 0xb343800 ; data: {...})"
).'
What is the correct type for to-many relationships?
To be able to work with one-to-many relationship in Swift you need to define property as:
class Node: NSManagedObject {
#NSManaged var tags: NSSet
}
If you try to use NSMutableSet changes will not be saved in CoreData. And of course it is recommended to define reverse link in Node:
class Tag: NSManagedObject {
#NSManaged var node: Node
}
But still Swift cannot generate dynamic accessors in runtime, so we need to define them manually. It is very convenient to define them in class extension and put in Entity+CoreData.swift file. Bellow is content of Node+CoreData.swift file:
extension Node {
func addTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.addObject(value)
}
func removeTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.removeObject(value)
}
}
Usage:
// somewhere before created/fetched node and tag entities
node.addTagObject(tag)
Important: To make it all work you should verify that class names of entities in you CoreData model includes your module name. E.g. MyProjectName.Node
As of Xcode 7 and Swift 2.0, the release note 17583057 states:
The NSManaged attribute can be used with methods as well as
properties, for access to Core Data’s automatically generated
Key-Value-Coding-compliant to-many accessors.
#NSManaged var employees: NSSet
#NSManaged func addEmployeesObject(employee: Employee)
#NSManaged func removeEmployeesObject(employee: Employee)
#NSManaged func addEmployees(employees: NSSet)
#NSManaged func removeEmployees(employees: NSSet)
These can be declared in your NSManagedObject subclass. (17583057)
So you just have to declare the following methods and CoreData will take care of the rest:
#NSManaged func addTagsObject(tag: Tag)
#NSManaged func removeTagsObject(tag: Tag)
#NSManaged func addTags(tags: NSSet)
#NSManaged func removeTags(tags: NSSet)
Actually you can just define:
#NSManaged var employees: Set<Employee>
And use the insert and remove methods of the Set directly.
Building on #Keenle's answer, if you want to be cheeky and concise and be able to say
node.tags.append(tag)
one can wrap the call to self.mutableSetValueForKey:
class Node: NSManagedObject {
var tags: NSMutableOrderedSet {
return self.mutableOrderedSetValueForKey("tags")
}
}

Resources