I have entities as so:
Entity 1
Entity 2
I am saving data as so:
#IBAction func saveButton(_ sender: UIButton) {
print("save")
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
let workout = Workout(context: context)
workout.name = workoutName.text
workout.noOfSets = Int16(setsStepper.value)
for index in 0..<setsVal {
let sets = Sets(context: context)
let test = IndexPath(row: index, section: 0)
let cell = tableView.cellForRow(at: test) as! RepsTableViewCell
sets.repAmount = Int16(cell.repsStepper.value)
// Does this line not create the relationship between Workout and Set Entities?
workout.addToSets(sets)
}
try! context.save()
}
And I am fetching data as so:
func fetch() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Workout")
request.relationshipKeyPathsForPrefetching = ["Sets"]
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "name") as! String)
print(data.value(forKey: "noOfSets") as! Int16)
}
}
catch {
print("failed")
}
}
I've set up the relationship between entities Workout and Sets as one-many, yet cannot retrieve Set's attributes from Workout.
How can I retrieve an entities relationship attributes?
Do I need to specify the relationship programmatically despite the relationship being setup in the xcdatamodel file?
Does Workout.addToSets(sets) create the relationship between the entities?
You should be able to access your relationship entities as an attribute so for instance
for workout in result as! [Workout] {
print(workout.name)
if let sets = workout.sets { //assuming you have name your to-many relationship 'sets'
print(sets.count)
}
}
Related
I need to get a NSManagedObject from Core data so I can share it with cloud Kit. I fetch the result based on the entity property and type. Then I try to convert the result into NSManagedObject.
// Fetch NSManagedObject so it can be shared
if let estProfile: NSManagedObject = fetchEntity(uniqueId: self.energyProfileId!, entityType: EstEnergyProfile.self) {
print("fetched NSManagedObject for sharing with cloud kit")
}
//Fetch NSManagedObject given specific property and its type
func fetchEntity (uniqueId: String, entityType: NSManagedObject.Type) -> NSManagedObject?{
var obj: NSManagedObject? = nil
let context = appDelegate.persistentContainer.viewContext
do {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = entityType.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "uniqueId == %#", uniqueId)
let fetchedResults = try context.fetch(fetchRequest)
obj = fetchedResults.first as NSManagedObject
}
catch {
print("Error fetching entity: ", entityType)
}
return obj
}
In the above code at the line
obj = fetchedResults.first as NSManagedObject
I get the error : 'NSFetchRequestResult?' is not convertible to 'NSManagedObject
I don't think I am doing this right. Can someone help fix this code?
I would make the fetch function generic
func fetchEntity<EntityType: NSManagedObject>(_: EntityType.Type, uniqueId: String) -> EntityType? {
let context = appDelegate.persistentContainer.viewContext
do {
let fetchRequest = EntityType.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "uniqueId == %#", uniqueId)
let fetchedResults = try context.fetch(fetchRequest)
return fetchedResults.first as? EntityType
}
catch {
print("Error fetching entity: ", error)
return nil
}
}
Example
let estProfile: NSManagedObject = fetchEntity(EstEnergyProfile.self, uniqueId: self.energyProfileId!)
Entity name :- Article
let entityInstance = Article()
I want to update its attributes, but don't know how to create its instance.
I've used this:
let entityInstance = NSEntityDescription.insertNewObject(forEntityName: "ArticleDetails", into: managedObjectContext) as? ArticleDetails
But it creates new instance instead of updating in the previous one.
To update an entity, you can try the following:
let empId = "001"
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "EmpDetails")
let predicate = NSPredicate(format: "empId = '\(empId)'")
fetchRequest.predicate = predicate
do
{
let test = try context?.fetch(fetchRequest)
if test?.count == 1
{
let objectUpdate = test![0] as! NSManagedObject
objectUpdate.setValue("newName", forKey: "name")
objectUpdate.setValue("newDepartment", forKey: "department")
objectUpdate.setValue("001", forKey: "empID")
do{
try context?.save()
}
catch
{
print(error)
}
}
}
catch
{
print(error)
}
Im using swift3. When fetching data from coredata, it returns duplicate values. Using software Datum, i understood that database only contains the original value.
class DatabaseManager: NSObject {
fileprivate static let sharedManager: DatabaseManager = DatabaseManager()
class var shared: DatabaseManager {
return sharedManager
}
/*Returns the ManagedObjectContext*/
var managedObjectContext: NSManagedObjectContext!
var privateManagedObjectContext: NSManagedObjectContext!
fileprivate var completionHandler: ((_ completed: Bool)-> Void)? = nil
override init() {
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
if let appdelegate = UIApplication.shared.delegate as? AppDelegate {
managedObjectContext = appdelegate.managedObjectContext
privateManagedObjectContext.persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator
}
}
deinit {
managedObjectContext = nil
privateManagedObjectContext = nil
}
}
//Fetching data
func getItem()->[ListItem]{
var objects = [ListItem]()
var uniqueObjects:[ListItem] = [ListItem]()
let sort = NSSortDescriptor(key: "itemName", ascending: false)
let request : NSFetchRequest<ShoppyListItem> = ShoppyListItem.fetchRequest() as NSFetchRequest<ShoppyListItem>
//let predicate = NSPredicate(format:"excludedIDContain = %#","New")
// request.predicate = predicate
request.sortDescriptors = [sort]
do {
if objects.count > 0 {
objects.removeAll()
}
objects = try managedObjectContext?.fetch(request) ?? []
return objects
} catch {
print("Error with request: \(error)")
}
return objects
}
// objects = try managedObjectContext?.fetch(request) ?? [] returns duplicated objects
i got it. Im not mistaken about the count. It was due to concurrency. i was not running fetch on the safe thread of coredata. All i had to do was put the code inside perform block.
managedObjectContext.perform(block).
Got this from stanford ios tutorial named coredata demo. Video time 26:00. The professor explains this.
I had a function that cleared all objects from given entity, in swift 2:
private static func clearTable(tableName : String)
{
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
let context : NSManagedObjectContext = appDel.managedObjectContext
let request = appDel.persistentStoreCoordinator
let fetchRequest = NSFetchRequest(entityName: tableName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try request.executeRequest(deleteRequest, withContext: context)
} catch let error as NSError {
debugPrint(error)
}
}
recently i migrated to swift 3 and now it looks like this:
static func clearTable(_ tableName : String)
{
let appDel = UIApplication.shared.delegate as! AppDelegate
//let context : NSManagedObjectContext = appDel.managedObjectContext
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest(entityName: tableName)
let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: tableName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: NSFetchRequest(entityName: tableName))
do {
try request.execute(deleteRequest, with: context)
} catch let error as NSError {
debugPrint(error)
}
}
As i understood, now i have to declare request and fetchRequest like
let fetchRequest: NSFetchRequest<SomeEntity> = NSFetchRequest(entityName: "SomeEntity")
The problem is that i don't know the entity beforehand. Is there any workaround or reflexion in swift 3? And i'm new to swift and core data, is this the normal way to fetch or delete objects?
All result types in Core Data including NSManagedObject conform to NSFetchRequestResult so use that as type
static func clearTable(_ tableName : String)
{
let appDel = UIApplication.shared.delegate as! AppDelegate
let context = appDel.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: tableName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
let persistentStoreCoordinator = context.persistentStoreCoordinator!
do {
try persistentStoreCoordinator.execute(deleteRequest, with: context)
} catch let error as NSError {
debugPrint(error)
}
}
This is the recommended way to delete all items of an entity.
However the deployment target must be ≥ iOS 9 / macOS 10.11.
PS: In Swift 3 I'd declare the function
static func clear(table tableName : String) { ...
Checkout this batch delete example for a better way.
I use the CodeDataStack pattern for separation of concern to make the code even more clean.
You can use extension to make your code more dynamic. Instead of passing the table name, you can simply invoke model.delete() or Model.deleteAll(). This is a common pattern used in other web frameworks.
ModelExtension.swift
extension NSManagedObject {
static func context() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.coreData.context()
}
public func delete() {
managedObjectContext?.delete(self)
}
public static func deleteAll() {
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: self.fetchRequest())
do {
try context().execute(batchDeleteRequest)
} catch {
// Error Handling
}
}
}
Unit Test:
// My entity name is `Location`
func test_deleteAll() {
_ = fixture.buildTestLocation()
_ = fixture.buildTestLocation()
coreData.saveContext()
Location.deleteAll()
XCTAssertEqual(0, Location.all()?.count)
}
func test_delete() {
let location = fixture.createTestLocation()
XCTAssertEqual(1, Location.all()?.count)
location.delete()
coreData.saveContext()
XCTAssertEqual(0, Location.all()?.count)
}
Note that Location.all() is a custom method.
I have seen many examples with one view controller for adding or updating core data items. Any thoughts on pros or cons of doing in separate view controllers?
My code for trying to do the update I think I am missing one key part to get it to work.
#IBAction func saveItem(sender: AnyObject) {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Items", inManagedObjectContext: context)
var existingItem = dataModel.self
if (row > 0) {
println(teaname.text)
existingItem.setValue(teaname.text as String, forKey: "name")
existingItem.setValue(teatype.text as String, forKey: "type")
existingItem.setValue(qty.text as String, forKey: "amount")
existingItem.setValue(temp.text as String, forKey: "temp")
existingItem.setValue(time.text as String, forKey: "time")
} else {
}
context.save(nil)
self.navigationController?.popViewControllerAnimated(true)
}
I get (lldb) with a thread breakpoint at existingItem.setValue(teaname.text as String, forKey: "name")
It does not appear you actually have a specific object to update. I use the following function to fetch an object by its unique ID. Only once you have an object (mine is called Event) can you update it.
func fetchEvent(eventID: Int) -> Event? {
// Define fetch request/predicate/sort descriptors
var fetchRequest = NSFetchRequest(entityName: "Event")
let sortSections = NSSortDescriptor(key: "eTitle", ascending: true)
let sortDescriptor = NSSortDescriptor(key: "eID", ascending: true)
let predicate = NSPredicate(format: "eID == \(eventID)", argumentArray: nil)
var error = NSErrorPointer()
// Assign fetch request properties
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [sortSections, sortDescriptor]
fetchRequest.fetchBatchSize = 1
fetchRequest.fetchLimit = 1
// Handle results
let fetchedResults = managedObjectContext?.executeFetchRequest(fetchRequest, error: error)
if fetchedResults?.count != 0 {
if let fetchedEvent: Event = fetchedResults![0] as? Event {
println("Fetched object with ID = \(eventID). The title of this object is '\(fetchedEvent.eTitle)'")
return fetchedEvent
}
}
return nil
}
Once you have fetched an object and have a core data object to update, then you can update it like so.
func updateEvent(eventDict: Dictionary<String, AnyObject>, id: Int) {
if let event: Event = fetchEvent(id) {
println(event)
event.eID = id
event.eTitle = getString(eventDict["title"])
event.eLocation = getString(eventDict["location"])
event.eDescription = getString(eventDict["description"])
event.eStart = getDate(eventDict["startDate"])
event.eEnd = getDate(eventDict["endDate"])
event.eMod = NSDate()
event.eSecID = getSecID(event)
}
}
And then you may want to save your managed object context.