I have entities Application and Process, one application can have many processes, but one process can only have one application. I get one specific application entity:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let appfetchRequest = NSFetchRequest(entityName: "Application")
//appfetchRequest.returnsObjectsAsFaults = false
do{
let results = try managedContext.executeFetchRequest(appfetchRequest)
applications.addObjectsFromArray(results as! [NSMutableArray])
}catch let error as NSError{
print("Jakis blad z applications entity: \(error)")
}
let predicate:NSPredicate = NSPredicate(format: "name == %#", appTitle)
let application:NSArray = applications.filteredArrayUsingPredicate(predicate)
and in this I have relationship (named applicationprocesses).
I try to get an array with these entities in many ways, but no one work.
Actually, I have:
let processes = application.valueForKey("applicationprocesses").allObjects.first
print(processes?.valueForKey("applicationprocesses"))
And this give me:
Optional({(
<NSManagedObject: 0x7f945871b7a0> (entity: Application; id: 0xd000000000080002 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Application/p2> ; data: {
appcolor = "#3F3F3F";
appicon = bed;
applabel = Proces;
applabelplural = Procesy;
applicationprocesses = (
"0xd000000000140004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p5>",
"0xd000000000100004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p4>",
"0xd000000000180004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p6>",
"0xd0000000001c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p7>",
"0xd0000000000c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p3>"
);
companyid = 392;
id = 1261;
name = "aplikacja 1";
processescount = 5;
})
)})
I need to display these processes in a UITablewView, so I need an array.
I will be grateful for any help.
The problem you are having is not the result of a bad line of code somewhere. It is actually working as it is supposed to. But you can make it a lot easier to work with NSManagedObject
Any fetch from your database results in [AnyObject]. If you leave it like it is, you are forced to use key-value coding which is a pain and very easy to mess up.
However it is very simple to create Classes from CD Entities and downcast the fetch result. This is an awesome feature of CoreData that unfortunately is not stressed enough.
link to gist
Your related entities might look like this:
Go to Menu -> Editor -> Create....
Select the entities you want to create a subclass for.
New files will show up in your project :
Now you can use code like this :
Insert objects
Notice the .... as? Company this is the downcast.
It allows you to access the attributes from the CD Entity like you would access any attributes from a Struct or Class.
func createACompany() {
// no appDel here. appDel and managedContext are best declared in the class scope. See gist for entire ViewController
guard let moc = managedContext else { return }
guard let company = NSEntityDescription.insertNewObjectForEntityForName("Company", inManagedObjectContext: moc) as? Company else {
return // guard is handy to "abort"
}
company.name = "Apple"
guard let bob = NSEntityDescription.insertNewObjectForEntityForName("Employee", inManagedObjectContext: moc) as? Employee else {
return
}
bob.name = "Bob"
bob.company = company
do {
try moc.save()
} catch {
print("core data save error : \(error)")
}
moc.refreshAllObjects()
}
Fetch objects
func fetchCompanies() -> [Company] {
guard let moc = managedContext else { return [] }
let request = NSFetchRequest(entityName: "Company")
request.returnsObjectsAsFaults = true
do {
let results = try moc.executeFetchRequest(request)
guard let companies = results as? [Company] else {
return []
}
return companies
}catch let error as NSError{
print("core data fetch error: \(error)")
return []
}
}
Get related objects
Look closely at the guard statement.
let employeeSet = company.employees -> unwrap of optional NSSet
let employees = employeeSet.allObjects -> get all objects in the NSSet
as? [Employee] -> downcast the result of allObjects to an Array
of Employee
func getEmployees(forCompany company : Company) -> [Employee] {
guard let employeeSet = company.employees, let employees = employeeSet.allObjects as? [Employee] else {
return []
}
return employees
}
Side note:
If you change your mind about the naming of the class and you change it everywhere. Don't forget the update it here too :
The class field has to be updated.
Similar to the InterfaceBuilder. If you change the class name it will not find it anymore.
I believe that you want to get all the managed objects (process) relate to the application object you already got. We usually use the "entity" word for same things as table in database.
I'm sorry I didn't have swift version for this line of code. I'm not sure about my swift skill
NSArray *processes = [[application fristObject] objectIDsForRelationshipNamed:#"applicationprocesses"]
Then use API to convert objectID to ManagedObject
(__kindof NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID;
The only problem is this API, (NSArray *)objectIDsForRelationshipNamed:(NSString *)key , only supported from version 8.3
Related
I'm new to Core Data, and while it is slowly making sense, I've a problem loading my initial database when I leave 'optional' relationship values without values (isn't that what optionals do?). The following code WORKS, but not if I leave out some cases that instantiate and define a relationship (exact detail below).
I'm using a class to load the data via a for loop that assigns the initialising values and passes them to a method that effectively loads the data. Firstly, to load an Entity called Gender.
class LoadData
{
init ()
{
for count in 0...3
{
var text = "Value not found"
let sortOrder: Int32 = Int32(count)
switch count
{
case 0:
text = "Masculine"
case 1:
text = "Neutral"
case 2:
text = "Feminine"
case 3:
text = "Plural"
default:
text = "Value unassigned"
}
loadGender(text: text, sortOrder: sortOrder)
}
In the same class and init, a separate for loop defines values and passes to save the context in the Entity, CellData. For CellData entries defined in cases 0 to 27 and 28 to 51, I use a predicate to instantiate a specific Gender entry...
for count in 0...51
{
var text = "Cell text not found"
...
var dataToGender: Gender? = nil
switch count
{
case 0...27: // HERE - if case 1...27, data will NOT load
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate
else { return }
let moc = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<Gender>(entityName: "Gender")
let genderToFetch = "Masculine"
request.predicate = NSPredicate(format: "text == %#", genderToFetch)
do
{
let fetched = try moc.fetch(request)
dataToGender = fetched[0] as Gender
} catch
{ fatalError("Failed to fetch employees: \(error)") }
case 28...51:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate
else { return } // get the app delegate
let moc = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<Gender>(entityName: "Gender")
let genderToFetch = "Feminine"
request.predicate = NSPredicate(format: "text == %#", genderToFetch)
do
{
let fetched = try moc.fetch(request)
dataToGender = fetched[0] as Gender
} catch
{ fatalError("Failed to fetch employees: \(error)") }
default: return
}
...
... which is passed into the method to put the data into the context and saved to Core Data.
func loadCellData(text: String, sortOrder: Int32, portion: Float, color1: String?, color2: String?, color3: String?, colorRangeLoc1: Int32?, colorRangeLoc2: Int32?, colorRangeLoc3: Int32?, colorRangeLen1: Int32?, colorRangeLen2: Int32?, colorRangeLen3: Int32?, underlineRangeLoc1: Int32?, underlineRangeLoc2: Int32?, underlineRangeLoc3: Int32?, underlineRangeLen1: Int32?, underlineRangeLen2: Int32?, underlineRangeLen3: Int32?, dataToGender: NSObject?)
{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entry: CellData = NSEntityDescription.insertNewObject(forEntityName: "CellData", into: context) as! CellData
...
entry.text = text
...
entry.dataToGender = dataToGender as? Gender
do { try context.save() ; print("saved")}
catch let error as NSError
{ print("Could not save. \(error), \(error.userInfo)") }
}
Amazingly, this works fine - but only if I define a relationship for all CellData entries. However, I do not want all entries to have a relationship (see comment // HERE). Cases 0...7 should not have a relationship, but if no value is assigned, the code executes fine - until calling on a DB value. On inspection, Gender enters fine, but no CellData entries were entered into the DB, so it crashes with no object found at index 0 section 0.
There are no errors in log; and I can print inside the loadCellData!?! What is going on?!?
Basically, an optional value in CoreData is not the same as a Swift optional value. Integers should never be optional, and maybe relationships also. To get around this, for integers at least, an impossible value should be assigned instead of nil, and used to check if a value is assigned.
While I understand it is expensive, I did not find a way to assign a relationship to an existing record in CoreData without fetching the record individually.
in my ios swift application I have a database using Core Data.
It has many entities, all entities have an integer field called syncStatus. it can be 0, 1, or 2.
On startup, I want to loop through ALL the entities that have syncStatus = 1 and change it to 0
Is there a way to do it without fetching each type alone and changing it?
So what I want is:
fetch ALL entities with syncStatus = 1
Loop through them and set syncStatus = 0
Currently I'm doing them one by one:
fetch UserEntities with syncStatus = 1
Loop through them and set syncStatus = 0
fetch countryEntities with syncStatus = 1
Loop through them and set syncStatus = 0
Do the same for every entity one by one
code:
let allUsers = context?.fetch(FetchRequest<UserEntity>().filtered(with: "syncStatus", equalTo: "1"))
let allCountries = context?.fetch(FetchRequest<CountryEntity>().filtered(with: "syncStatus", equalTo: "1"))
.
.
.
I'm just trying to find a generic approach, in case later we add another entity/table we don't have to come back to this code and add it here also.
First of all, fetching all entries and filter them is much more expensive than applying a predicate.
I recommend to use a protocol extension with static methods. The benefit is that you can call the methods directly on the type
protocol SyncStatusResettable
{
associatedtype Entity: NSManagedObject = Self
var syncStatus : String {get set}
static var entityName : String { get }
static func resetSyncStatus(in context: NSManagedObjectContext) throws
}
extension SyncStatusResettable where Entity == Self
{
static var entityName : String {
return NSStringFromClass(self).components(separatedBy: ".").last!
}
static func resetSyncStatus(in context: NSManagedObjectContext) throws
{
let request = NSFetchRequest<Entity>(entityName: entityName)
request.predicate = NSPredicate(format: "syncStatus == 1")
let items = try context.fetch(request)
for var item in items { item.syncStatus = "0" }
if context.hasChanges { try context.save() }
}
}
To use it, adopt SyncStatusResettable for all NSManagedObject subclasses and call
do {
try UserEntity.resetSyncStatus(in: managedObjectContext)
try CountryEntity.resetSyncStatus(in: managedObjectContext)
} catch { print(error) }
managedObjectContext is the current NSManagedObjectContext instance
NSManagedObjectModel allows you to enumerate through the entities it contains, and NSEntityDescription can give you properties for each entity. Once you have a reference to the model:
let entitiesWithSync = model.entities.filter {
$0.properties.contains(where: { $0.name == "syncStatus" })
}
Will give you all of the relevant entities. You can then use this list of entities to drive your updates - note that using NSBatchUpdateRequest is faster if you're doing this on startup. You can create batch update requests using the entity descriptions obtained in the loop above.
In the past I have looped through all the entitiesByName from the object model:
lazy var managedObjectModel: NSManagedObjectModel = {
let modelUrl = NSBundle.mainBundle().URLForResource("SomeProject", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelUrl)
}
func updateAllData() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
context.performAndWait {
let allEntities = self.managedObjectModel.entitiesByName
for (entity, items) in allEntities {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
...
}
}
}
I am adding a newly created user exercise to an existing user routine via a save command. To explain the code below, I set the objectcontext, then is the exercise is new (userExercise is nil) I execute a new save block.
I then check if this new exercise is being added to an existing routine (associatedRoutineToAddTo is a string routine name from the previous VC).
This is where the issue is. I attempt to get the UserRoutine object that it needs to be added to, using a predicate based on the routines name from the string i passed from the previous VC. then i attempt to add the exercise to the routine. This causes the crash.
The rest of the code isnt too relevant as it works fine in terms of saving an edit or new exercise with no parent routine, its just this one part.
Here is what I am trying:
func getMainContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func createExercise() {
print("SAVE EXERCISE PRESSED")
let managedObjectContext = getMainContext()
if userExercise == nil {
print("SAVING THE NEW EXERCISE")
let newUserExercise = UserExercise(context: self.managedObjectContext!)
newUserExercise.name = userExerciseName.text
newUserExercise.sets = Int64(userSetsCount)
newUserExercise.reps = Int64(userRepsCount)
newUserExercise.weight = Double(self.userExerciseWeight.text!)!
newUserExercise.dateCreated = NSDate()
if self.associatedRoutineToAddTo != nil {
let existingUserRoutine = UserRoutine(context: managedObjectContext)
let request: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
request.predicate = NSPredicate(format: "usersroutine.name == %#", self.associatedRoutineToAddTo!)
existingUserRoutine.addToUserexercises(newUserExercise)
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
} else if self.associatedRoutineToAddTo == nil {
print("THIS IS A FRESH EXERCISE WITHOUT A PARENT ROUTINE")
}
}
The error reads:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'usersroutine' between objects in different contexts (source = <UserExercise: 0x618000280e60>
edit: revised my fetch code for review:
let fetchRequest: NSFetchRequest<UserRoutine> = UserRoutine.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "usersroutine.name == %#", self.associatedRoutineToAddTo!)
do {
existingUserRoutine = try managedObjectContext.fetch(fetchRequest) as! UserRoutine
print("Routine Below Fetched")
print(existingUserRoutine)
} catch {
print("Fetch Failed")
}
existingUserRoutine.addToUserexercises(newUserExercise)
You create the object here:
let newUserExercise = UserExercise(context: self.managedObjectContext!)
And fetch the related object here:
let existingUserRoutine = UserRoutine(context: managedObjectContext)
You've created a local variable called managedObjectContext here:
let managedObjectContext = getMainContext()
And your error states:
attempt to establish a relationship 'usersroutine' between objects in different contexts
Therefore, your property managedObjectContext is not the same as that returned by getMainContext()
In addition to all that, you're creating a brand new UserRoutine, and assigning it to a value called existingRoutine, then creating a fetch request that you don't do anything with, which suggests you're a little confused about what is supposed to be happening here.
WHen you make UserExcercise you are referencing a context saved on your VC:
let newUserExercise = UserExercise(context: self.managedObjectContext!)
Then
let existingUserRoutine = UserRoutine(context: managedObjectContext)
The getMainContext() acquire context here?
I am fetching the entities from Core Data, but I can´t access the object values to assign a variable etc.
This is my code:
#IBAction func loadItem(sender: UIButton) {
let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context: NSManagedObjectContext = appDel.managedObjectContext
let request = NSFetchRequest(entityName: "Words")
request.returnsObjectsAsFaults = false
do {
let results:NSArray = try context.executeFetchRequest(request)
for res in results{
print(res)
print(res.word)
}
} catch {
print("Unresolved error")
abort()
}
}
print(res) works fine and give me the object in the console:
<NSManagedObject: 0x7ff0095355f0> (entity: Words; id: 0xd000000000040000 <x-coredata://0E645702-9493-4D1A-8D55-4482B7948054/Words/p1> ; data: {
image = <89504e47 0d0a1a0a 0000000d 49484452 00000a6c 000006ec 08020000 009fcbb9 fc000001 18694343 50494343 2050726f 6669>;
word = test;
})
But how can I access for instance the "word" value? It only returns this error:
"ViewController.swift:36:23: Value of type 'Element' (aka 'AnyObject') has no member 'word'"
I´m trying to cast "res" to be a NSManagedObject, but I can´t seem to make it work.
Any help?
You have two options. Either use Key-Value Coding:
let results = try context.executeFetchRequest(request)
for res in results {
print(res)
print(res.valueForKey("word"))
}
Or (better) use "Xcode->Editor->Create NSManagedObject subclass ...".
In Xcode 7 this will add two files "Words.swift" and "Words+CoreDataProperties.swift" to your project.
The first file
"Words.swift" defined the Word class (and you can extend the class
definition e.g. to add custom methods).
The second file "Words+CoreDataProperties.swift"
contains property definitions for all your Core Data properties,
for example:
extension Words {
#NSManaged var word: String?
// ...
}
And now you can cast the objects from the fetch request to the Word type and access the properties directly:
let results = try context.executeFetchRequest(request) as! [Words]
for res in results {
print(res)
print(res.word)
}
I'd like to update a CoreData Object.
Backgrund: I made an app which includes a UITableView. In the textLabel of the UITableViewCell is a name. In the detailTextLabel of this cell is a date which can be changed/updated. Now I'd like to change this date.
I wrote the following code:
var people = [NSManagedObject]()
func saveDate(date: NSDate) {
//1
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext:managedContext)
let person = people[dateIndexPath.row]
//3
person.setValue(date, forKey: "datum")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
people.append(person)
tableView.reloadData()
}
Now, if I run this code:
The date is successfully updated but the cell in which the date has been updated is displayed 2 times. For example if I added 3 cells and changed the date in the 3rd cell, I now get 4 cells displayed and 2 of them have the same content/are duplicated.
Does someone knows how to solve this problem?
You're adding an additional object to your array each time. Your updated Person is already in the array and will display the new information when you reload your table data. To fix this, just take out this line:
people.append(person)
You're going to want to associate some kind of unique identifier attribute to your Person class. This allows to retrieve that same object later using it identifier. I would suggest using a UUID string value, called personID, identifier, or something similar.
You can override the awakeFromInsert method on your Person class like so:
// This is called when a new Person is inserted into a context
override func awakeFromInsert()
{
super.awakeFromInsert()
// Automatically assign a randomly-generated UUID
self.identifier = NSUUID().UUIDString
}
When you want to edit an existing Person, you want to retrieve it by the UUID. I suggest a class function like so (in the Person class):
class func personWithIdentifier(identifier: String, inContext context: NSManagedObjectContext) -> Person?
{
let fetchRequest = NSFetchRequest(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "identifier ==[c] %#", identifier)
fetchRequest.fetchLimit = 1 // Only want the first result
var error : NSError?
let results = context.executeFetchRequest(fetchRequest, error: &error) as [Person]
// Handle error here
return results.first?
}
This way you can use the following function:
let identifier = ...
let context = ...
var person = Person.personWithIdentifier(identifier, inContext: context)
if let person = person
{
// Edit the person
person.value = // change the values as you need
}
else
{
// Person does not exist!
person = // possibly create a person?
}