I'm trying to write and read with core data in SWIFT. When it comes to writing new data and reading existing data everything is ok. But now I want to update a record (lets say where "pagina" is "1") and suddenly I feel completely lost! I've looked into batch update, since it seems that's the best way to do it. Unfortunately none of the tutorials I've found seem to be basic enough for me.
I've created the entity "Stories" and added three attributes: "pagina" (String), "plaats" (String) and "naam" (String). The way I was going to do this was by retrieving the Page number from txtPagina.text and storing it in the variable Pagina.
Then updating the records with the UITextField contents of txtPlaats.text and txtNaam.text where pagina = Pagina. It seems simple enough. Unfortunately I can't seem figure out batch updating.
Can anyone help me? I'm totally lost here.
#IBAction func btnSave(){
var Pagina = txtPagina.text
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
//check if data exists
var request = NSFetchRequest(entityName: "Stories")
var results:NSArray = context.executeFetchRequest(request, error:nil)!
if(results.count > 0) {
Pagina = "\(results.count)"
txtPagina.text = Pagina
//code to update plaats and naam where pagina = Pagina
} else {
var newStory = NSEntityDescription.insertNewObjectForEntityForName("Stories", inManagedObjectContext: context) as NSManagedObject
newStory.setValue(Pagina, forKey: "pagina")
newStory.setValue("\(txtPlaats.text)", forKey: "plaats")
newStory.setValue("\(txtNaam.text)", forKey: "naam")
context.save(nil)
println(newStory)
println("Object Saved.")
}
}
If the only problem you're having is updating a record, it looks as though your problem is that you aren't making a call to context.save() after updating it. This should solve that problem.
I've got something working right now, but I'm not sure it's the most efficient way:
I've created a class called Stories:
import UIKit
import CoreData
#objc(Stories)
class Stories: NSManagedObject {
#NSManaged var pagina:String
#NSManaged var plaats:String
#NSManaged var naam:String
}
Now my btnSave action is this:
#IBAction func brnSave(sender: AnyObject) {
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Stories")
var results:NSArray = context.executeFetchRequest(request, error: nil) as [Stories]
if (results.count > 0) {
println(results.count)
if (results.count >= txtPagina.text.toInt()) {
for res in results{
var data = res as Stories
println(data.plaats)
if (data.pagina == txtPagina.text) {
data.plaats = txtPlaats.text
data.naam = txtNaam.text
context.save(nil)
println("Page \(data.pagina) updated")
}
}
} else {
// Save new Story
var ent = NSEntityDescription.entityForName("Stories", inManagedObjectContext: context)
var newStory = Stories(entity: ent!, insertIntoManagedObjectContext: context)
newStory.pagina = txtPagina.text
newStory.plaats = txtPlaats.text
newStory.naam = txtNaam.text
context.save(nil)
println("New story saved")
// Save new Story end
}
} else {
// Save new Story
var ent = NSEntityDescription.entityForName("Stories", inManagedObjectContext: context)
var newStory = Stories(entity: ent!, insertIntoManagedObjectContext: context)
newStory.pagina = txtPagina.text
newStory.plaats = txtPlaats.text
newStory.naam = txtNaam.text
context.save(nil)
println(newStory)
println("New story saved")
// Save new Story end
}
}
The code works, but doesn't seem perfect.
Is there a way to make this more efficient? When updating the array I'm basically rewriting the whole coredata table instead of just updating the changed data. I'm can only imagine what this does performance wise when there's a lot more data!
Related
In my swift code below I am trying to save ints to core data. Every time a user hits a button a new int is created. So if the user hits the button twice there are know 2 int entities in core data. My code below is having a runtime error and I dont know how to solve it.
pic
var pageNumber = 0
var itemName : [NSManagedObject] = []
func enterData() {
let appDeldeaget = UIApplication.shared.delegate as! AppDelegate
let context = appDeldeaget.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Player", in: context)
let theTitle = NSManagedObject(entity: entity!, insertInto: context)
theTitle.setValue(pageNumber, forKey: "positon")
do {
try context.save()
itemName.append(theTitle)
pageNumber += 1
}
catch {
}
self.theScores.reloadData()
positionTextField.text = ""
positionTextField.resignFirstResponder()
}
You are introducing a few complications that might be causing the issue.
First, if I understood your purpose, the itemName should not be an array of NSManagedObject, but rather an array of Player. Also, creating the theTitle can be simplified.
Try this instead of the code you proposed:
var pageNumber = 0
// If I understand correctly, you should have an array of Players
var itemName : [Player] = []
func enterData() {
let appDeldeaget = UIApplication.shared.delegate as! AppDelegate
let context = appDeldeaget.persistentContainer.viewContext
// Simpler way to create a new Core Data object
let theTitle = Player(context: context)
// Simpler way to set the position attribute
theTitle.position = pageNumber // pageNumber must be of type Int64, otherwise use Int64(pageNumber)
do {
try context.save()
itemName.append(theTitle)
pageNumber += 1
} catch {
// handle errors
}
// rest of the code
}
i've tried saving the data of my tableview using core data however i haven't been able to get it to work with the way i've setup my code.
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskCell
cell.taskText.text = tasks[indexPath.row].name
cell.taskPriority.image = tasks[indexPath.row].priority
return cell
where tasks is
var tasks = [Task]()
and it looks like this
class Task {
var name = ""
var priority = UIImage()
var priorityInt = Int()
convenience init(priority: UIImage, name: String, priorityInt: Int) {
self.init()
self.name = name
self.priority = priority
self.priorityInt = priorityInt
PriorityInt is used to change the image which determines the priority of the task.
Leja, "Save data from a table view in the CoreData" doesn't make sense but I will try answering by talking about 2 things.
Saving on CoreData
Create your Data Model with your Task model.
Then you will end up having your Task class inheriting from NSManagedObject like this:
import CoreData
import Foundation
class TaskMO: NSManagedObject {
#NSManaged var name: String?
#NSManaged var priority: Int?
}
You will use NSManagedObjectContext to save your entity with the following code:
guard let task = NSEntityDescription.insertNewObjectForEntityForName("Task", inManagedObjectContext: managedObjectContext) as? TaskMO else { return
do {
try managedObjectContext.save()
} catch {
fatalError("Failed to save: \(error)")
}
Show local data on TableView or CollectionView
let managedObjectContext = …
let tasksFetch = NSFetchRequest(entityName: "Task")
do {
let fetchedTasks = try managedObjectContext.executeFetchRequest(tasksFetch) as! [TaskMO]
} catch {
fatalError("Failed to fetch tasks: \(error)")
}
For a huge amount of data, you can even use NSFetchedResultsController
You can read more about, samples and explanation are well documented. apple documentation here.
I am trying to get the first or current exercise from my core data but swift keeps telling me that the element is empty. When i run the app and set the break points the debugger shows that the element is empty but no errors. here are the functions i am using to get the element data.
func currentWorkout() -> Workout? {
let client = currentClient()
return (appointment?.workouts as? Set<Workout>)?.first(where: { $0.client == client })
}
private func currentCard() -> Card? {
return currentWorkout()?.card
}
private func currentClientPlannedExercises() -> [ExerciseInfo] {
if let currentCard = currentCard(), let template = currentCard.template, let exerciseSets = template.exerciseSets?.array as? [ExerciseSet] {
let numCardsWithThis = (template.cardsWithThisTemplate as? Set<Card>)?.filter { $0.client != currentClient() }.count ?? 0
let exercsiseSetNumber = numCardsWithThis % exerciseSets.count
if let result = exerciseSets[exercsiseSetNumber].exercises?.array as? [ExerciseInfo] {
return result
}
}
return [ExerciseInfo]()
}
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo(), let currentCard = currentCard() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>{
return exercises.first(where: { $0.exerciseInfo == selectedExercise })
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}
private func currentExerciseInfo() -> ExerciseInfo? {
guard let selectedRow = exercisesTableView.indexPathForSelectedRow else {
return nil
}
return currentClientPlannedExercises()[selectedRow.row]
}
if the Issue is fetching then You can use this Code:
For Fetching the data from Core Data
var tasks: [Task] = [] //Your Entity Name in Bracket
func getData() {
do {
tasks = try context.fetch(Task.fetchRequest()) //Instead of Task your Entity Name
} catch {
print("Fetching Failed")
}
}
And use it like:
for data in tasks{
print(data.name) // All the Attributes name after data.attributename
print(data.address)
}
If it is in tableView:
let data = tasks[indexPath.row]
print(data.name)
You will get the data if it is there.
Edit to Check if Data entered or not
Print the Path like this:
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
print(paths[0])
Go to sqlite file and open and check if there is Data or not inside that.
Edit If you are facing the issue in Adding Data to Core Data
Simple code to add Data
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context) //Entity Name here instead of Task
task.name = taskTextField.text! // Attribute name here after task.attributename
// Save the data to coredata
(UIApplication.shared.delegate as! AppDelegate).saveContext()
Hope this help.
I found the issue was in the currentExercise function it wasn't calling the first exercise until the it had an exercise. I fixed by rewriting the function
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>, let firstExercise = exercises.first(where: { $0.exerciseInfo == selectedExercise }) {
return firstExercise
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}
I have an entity in my "ProjName.xcdatamodel" with the name "Questions". In this entity I have 5 attributes ("icehockey","volleyball","soccer",...), each with type transformable. Each row (attribute) will be filled with a NSMutableArray.
What I want to do is to get the value of a specific attribute in this entity. This is my code:
func readQuestionsFromCore(sport:NSString) -> NSMutableArray {
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Questions")
request.returnsObjectsAsFaults = false
var results: NSArray = context.executeFetchRequest(request, error: nil)!
var qArr:NSMutableArray!
if results.count > 0 {
var res = results[0] as NSManagedObject
qArr = res.valueForKey("\(sport)") as NSMutableArray
return qArr
} else {
qArr = []
return qArr
}
}
This will ofcourse not work since I take out the first index of the results from the database (results[0] as NSManagedObject) and thus it will crash if that element is not the same as the valueForKey I'm looking for.
How do I get the one result row that I'm looking for? I.e. "soccer", or at least can I somehow loop through the results and compare the keys of each result row so it doesn't crash when I try with the wrong key? Like something like this:
for (res) in results as NSManagedObject {
if(res.key == "soccer") {
qArr = res.valueForKey("soccer") as NSMutableArray
return qArr
}
}
I hope I'm clear in my explanation!
the valueForKey method returns an optional, you can use if let as below
if let questionsArr = res.valueForKey("\(sport)") as? NSMutableArray {
return questionsArr
} else {
return []
}
This works in Xcode 6.3.2, but looks like you are using older one. If so update to latest one.
I'm not sure I clearly understand what you are trying to achieve. But using next function(that using KVC) you can get a list of class properties and than check if the one you need is there:
func getPropertyList(#classToInspect: AnyObject.Type) -> [String]
{
var count : UInt32 = 0
let properties : UnsafeMutablePointer <objc_property_t> = class_copyPropertyList(classToInspect, &count)
var propertyNames : [String] = []
let intCount = Int(count)
for var i = 0; i < intCount; i++ {
let property : objc_property_t = properties[i]
let propertyName = NSString(UTF8String: property_getName(property))!
propertyNames.append(propertyName as String)
}
free(properties)
println(propertyNames)
return propertyNames
}
I don't really know what I have to explain or not, don't hesitate to ask me more code or explanations if needed..
I'm trying to use a CoreData to stock datas gotten from an http POST request and then print them on an UITableView.
I successfully get datas from the JSON and send them to the database. The problem is when I try to send the datas from the database to the UITableView.
It's my first time with the Core Data, so to understand how it works, I have followed this tutorial I adapted to my situation: https://www.youtube.com/watch?v=UniafUWsvLg
This is the Entity in which I'm working:
import Foundation
import CoreData
class Task: NSManagedObject {
#NSManaged var summary: String
#NSManaged var status: String
#NSManaged var responsable: String
#NSManaged var id: String
#NSManaged var detail: String
#NSManaged var date: String
#NSManaged var context: String
}
This is a part of the code preparing the work on the CoreData, I have some comments on it:
//Preparing variables used to get and send datas from DB
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var nTask: Task? = nil
var frc : NSFetchedResultsController = NSFetchedResultsController()
func getFetchedResultsController() -> NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: taskFetchRequest(), managedObjectContext: context!, sectionNameKeyPath: nil, cacheName: nil)
return frc
}
func taskFetchRequest() -> NSFetchRequest {
//On which Entity are we working?
let fetchRequest = NSFetchRequest(entityName: "Task")
//Which attribute get the Order by. There summary as Ascending
let sortDescriptor = NSSortDescriptor(key: "summary", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
Now I have declared this, I set on the viewDidLoad the delegate of the getFetchedResultsController to self:
override func viewDidLoad() {
super.viewDidLoad()
frc = getFetchedResultsController()
frc.delegate = self
frc.performFetch(nil)
}
This is how I create the link to the database to get datas from:
//Link creation to SQLite DB
let context = self.context
let ent = NSEntityDescription.entityForName("Task", inManagedObjectContext: context!)
let nTask = Task(entity: ent!, insertIntoManagedObjectContext: context)
then I populate my nTask with String extracted from the JSON, I save the context and I reload the DataBase:
for dict in json2 {
var apps = [String]()
if let summary = dict["summary"] as? String{
nTask.summary = summary
}
if let description = dict["description"] as? String{
nTask.detail = description
}
if let context = dict["context"] as? String{
nTask.context = context
}
if let due = dict["due"] as? String {
nTask.date = due
}
if let status = dict["status"] as? String{
nTask.status = status
}
if let responsible = dict["responsible"] as? String{
nTask.responsable = responsible
}
if let id = dict["id"] as? String{
nTask.id = id
}
}
context?.save(nil)
println(nTask)
self.tableView.reloadData()
When we use a TableView, we have to declare cellForRowAtIndexPath and numberOfRowsInSection, these are them:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("customTableViewCell") as! UITableViewCell
let task = frc.objectAtIndexPath(indexPath) as! Task
cell.textLabel?.text = task.summary
var detail = task.detail
var context = task.context
var due = task.date
var status = task.status
var responsible = task.responsable
cell.detailTextLabel?.text = "Contexte: \(context), Detail: \(detail), Status: \(status), Ending date: \(due)"
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRowsInSection = frc.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
The error is line let task = frc.objectAtIndexPath(indexPath) as! Task on my cellForRowAtIndexPath.
The complete error is: Could not cast value of type 'NSManagedObject_Task_' (0x79ebd190) to 'TaskManager.Task' (0xa1f08).
I search for more than half day and no results. I really don't understand what's happening to me...
I'm sorry to give so much code but I haven't any idea of where or why I have this error, so I have to give as informations as possible..
Thanks you so much for having read to the end, thank you for your help.
Regards.
Edit:
I have finally solved my problem by doing several things. I don't really know which one solved... I added the annotation #objc(Task) on my Task class,on my DataModel I changed the class to Task, checked my NSManagedObjectModel was let modelURL = NSBundle.mainBundle().URLForResource("TaskManager", withExtension: "momd")! and the url let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("TaskManager.sqlite") on the AppDelegate..
Thank you for your help.
I experienced a similar issue, and in my case what worked was to add this #objc(NameOfClass) above my core data class definition. Thank you!
Try:
let task = frc.objectAtIndexPath(indexPath) as NSManagedObject
Perhaps the real problem you have is not the extraction in its "cellForRowAtIndexPath" is in its "FOR":
for dict in json2 {
...
if let summary = json2["summary"] as? String{
nTask.summary = summary
}
...
You it is seeking "summary" of "dict" when you should get it from "json2"