Saving tableview cells - swift

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.

Related

Swift CoreData Save Attribute with Type of Custom Class

I'm new to using CoreData as the database to store the users' data in order to support offline app usage. I have set up the data model for my objects and some of the attributes have to be stored in the type of my other object classes. However, it seems to be saved successfully but it returns nil when I tried to fetch the results from the database. I also looked into the SQLite database file to see what happened to the field that stored into CoreData giving me nil as the results (and the returning type is found to be Optional(Any)), and found that the attribute of the data was stored in the type of BLOB.
The results stored in SQLite:
Originally, my app can only be used when network is available but I'm trying to turn it be supporting offline access. However, I have be sticking with the issues and do not know how to solve it, and even I'm not sure whether I was doing in the right way.
One of the example of my custom object classes StudentMarkList:
import Foundation
import UIKit
import CoreData
#objc(StudentMarkList)
class StudentMarkList: NSObject {
var id = Int() // Student ID in DB (Primary Key)
var paperId = String() // Paper ID which the student attended
var studentClass = String() // Student class
var totalMark = Int() // Total mark of the attended paper
var allMarked = Bool() // Flag whether student's paper is all marked
var markList = [MarkList]() // Array of marks for each question <- Custom object class of defining the structure of each question's mark
override init() {
super.init()
}
init(id:Int, paperId:String, studentClass:String, totalMark:Int, allMarked:Bool, markList:[MarkList]){
self.id = id
self.paperId = paperId
self.studentClass = studentClass
self.totalMark = totalMark
self.allMarked = allMarked
self.markList = markList
}
func save() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
let entity = NSEntityDescription.entity(forEntityName: "StudentMarkList", in: managedContext)!
let studentMarkList = NSManagedObject(entity: entity, insertInto: managedContext)
studentMarkList.setValue(self.id, forKey: "id")
studentMarkList.setValue(self.paperId, forKey: "paperId")
studentMarkList.setValue(self.studentClass, forKey: "studentClass")
studentMarkList.setValue(self.totalMark, forKey: "totalMark")
studentMarkList.setValue(self.allMarked, forKey: "allMarked")
studentMarkList.setValue(self.markList, forKey: "markList")
if managedContext.hasChanges {
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
}
Model of StudentMarkList:
The corresponding custom object class MarkList:
import Foundation
import UIKit
import CoreData
#objc(MarkList)
class MarkList: NSObject, NSCoding {
func encode(with aCoder: NSCoder) {
aCoder.encode(questionId, forKey: "questionId")
aCoder.encode(questionNo, forKey: "questionNo")
aCoder.encode(questionMark, forKey: "questionMark")
}
required init?(coder aDecoder: NSCoder) {
guard let questionId = aDecoder.decodeObject(forKey: "questionId") as? Int,
let questionNo = aDecoder.decodeObject(forKey: "questionNo") as? Int,
let questionMark = aDecoder.decodeObject(forKey: "questionMark") as? String else {
return nil
}
self.questionId = questionId
self.questionNo = questionNo
self.questionMark = questionMark
}
var questionId = Int()
var questionNo = Int()
var questionMark = String()
override init() {
super.init()
}
init(questionId: Int, questionNo: Int, questionMark: String) {
self.questionId = questionId
self.questionNo = questionNo
self.questionMark = questionMark
}
func save() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
let entity = NSEntityDescription.entity(forEntityName: "MarkList", in: managedContext)!
let markList = NSManagedObject(entity: entity, insertInto: managedContext)
markList.setValue(self.questionId, forKey: "questionId")
markList.setValue(self.questionNo, forKey: "questionNo")
markList.setValue(self.questionMark, forKey: "questionMark")
if managedContext.hasChanges {
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
}
Model of MarkList:
The function save() is used to save the instance to CoreData as the corresponding object class.
Hope someone can help me or clarify me the concepts of using CoreData. Thank you!
To achieve custom class as attribute of your CoreData entity. Each class should be treated as separate entity and data linking should be manage through relationships
You have to create new entity for marklist, StudentMarkList will be your parent entity.
Simply add relation of marklist to studentMarkList, Select your StudentMarkList entity and create relation as shown in image. Don't worry about inverse follow next step it will be clear.
Now select Marklist entity and add relationship like shown below
Please make sure to change class codegen to use manual

Structure, with the same name as a Core Data object type?

Can I have a structure with the same name as a Core Data object type? If so, how do I differentiate between the two in code?
Edit: For example, I have a Track core data object, and when I read in "track" information externally, it comes in via json. Instead of using the core data object, since its a managed object, I'm using another structure. I was planning on naming this Track as well, however this may result in conflicts which I'm not sure about, so at present I've called it TrackStruct instead. Also, is this the right approach?
Thanks!
Well I've made a sample project for you after going through a lot of difficulties. But I'm posting the main concept here.
You can have the sample project here. Though I've loaded data from a local .plist file. You can check out the loadPersonWithJSON(fromPath:) function's job. Just follow my code commenting.
Suppose I've a Person Entity in my Core-Data with two String property name and location. From the json I'm getting array which is of type [[String:Any]]. Now I want to map my json data to the core data object model.
enum CoreDataError: String, Error {
case NoEntity = "ERROR: No Entity, Check the Entity Name"
}
enum JSONError: String, Error {
case NoData = "ERROR: no data"
case ConversionFailed = "ERROR: conversion from JSON failed"
}
typealias personJSONObjectType = [[String:String]]
class PersonTableViewController: UITableViewController {
var person: [Person] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.loadPersonWithJSON(fromPath: "your json URL in String format")
}
func loadPersonWithJSON(fromPath jsonURLString:String) {
guard let jsonURL = URL(string: jsonURLString) else {
print("Error creating an URL from \(jsonURLString)")
return
}
URLSession.shared.dataTask(with: jsonURL) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? personJSONObjectType else {
throw JSONError.ConversionFailed
}
// Here you have your json data. Now map this data to your model object.
// First you need to have your shared App Delegate
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
print("No shared AppDelegate")
return
}
// Use shared App Delegate to have the persistent containers view context as managed object context. This will be used to verify whether your Entity exists or not
let managedObjectContext = appDelegate.persistentContainer.viewContext
// Get the Entity in your core data model
guard let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedObjectContext) else {
throw CoreDataError.NoEntity
}
let persons = json.map({ (personInfo) -> Person in
let personName = personInfo["name"] as? String // use appropriate key for "name"
let personLocation = personInfo["location"] as? String // use appropriate key for "location"
// Get your object as Core data Managed object.
let aPerson = NSManagedObject(entity: entity, insertInto: managedObjectContext) as! Person
// Manipulate core data object with json data
aPerson.name = personName
aPerson.location = personLocation
// Manipulation done
return aPerson
})
self.person = persons
self.tableView.reloadData()
} catch let error as JSONError {
print(error.rawValue)
} catch let error as CoreDataError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
}
Additional Resource
You can use the following table view data source method to check if that works:
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.person.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let aPerson = self.person[indexPath.row]
cell.textLabel?.text = aPerson.name
cell.detailTextLabel?.text = aPerson.location
return cell
}
You have a good approach for that, to separate CoreData from business models. There is just this naming issue. So, I will just share my experience, is that CoreData entities a prefixed with Managed and the business models are as they are which leads to have this in your case:
ManagedTrack <-> Track.

Fetch some data from Core Data

I'm saving some data - name and surname. But i don't know how to fetch some data.
Here is my code, but i don't know why it doesn't work - i don't know
class UserIN: NSManagedObject {
#NSManaged var name: String?
}
I create NSManagedObject Subclass
func fetching(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let usersFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
do {
let fetchedUsers = try context.fetch(usersFetch) as! [UserIN]
print(fetchedUsers)
} catch {
fatalError("Failed to fetch employees: \(error)")
}
}
Please tell me the reason why it doesn't display fetched data.
The class of the NSManagedObject subclass must match the name of the entity (unless you changed it manually in Interface Builder)
class Users: NSManagedObject {
#NSManaged var name: String?
}
Not related to the issue but in Swift 3 use a fetch request with static type rather than the generic NSFetchRequestResult:
func fetching(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let usersFetch = NSFetchRequest<Users>(entityName: "Users")
do {
let fetchedUsers = try context.fetch(usersFetch)
print(fetchedUsers)
} catch {
fatalError("Failed to fetch employees: \(error)")
}
}

Using Realm with MPMediaQuery

I want to build an Audiobookplayer which can set Bookmarks. Loading the Audiobooks from my Library with MPMediaQuery works fine, but when I take an audiobook off through iTunes, it stays in my realmfile.
I would like realm to delete the entry automatically when the playlist is updated through iTunes, but I can't seem to figure out how.
Here is my code.
class Books: Object {
dynamic var artistName: String?
dynamic var albumTitle: String?
dynamic var artwork: NSData?
dynamic var albumUrl: String?
dynamic var persistentID: String?
let parts = List<BookParts>()
override static func primaryKey() -> String? {
return "persistentID"
}
override class func indexedProperties() -> [String] {
return ["albumTitle"]
}
convenience init(artistName: String, albumTitle: String, albumUrl: String) {
self.init()
self.artistName = artistName
self.albumTitle = albumTitle
self.albumUrl = albumUrl
}
class BookQuery {
let realm = try! Realm()
var bookItems = Array<Books>()
var partItems = Array<BookParts>()
func getBooks() {
let query: MPMediaQuery = MPMediaQuery.audiobooks()
query.groupingType = .album
let collection: [MPMediaItemCollection] = query.collections!
try! realm.write {
for allbooks in collection {
let item = allbooks.representativeItem
let book = Books()
let id = item?.value(forProperty: MPMediaItemPropertyAlbumPersistentID) as! Int
book.artistName = item?.artist
book.albumTitle = item?.albumTitle
book.albumUrl = item?.assetURL?.absoluteString
book.artwork = Helper.getArtwork(item?.artwork) as NSData?
book.persistentID = id.stringValue
realm.add(book, update: true)
guard realm.object(ofType: Books.self, forPrimaryKey: "persistentID") != nil else {
continue
}
bookItems.append(book)
}
}
}
}
I calling the MediaQuery in "viewDidLoad" in my LibraryViewController.
I am pretty new to coding and are trying to solve this for a while.
Thanks for any help.
The high level thing you'll need to do is to have a way to detect when the iTunes playlist is updated and then delete the removed items' corresponding objects from the Realm.
A general approach to this is to get all the "persistent ID"s currently in the Realm at the start of the for loop, put those in an array, remove each ID it sees from the array, then delete objects with the persistent ID in the array that's left, since those weren't in the collection.

Swift: Could not cast value of type 'NSManagedObject_' to 'dataModel.Entity'

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"