Swift : Core Data Integer weird values - swift

Just started learning Swift and have a question around Core Data. I have a very simple entity named "Score" with one attribute "points" which is of type Int16.
Here's my data model class:
class Score: NSManagedObject {
#NSManaged var points: Int}
Here's the code on my View Controller:
override func viewDidLoad() {
super.viewDidLoad()
let entity = NSEntityDescription.entityForName("Score", inManagedObjectContext: managedObjectContext!)
let score = Score(entity: entity!, insertIntoManagedObjectContext: managedObjectContext)
println(score.points)
}
What prints in the console is this number: -5764607523034234879
I don't understand what I'm doing wrong. I looked elsewhere and perhaps I need to do something with NSNumber, but I'm unfamiliar with that class.
Shouldn't score.points be empty right now?

It looks like you've got your questions answered, but you CAN set a default value for attributes in NSManagedObject. Just not via code.
The default value is set when you save without setting a value, so you still need to deal with the uninitialized pointer reference to a NSNumber value until it is saved.

Related

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

Retrieving NSOrderedSet from Core Data and casting it to entity managedObjectSubclasss

Im making a Fitness app to learn Core data, and I have found that I need to let the user store every performed workout as a WorkoutLog item, and in there, there should be a series of ExerciseLogs which represents performances of that exercise (it contains each lift and also a reference to the actual exercise design).
Problem is that after a while i realize that i need to have these ordered, so that the next time i want to show the user their workout, the order that the exercisese were performed should be the same.
So I checked "ordered" in the top right of the image there, and now my code is in dire need of an update. I have tried to read as much as I could about working with NSOrderedSet and how to fetch them from core data and then manipulate them, but I havent really found much of use to me. (I have no experice in objective-c)
For example my code that used to be:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
guard let exerciseLogsToDelete = workoutLogToDelete.loggedExercises as? Set<ExerciseLog> else {
print("error unwrapping logged exercises in deleteWorkoutLog")
return
}
I get the error: .../DatabaseFacade.swift:84:77: Cast from 'NSOrderedSet?' to unrelated type 'Set' always fails
So what ive learned about sets and core data no longer seems applicable.
Im far from an expert in programming, but im very eager to learn how to get access to the loggedExercises instances.
TLDR; Is there a way to cast NSOrderedSet to something I can work with? How do we usually work with NSManagedSets from core data? Do we cast them to Arrays or MutableSets? I would very much appreciate an example or two on how to get started with retrieving and using these ordered sets!
Thanks
For anyone else wondering how to get started with orderedSets in core data:
After setting my the WorkoutLog.loggedExercises "to-many" relationship to be ordered, I managed to access them through the mutableOrderedSetValue function like this:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
let orderedExerciseLogs: NSMutableOrderedSet = workoutLogToDelete.mutableOrderedSetValue(forKey: "loggedExercises")
let exerciseLogsToDelete = orderedExerciseLogs.array
for exerciseLog in exerciseLogsToDelete {
guard let exerciseLog = exerciseLog as? ExerciseLog else {
return
}
Works great so far.
And to rearrange the NSOrderedSet I ended up doing something like this:
// Swap the order of the orderedSet
if let orderedExerciseLogs: NSOrderedSet = dataSourceWorkoutLog.loggedExercises {
var exerciseLogsAsArray = orderedExerciseLogs.array as! [ExerciseLog]
let temp = exerciseLogsAsArray[indexA]
exerciseLogsAsArray[indexA] = exerciseLogsAsArray[indexB]
exerciseLogsAsArray[indexB] = temp
let exerciseLogsAsOrderedeSet = NSOrderedSet(array: exerciseLogsAsArray)
dataSourceWorkoutLog.loggedExercises = exerciseLogsAsOrderedeSet
}

Core Data is returning 0

Update: Following some advice from the comments, I am using NSManagedObject Subclassing. So my code is completely different so I am rewriting the question to better reflect what I am doing now.
So my question is: I am saving non-zero values to Core Data, but when I retrieve a value I am getting zero back. What could be wrong?
So now let's pretend I have two entities named FirstEntity, SecondEntity with two attributes named firstAttribute, secondAttribute. And since I did the NSManagedObject Subclassing I have two classes one named FirstEntity and the second named SecondEntity. Each class has an extension for the two attributes (firstAttribute, secondAttribute).
Saving Values:
So when I save a value I would use code along the lines of:
let number = 100
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
firstEntity.firstAttribute = number
Retrieving Values: When I try to receive an attribute's value in Core Data I use code like this:
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
print(firstEntity.firstAttribute) // This returns zero when I clearly saved a value of 100 above.
I am pretty new to Core Data, and I have looked up many examples of how to use Core Data online. I am still kind of confused about what I could possibly be doing wrong. If there is any clarification required just ask in the comments, and I will definitely respond.
Potential Error: So what I am doing is I am trying to save an attributes value in the completionHandler of a function. Also, if I print the variable's value inside the completionHandler I get the proper value of the attribute. For example:
someFunction() {
number in // Pretend number is 5000
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
firstEntity.firstAttribute = number
print(firstEntity.firstAttribute) // Prints 5000 to the console
}
However, let's say I go to access the firstAttribute value outside of the completionHandler and I print the value to the console I get a value of 0. For example:
class MyClass {
func myFunction() {
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
print(firstEntity.firstAttribute) // Prints 0
}
}
I also wanted to mention that I am making sure that the completionHandler in the function has set the value of the firstAttribute before calling the MyClass's myFunction().
Ok, so I think I found the answer I was creating too many instances of the entity. So what I ended up doing was I created a global constant:
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
Then I referenced firstEntity whenever I needed to retrieve or save a value so I didn't have multiple instances of FirstEntity. Then I can access the value from anywhere.

Binding Swift properties to NSTableView?

I think I have programmed myself into a corner, but I'm hoping you all know a way out. I have a class...
class Card {
var order: Int? = -1
var tag: String = "0"
var comment: String?
var data : [String: NSNumber]
}
Ideally everything would be in data, which is a few strings and lots of numbers. I started with [String, String] but I found I was writing lots of code to cast and convert when I wanted to (say) compare one of those numbers to zero. Changing it to [String, NSNumber] simplified all that code, but now my tableViewDataSource becomes very complex because some of the data is in data and some is in a separate property like comment. I even tried [String, Any], but then everything had to be cast all the time to do anything.
I have a feeling I am missing something fundamental here. When working with NSTableViews, is there a simple way to use Swift properties that I'm missing? valueForKey: does not work, there's no easy way to do a reflection-like solution I know of, etc. Any suggestions?
You can only bind dynamic properties, and your class needs to inherit from NSObject or implement NSObjectProtocol. Additionally, nilable value-types aren't allowed, so you cannot bind Int?
ie.:
class Card: NSObject {
dynamic var order: Int = -1
dynamic var tag: String = "0"
dynamic var comment: String?
dynamic var data: [String: NSNumber]
}

Swift: CoreData - unrecognized selector sent to instance when insert row

This error is strange:
-[<>.Tips setTipName:]: unrecognized selector sent to instance <>
the name setTipName does not occur anywhere in the code but there is a variable tipName (note the lower case "t"
I am attempting to insert a row into a CoreData entity
class Tips: NSManagedObject {
#NSManaged var sectionNumber: NSNumber
#NSManaged var tipDescription: String
#NSManaged var viewName: String
#NSManaged var tipName: String
#NSManaged var tipOrder: NSNumber
#NSManaged var tipType: String
#NSManaged var tipLinkName: String
}
Here is the code doing the insert:
func createNewTips ()
{
// set create all switches and set to off
var error: NSError? = nil
var textCount = textArray.count
for var i = 0; i<textCount; ++i
{
var newTip = NSEntityDescription.insertNewObjectForEntityForName("Tips", inManagedObjectContext: managedObjectContext!) as! Tips
newTip.viewName = viewName
newTip.tipName = textArray [i].tipName
NSLog("after tipName")
newTip.tipDescription = textArray[i].tipDescription
NSLog("after set tipDescription")
newTip.tipOrder = textArray[i].tipOrder
NSLog("after set tipOrder")
newTip.sectionNumber = textArray[i].sectionNumber
NSLog("after set sectionNumber")
newTip.tipType = textArray[i].type
NSLog("after set type")
newTip.tipLinkName = textArray[i].tipLinkName
var error: NSError? = nil
if !managedObjectContext!.save(&error) {
NSLog("error &d", error!)
abort()
} // end save
} // end loop
} // end of createNewSwitches
I've recreated the data model several times
I've also changed the order of the attributes and the error occurs on a different attribute .. I've noticed that it appears to be the first attribute when I move the viewName attribute later in the list.
Here is the code in textArray
var textArray:[(sectionNumber: Int, tipOrder: Int, tipName: String, tipDescription: String, type: String, tipLinkName:String)] =
[
(1,0,"sw1","Check in and around your home for damage","text",""),
(1,1,"sw2","Dispose of any spoiled or contaminated foods, especially after a power outage. If you’re not sure, throw it out. You can check the food safety tips below","text",""),
(1,2,"sw3","Encourage family members to talk about their experience and their feelings, especially children","text",""),
(1,3,"sw4","Contact other family members to let them know that you are safe","text",""),
(2,0,"sw5","Check Utilities","link",""),
(3,0,"sw6","Food Safety Tips","link",""),
]
Any suggestions about this?
The method setTipName is an auto-generated setter method created for NSManagedObject subclasses behind the scenes. It won't appear in code even if you use the modeler to create the NSManagedObject Subclass.
Core Data has to wrap all all modeled attributes in getters and setters to ensure that key-value observing, validation etc gets trigger.The naming is automatic and follows the old Objective-C convention. There will also be either a tipName or getTipName method.
I suspect you are not actually getting a Tip object back from the insertion. I'm behind the curve on Swift but I'm good with Core Data, I don't think the "as!" cast should be needed here.
var newTip = NSEntityDescription.insertNewObjectForEntityForName("Tips", inManagedObjectContext: managedObjectContext!) as! Tips
… because the compiler should be expecting a Tips object. The empty "<>" in the error message suggest that you don't in fact have a Tips object (or did you edit the error message.)
Were this Objective-C the answer to the error would definitely be that you have the wrong class returned from the insert. The most common causes of the error are:
Failing to assign a NSManagedObject subclass name in the Core Data model editor and leaving it just a generic NSManageObject
Misspelling the class name in the model e.g. "Tip", "tips", Tipps" or some such.