How to persist tableview rows? - ios

I am creating an ios app that will show me a user specific set of tasks everyday that i click to delete to show its done. I save the tasks in core data, and just delete the tableview row on click. I dont delete the data in coredata as its userdefined and need to reload it everyday. I use a newDay() function to decide to load data from coredata if the app is opened on a new day. What should i do to remember which all tasks have been done for the day? Do i need to create another enitity to remember which all tasks are completed or is there an simpler way?
var tasks: [NSManagedObject] = []
let defaults = UserDefaults.standard
var calender = Calendar.current
override func viewDidLoad() {
super.viewDidLoad()
title = "DailyTasker"
navigationItem.leftBarButtonItem = editButtonItem
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let checkDate = newDay()
if checkDate{
//1
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
//2
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Task")
//3
do {
tasks = try managedContext.fetch(fetchRequest)
defaults.set(Date(), forKey: "LastRun")
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
}
func newDay() -> Bool{
if let lastRun = defaults.object(forKey: "LastRun") as? Date{
if !calender.isDateInToday(lastRun){
return true
} else {
return false
}
} else {
return true
}
}
#IBAction func addName(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Task",
message: "Add a new task",
preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save",
style: .default) {
[unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text else {
return
}
self.save(name: nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel",
style: .default)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
func save(name: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
// 1
let managedContext =
appDelegate.persistentContainer.viewContext
// 2
let entity =
NSEntityDescription.entity(forEntityName: "Task",
in: managedContext)!
let task = NSManagedObject(entity: entity,
insertInto: managedContext)
// 3
task.setValue(name, forKeyPath: "name")
// 4
do {
try managedContext.save()
tasks.append(task)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return tasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let task = tasks[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskerCell", for: indexPath)
cell.textLabel?.text = task.value(forKeyPath: "name") as? String
return cell
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
tasks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tasks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}

You could add a date attribute to your task and name it lastDonefor instance. Then you set that to current date time when a task is done and also use a predicate when fetching task instances so you only get those not done today.
task.lastDone = Date()
I am not sure how you define "today" but this question should help you create a predicate that properly filters your task although you might also want to include tasks where lastDone is null.

I am not sure Why you can't delete the data. Is it due to specifications.
If not then, when you save your task in coredata simply assign it with a unique Identifer(id) and then you can create your own data stack Method to delete the specific task.
You can create a data Model Class or struct for the task example
class TaskData {
var id: Int!
var task: String!
init(id: Int, task: String) {
self.id = id
self.task = task
}
}
Save task as this dataClass into coreData.
When you delete the row at that time captue the Task Id and delete it from coreDataStack.
A good way will be to create a TaskManager Singelton class to handle all the core data Methods.

Related

Implementing a UISearchController on top of UITableView displaying Core Data in swift 5

I have a core data Swift master/detail view application (code is located here) where the core data object, called sweetnote, is basically this:
class sweetnote: NSObject {
private(set) var noteId : UUID
private(set) var noteTitle : String
private(set) var noteText : NSAttributedString
private(set) var noteCreated : Int64
private(set) var noteModified : Int64
private(set) var noteCategory : String
}
I have a Master view controller which renders these notes in a UITableView with the help of a function in Helpers/sweetnoteCoreDataHelper.swift called readNotesFromCoreData:
static func readNotesFromCoreData(fromManagedObjectContext: NSManagedObjectContext) -> [sweetnote] {
var returnedNotes = [sweetnote]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
fetchRequest.predicate = nil
do {
let fetchedNotesFromCoreData = try fromManagedObjectContext.fetch(fetchRequest)
fetchedNotesFromCoreData.forEach { (fetchRequestResult) in
let noteManagedObjectRead = fetchRequestResult as! NSManagedObject
returnedNotes.append(sweetnote.init(
noteId: noteManagedObjectRead.value(forKey: "noteId") as! UUID,
noteTitle: noteManagedObjectRead.value(forKey: "noteTitle") as! String,
noteText: noteManagedObjectRead.value(forKey: "noteText") as! NSAttributedString,
noteCreated: noteManagedObjectRead.value(forKey: "noteCreated") as! Int64,
noteModified: noteManagedObjectRead.value(forKey: "noteModified") as! Int64,
noteCategory: noteManagedObjectRead.value(forKey: "noteCategory")
as! String))
}
} catch let error as NSError {
// TODO error handling
print("Could not read notes from core data. \(error), \(error.userInfo)")
}
// Set note count
self.count = returnedNotes.count
// Sort by modified date
return returnedNotes.sorted() {
$0.noteModified > $1.noteModified
}
}
And then in my Master view controller (at UI/MasterViewController.swift) I have a UISearchController configured at the top of the UITableView which is currently not applying any filtering logic based on the text and I'm having trouble determining where I need to update:
import UIKit
import Foundation
import CoreData
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating {
var detailViewController: DetailViewController? = nil
var searchtemplate: String? {didSet {print (searchtemplate as Any)}}
// Search results controller
let resultSearchController = UISearchController(searchResultsController: nil)
var sweetnotes: [sweetnote] = []
var searchResults: [sweetnote] = []
override func viewDidLoad() {
super.viewDidLoad()
// Core data initialization
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
// Create alert controller
let alert = UIAlertController(
title: "Could note get app delegate",
message: "Could note get app delegate, unexpected error occurred. Try again later.",
preferredStyle: .alert)
// Add OK action
alert.addAction(UIAlertAction(title: "OK",
style: .default))
// Show alert
self.present(alert, animated: true)
return
}
// Pass the context forward from the app delegate
let managedContext = appDelegate.persistentContainer.viewContext
// Set context in the storage
sweetnoteStorage.storage.setManagedContext(managedObjectContext: managedContext)
// Set the search controller programmatically
resultSearchController.searchResultsUpdater = self
resultSearchController.hidesNavigationBarDuringPresentation = false
resultSearchController.obscuresBackgroundDuringPresentation = false
self.definesPresentationContext = true
// Scope bar
//resultSearchController.searchBar.scopeButtonTitles = ["All", "Ideas", "Information", "Lifestyle", "Lists", "Recipes", "Other"]
//searchView.addSubview(resultSearchController.searchBar)
tableView.tableHeaderView = resultSearchController.searchBar
resultSearchController.searchBar.tintColor = UIColor.darkGray
resultSearchController.searchBar.barTintColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 0.8)
resultSearchController.searchBar.placeholder = "Search sweetnotes"
// Prefer large titles
self.navigationController?.navigationBar.prefersLargeTitles = true
// Edit-note button
navigationItem.leftBarButtonItem = editButtonItem
// Add-note button
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
if let split = splitViewController {
let controllers = split.viewControllers
detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
self.tableView.delegate = self
}
...
override func viewWillAppear(_ animated: Bool) {
clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed
super.viewWillAppear(animated)
}
// Dismiss search bar on scroll-down
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
resultSearchController.dismiss(animated: false, completion: nil)
}
#objc
func insertNewObject(_ sender: Any) {
performSegue(withIdentifier: "showCreateNoteSegue", sender: self)
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
//let object = objects[indexPath.row]
let object = sweetnoteStorage.storage.readNote(at: indexPath.row)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
//controller.detailItem = resultSearchController.isActive ? searchResults[indexPath.row] : sweetnotes[indexPath.row]
}
}
}
// MARK: - Table View
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text else { return }
print(text)
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
searchtemplate = searchText
tableView.reloadData()
}
func updateSearchResultsForSearchController(searchController: UISearchController, fromManagedObjectContext: NSManagedObjectContext) {
let searchText = searchController.searchBar.text!
let predicate = NSPredicate(format: "%K CONTAINS[c] %#", argumentArray: ["noteText", searchText])
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
fetchRequest.predicate = predicate
do {
let fetchedNotesFromCoreData = try fromManagedObjectContext.fetch(fetchRequest)
fetchedNotesFromCoreData.forEach { (fetchRequestResult) in
let noteManagedObjectRead = fetchRequestResult as! NSManagedObject
searchResults.append(sweetnote.init(
noteId: noteManagedObjectRead.value(forKey: "noteId") as! UUID,
noteTitle: noteManagedObjectRead.value(forKey: "noteTitle") as! String,
noteText: noteManagedObjectRead.value(forKey: "noteText") as! NSAttributedString,
noteCreated: noteManagedObjectRead.value(forKey: "noteCreated") as! Int64,
noteModified: noteManagedObjectRead.value(forKey: "noteModified") as! Int64,
noteCategory: noteManagedObjectRead.value(forKey: "noteCategory")
as! String))
}
} catch let error as NSError {
// TODO error handling
print("Could not read notes from core data. \(error), \(error.userInfo)")
}
tableView.reloadData()
}
...
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if resultSearchController.isActive && resultSearchController.searchBar.text != "" {
return searchResults.count
} else {
return sweetnoteStorage.storage.count()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! sweetnoteUITableViewCell
//let note = fetchedResultsController.object(at: indexPath)
//configureCell(cell, withEvent: note)
if let object = sweetnoteStorage.storage.readNote(at: indexPath.row) {
cell.noteTitleLabel!.text = object.noteTitle
cell.noteTextLabel!.attributedText = object.noteText
cell.noteCategoryLabel!.text = object.noteCategory
cell.noteDateLabel!.text = sweetnoteDateHelper.convertDate(date: Date.init(minutes: object.noteModified))
}
//cell.contentView.layer.cornerRadius = 6.0
//cell.contentView.layer.borderColor = UIColor.gray.withAlphaComponent(0.5).cgColor
//cell.contentView.layer.borderWidth = 1.0
//cell.contentView.layer.masksToBounds = true
//cell.contentView.clipsToBounds = true
cell.backgroundColor = UIColor.white
cell.selectionStyle = UITableViewCell.SelectionStyle.none
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable
if resultSearchController.isActive {
return false
} else {
return true
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
presentDeletionFailsafe(indexPath: indexPath)
//objects.remove(at: indexPath.row)
//sweetnoteStorage.storage.removeNote(at: indexPath.row)
//tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
// Deletion failsafe function
func presentDeletionFailsafe(indexPath: IndexPath) {
let alert = UIAlertController(title: nil, message: "Are you sure you would like to delete this note?", preferredStyle: .alert)
// Delete the note
let yesAction = UIAlertAction(title: "Yes", style: .default) { _ in
// replace data variable with your own data array
sweetnoteStorage.storage.removeNote(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
alert.addAction(yesAction)
// Cancel and don't delete note
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
func configureCell(_ cell: sweetnoteUITableViewCell, withEvent note: Note) {
cell.noteTitleLabel!.text = note.noteTitle
cell.noteTextLabel!.attributedText = note.noteText
cell.noteCategoryLabel!.text = note.noteCategory
cell.noteDateLabel!.text = sweetnoteDateHelper.convertDate(date: Date.init(minutes: note.noteModified))
}
}
The function updateSearchResultsForSearchController is where I tried to establish a predicate and execute the search result fetch request, based on some similar questions out there where people implemented search controllers on top of core data projects, but I am definitely still missing something or am in the wrong place because when text is entered to the search bar, all notes remain in the table view.
Any answers/suggestions are massively appreciated as I've spent a good deal of time on this. I am happy to provide more information and context if that helps you out. And again the code is located in my 'searchbar' branch of my project at here. Thank you very much in advance.

Delete CoreData object

I am still struggling with CoreData to start the week haha. I finally succeeded in saving and fetching my array, now is time to edit and delete.
I'm adding the delete function first but I'm having trouble passing in the correct argument:
Core Data functions:
class CDHandler: NSObject {
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
class func saveObject(name:String, code:String, symbol:String, placeholder:String, amount:String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "CryptosMO", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
managedObject.setValue(code, forKey: "code")
managedObject.setValue(symbol, forKey: "symbol")
managedObject.setValue(placeholder, forKey: "placeholder")
managedObject.setValue(amount, forKey: "amount")
do {
try context.save()
return true
} catch {
return false
}
}
class func fetchObject() -> [CryptosMO]? {
let context = getContext()
var cryptos: [CryptosMO]? = nil
do {
cryptos = try context.fetch(CryptosMO.fetchRequest()) as? [CryptosMO]
return cryptos
} catch {
return cryptos
}
}
class func deleteObject(crypto: CryptosMO) -> Bool {
let context = getContext()
context.delete(crypto)
do {
try context.save()
return true
} catch {
return false
}
}
}
Creating and saving the array :
if addedCrypto != "" {
if addedCrypto == "Bitcoin BTC" {
if CDHandler.saveObject(name: "Bitcoin", code: "bitcoin", symbol: "BTC", placeholder: "BTC Amount", amount: "0.0") {
for crypto in CDHandler.fetchObject()! {
print("\(String(describing: crypto.name))'s symbol is \(String(describing: crypto.symbol))")
}
}
}
}
Fetching Core Data for the TableView:
override func viewWillAppear(_ animated: Bool) {
tableView.delegate = self
tableView.dataSource = self
if CDHandler.fetchObject() != nil {
cryptos = CDHandler.fetchObject()!
tableView.reloadData()
}
}
TableView functions:
extension WalletTableViewController: UITableViewDelegate, UITableViewDataSource, CryptoCellDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cryptos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! WalletTableViewCell
cell.cryptoNameLabel.text = cryptos[indexPath.row].name
cell.amountTextField.placeholder = cryptos[indexPath.row].placeholder
cell.delegate = self
cell.amountTextField.delegate = self
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
cryptos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
CDHandler.deleteObject(crypto: cryptos) // <----- Cannot convert value of type '[CryptosMO]' to expected argument type 'CryptosMO'
}
}
}
What is the problem here? I can change func deleteObject(crypto: CryptosMO) to func deleteObject(crypto: [CryptosMO]) but then I get Cannot convert value of type '[CryptosMO]' to expected argument type 'NSManagedObject'.
I read that delete() only take an NSManagedObject as its sole argument so I believe I created an incorrect object in the first place to be able to delete it??
Just call this method and pass entity with managedObjectwhich you want to delete:
func deleteData(entity:String,deleteObject:NSManagedObject){
//for iOS 10+
// let delegate = UIApplication.shared.delegate as? AppDelegate
// let context = delegate!.persistentContainer.viewContext
let context = getContext()
context.delete(deleteObject)
do {
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let selectedManagedObject = cryptos[indexPath.row]
deleteData(entity:"yourEntityName",deleteObject: selectedManagedObject)
cryptos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
same like save method you can edit, just you need to pass the managedObject which you want to edit:
class func updateObject(name:String, code:String, symbol:String, placeholder:String, amount:String,selectedManagedObject:NSManagedObject) {
let context = getContext()
selectedManagedObject.setValue(name, forKey: "name")
selectedManagedObject.setValue(code, forKey: "code")
selectedManagedObject.setValue(symbol, forKey: "symbol")
selectedManagedObject.setValue(placeholder, forKey: "placeholder")
selectedManagedObject.setValue(amount, forKey: "amount")
do {
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
When you call CDHandler.deleteObject(crypto: ...) just pass (crypto: cryptos[indexPath.row]) instead of (crypto: cryptos).
...
CDHandler.deleteObject(crypto: cryptos[indexPath.row])
cryptos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)

Saving array to CoreData Swift

I would like to save this kind of arrays with Core Data:
let crypto1 = Cryptos(name: "Bitcoin", code: "bitcoin", symbol: "BTC", placeholder: "BTC Amount", amount: "0.0")
let crypto2 = Cryptos(name: "Bitcoin Cash", code: "bitcoinCash", symbol: "BCH", placeholder: "BCH Amount", amount: "0.0")
Is that even possible?
I know I can create an array to save like that...
let name = "Bitcoin"
let code = "bitcoin"
let symbol = "BTC"
let placeholder = "BTC Amount"
let amount = "0.0"
let cryptos = CryptoArray(context: PersistenceService.context)
cryptos.name = name
cryptos.code = code
cryptos.symbol = symbol
cryptos.placeholder = placeholder
cryptos.amount = amount
crypto.append(cryptos)
PersistenceService.saveContext()
...but this seems pretty inconvenient when a theoretical infinite number of arrays will be created by the user.
What would be the best way for me to save data, load it, edit it and delete it?
This is a question for a tutorial rather than a straight forward answer. I suggest you give some time to read about CoreData. Having said that, your question sounds generic, "Saving array to CoreData in Swift", so I guess it doesn't hurt to explain a simple implementation step by step:
Step 1: Create your model file (.xcdatamodeld)
In Xcode, file - new - file - iOS and choose Data Model
Step 2: Add entities
Select the file in Xcode, find and click on Add Entity, name your entity (CryptosMO to follow along), click on Add Attribute and add the fields you like to store. (name, code, symbol... all of type String in this case). I'll ignore everything else but name for ease.
Step 3 Generate Object representation of those entities (NSManagedObject)
In Xcode, Editor - Create NSManagedObject subclass and follow the steps.
Step 4 Lets create a clone of this subclass
NSManagedObject is not thread-safe so lets create a struct that can be passed around safely:
struct Cryptos {
var reference: NSManagedObjectID! // ID on the other-hand is thread safe.
var name: String // and the rest of your properties
}
Step 5: CoreDataStore
Lets create a store that gives us access to NSManagedObjectContexts:
class Store {
private init() {}
private static let shared: Store = Store()
lazy var container: NSPersistentContainer = {
// The name of your .xcdatamodeld file.
guard let url = Bundle().url(forResource: "ModelFile", withExtension: "momd") else {
fatalError("Create the .xcdatamodeld file with the correct name !!!")
// If you're setting up this container in a different bundle than the app,
// Use Bundle(for: Store.self) assuming `CoreDataStore` is in that bundle.
}
let container = NSPersistentContainer(name: "ModelFile")
container.loadPersistentStores { _, _ in }
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
// MARK: APIs
/// For main queue use only, simple rule is don't access it from any queue other than main!!!
static var viewContext: NSManagedObjectContext { return shared.container.viewContext }
/// Context for use in background.
static var newContext: NSManagedObjectContext { return shared.container.newBackgroundContext() }
}
Store sets up a persistent container using your .xcdatamodeld file.
Step 6: Data source to fetch these entities
Core Data comes with NSFetchedResultsController to fetch entities from a context that allows extensive configuration, here is a simple implementation of a data source support using this controller.
class CryptosDataSource {
let controller: NSFetchedResultsController<NSFetchRequestResult>
let request: NSFetchRequest<NSFetchRequestResult> = CryptosMO.fetchRequest()
let defaultSort: NSSortDescriptor = NSSortDescriptor(key: #keyPath(CryptosMO.name), ascending: false)
init(context: NSManagedObjectContext, sortDescriptors: [NSSortDescriptor] = []) {
var sort: [NSSortDescriptor] = sortDescriptors
if sort.isEmpty { sort = [defaultSort] }
request.sortDescriptors = sort
controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
// MARK: DataSource APIs
func fetch(completion: ((Result) -> ())?) {
do {
try controller.performFetch()
completion?(.success)
} catch let error {
completion?(.fail(error))
}
}
var count: Int { return controller.fetchedObjects?.count ?? 0 }
func anyCryptos(at indexPath: IndexPath) -> Cryptos {
let c: CryptosMO = controller.object(at: indexPath) as! CryptosMO
return Cryptos(reference: c.objectID, name: c.name)
}
}
All we need from an instance of this class is, number of objects, count and item at a given indexPath. Note that the data source returns the struct Cryptos and not an instance of NSManagedObject.
Step 7: APIs for add, edit and delete
Lets add this apis as an extension to NSManagedObjectContext:
But before that, these actions may succeed or fail so lets create an enum to reflect that:
enum Result {
case success, fail(Error)
}
The APIs:
extension NSManagedObjectContext {
// MARK: Load data
var dataSource: CryptosDataSource { return CryptosDataSource(context: self) }
// MARK: Data manupulation
func add(cryptos: Cryptos, completion: ((Result) -> ())?) {
perform {
let entity: CryptosMO = CryptosMO(context: self)
entity.name = cryptos.name
self.save(completion: completion)
}
}
func edit(cryptos: Cryptos, completion: ((Result) -> ())?) {
guard cryptos.reference != nil else {
print("No reference")
return
}
perform {
let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
entity?.name = cryptos.name
self.save(completion: completion)
}
}
func delete(cryptos: Cryptos, completion: ((Result) -> ())?) {
guard cryptos.reference != nil else {
print("No reference")
return
}
perform {
let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
self.delete(entity!)
self.save(completion: completion)
}
}
func save(completion: ((Result) -> ())?) {
do {
try self.save()
completion?(.success)
} catch let error {
self.rollback()
completion?(.fail(error))
}
}
}
Step 8: Last step, use case
To fetch the stored data in main queue, use Store.viewContext.dataSource.
To add, edit or delete an item, decide if you'd like to do on main queue using viewContext, or from any arbitrary queue (even main queue) using newContext or a temporary background context provided by Store container using Store.container.performInBackground... which will expose a context.
e.g. adding a cryptos:
let cryptos: Cryptos = Cryptos(reference: nil, name: "SomeName")
Store.viewContext.add(cryptos: cryptos) { result in
switch result {
case .fail(let error): print("Error: ", error)
case .success: print("Saved successfully")
}
}
Simple UITableViewController that uses the cryptos data source:
class ViewController: UITableViewController {
let dataSource: CryptosDataSource = Store.viewContext.dataSource
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "YourCellId", for: indexPath)
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cryptos: Cryptos = dataSource.anyCryptos(at: indexPath)
// TODO: Configure your cell with cryptos values.
}
}
You cannot save arrays directly with CoreData, but you can create a function to store each object of an array. With CoreStore the whole process is quite simple:
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "ModelName")
do {
try dataStack.addStorageAndWait()
} catch let error {
print("Cannot set up database storage: \(error)")
}
return dataStack
}()
func addCrypto(name: String, code: String, symbol: String, placeholder: String, amount: Double) {
dataStack.perform(asynchronous: { transaction in
let crypto = transaction.create(Into<Crypto>())
crypto.name = name
crypto.code = code
crypto.symbol = symbol
crypto.placeholder = placeholder
crypto.amount = amount
}, completion: { _ in })
}
You can show the objects within a UITableViewController. CoreStore is able to automatically update the table whenever database objects are added, removed or updated:
class CryptoTableViewController: UITableViewController {
let monitor = dataStack.monitorList(From<Crypto>(), OrderBy(.ascending("name")), Tweak({ fetchRequest in
fetchRequest.fetchBatchSize = 20
}))
override func viewDidLoad() {
super.viewDidLoad()
// Register self as observer to monitor
self.monitor.addObserver(self)
}
// 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.monitor.numberOfObjects()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CryptoTableViewCell", for: indexPath) as! CryptoTableViewCell
let crypto = self.monitor[(indexPath as NSIndexPath).row]
cell.update(crypto)
return cell
}
}
// MARK: - ListObjectObserver
extension CryptoTableViewController : ListObjectObserver {
// MARK: ListObserver
func listMonitorWillChange(_ monitor: ListMonitor<Crypto>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(_ monitor: ListMonitor<Crypto>) {
self.tableView.endUpdates()
}
func listMonitorWillRefetch(_ monitor: ListMonitor<Crypto>) {
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Crypto>) {
self.tableView.reloadData()
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<Crypto>, didInsertObject object: Switch, toIndexPath indexPath: IndexPath) {
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didDeleteObject object: Switch, fromIndexPath indexPath: IndexPath) {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didUpdateObject object: Crypto, atIndexPath indexPath: IndexPath) {
if let cell = self.tableView.cellForRow(at: indexPath) as? CryptoTableViewCell {
cell.update(object)
}
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didMoveObject object: Switch, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
self.tableView.insertRows(at: [toIndexPath], with: .automatic)
}
}
Assuming that you have a CryptoTableViewCell with the function update registered to the CryptoTableViewController.

Core Data not fetching in table view

My code below is using core data to add entities to a tableview. The problem is that when I hit the button to add a new entry the data is not displayed on the tableview. But if I quit the simulator and re install the app the entity is there. To get the entities displayed I have to enter it then reinstall the app to get the entity displayed.
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var rot: UITableView!
#IBOutlet var enterName: UITextField!
var people : [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
showdata()
}
#IBAction func iDontLikeSchool(_ sender: Any) {
if(enterName.text?.isEmpty)! {
alertmsg(name: "d")
showdata()
} else {
saveData(name: enterName.text!)
}}
func alertmsg(name:String){
let alert = UIAlertController(title: "D", message: "d", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
func saveData(name: String) {
guard let appDelage = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelage.persistentContainer.viewContext
let ee = NSEntityDescription.entity(forEntityName: "PersonalInfo", in: managedContext)!
let person = NSManagedObject(entity : ee , insertInto: managedContext)
person.setValue(name, forKey: "userName")
do {
try managedContext.save()
people.append(person)
alertmsg(name: "saved")
enterName.text = ""
}
catch let err as NSError {
print("judo",err)
}
///
}
func showdata() {
guard let appDelage = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelage.persistentContainer.viewContext
let fetchReuqst = NSFetchRequest<NSManagedObject>(entityName: "PersonalInfo")
do {
people = try managedContext.fetch(fetchReuqst)
if people.count == 0 {
} else {
}}
catch let err as NSError {
print("judo", err)
}
///
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = rot.dequeueReusableCell(withIdentifier: "Person")
let personn = people[indexPath.row]
cell?.textLabel?.text = personn.value(forKey: "userName") as? String
return cell!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
The reason why the entry doesn't show up is that you don't tell the view that your model has changed so it doesn't refresh itself.
The tableView gets rendered once on viewDidLoad that's why the data is displayed correctly after the view is loaded freshly (after your app restart).
To solve this issue, keep your viewController's tableView as a property (#IBOutlet if you use the Storyboard) and call
tableView.reloadData()
after you changed your data, in your case e.g. at the end of your func showData() like this.
func showdata() {
guard let appDelage = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelage.persistentContainer.viewContext
let fetchReuqst = NSFetchRequest<NSManagedObject>(entityName: "PersonalInfo")
do {
people = try managedContext.fetch(fetchReuqst)
if people.count == 0 {
//sth
} else {
// sth
}
} catch let err as NSError {
print("judo", err)
}
tableView.reloadData()
}

Delete function (commit editingStyle function) cause crashing the application once the swipe delete button is pressed

Im working on a project in swift 3.0 and Im fetching data from core data on to two tableViews namely;'recurringIncomeTableView', and 'otherIncomeTableView'. However when 'commit editingStyle' function is activated (once I slide the row), I can deleted the particular row in 'recurringIncomeTableView'. But when i slide a row in 'otherIncomeTableView' and pressed delete, in the line 'let task = stores [indexPath.row]' causing the problem and the app is crashing. The code as bellow.
import UIKit
import CoreData
class MyIncomesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var recurringIncomeTableView: UITableView!
#IBOutlet weak var otherIncomeTableView: UITableView!
//var myIncomeType : String?
var stores = [UserIncome] ()
var other = [UserIncome] ()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
self.recurringIncomeTableView.reloadData()
self.otherIncomeTableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
stores.removeAll()
other.removeAll()
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "UserIncome")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request) as! [UserIncome]
print("Results from the fetch request are : ", request)
// check data existance
if results.count>0 {
print("results are :", results.count)
for resultGot in results {
//lets check if the data is available and whether the loop is working by printing out the "name"
if let incName = resultGot.incomeName {
print("expence name is :", incName)
//set the value to the global variable as to filter the arrays
let myIncomeType = resultGot.incomeType
if myIncomeType == "Recurring Income"{
stores += [resultGot]
print("my recurring income array is : \(stores)")
}else if myIncomeType == "Other Income"{
other += [resultGot]
print("my other income array is : \(other)")
}
}
}
self.recurringIncomeTableView.reloadData()
self.otherIncomeTableView.reloadData()
}
}catch{
print("No Data to load")
}
}
#IBAction func addIncome(sender: UIButton) {
print("Add Income Button Clicked")
performSegue(withIdentifier: "ShowAddIncomeVC", sender: nil)
// Do whatever you need when the button is pressed
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.recurringIncomeTableView {
print("recurringIncomeTableView count is ", stores.count)
return stores.count
}else {
print("otherIncomeTableView count is ", other.count)
return other.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.recurringIncomeTableView {
let cell: RecuringIncomeTableViewCell = tableView.dequeueReusableCell(withIdentifier: "recurringIncomeCell") as! RecuringIncomeTableViewCell
let store = stores [indexPath.row]
cell.incomeNameLabel.text = store.incomeName
cell.amountLabel.text = store.amount
//cell.textLabel?.text = myExpensesArray[indexPath.row]
return cell
}else {
let cell: OtherIncomeTableViewCell = tableView.dequeueReusableCell(withIdentifier: "otherIncomeCell") as! OtherIncomeTableViewCell
let otherIncomes = other [indexPath.row]
cell.incomeNameLabel.text = otherIncomes.incomeName
cell.amountLabel.text = otherIncomes.amount
//cell.textLabel?.text = myExpensesArray[indexPath.row]
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//performSegue(withIdentifier: "editStore", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editRecurringIncome"{
let v = segue.destination as! AddIncomeViewController
let indexPath = self.recurringIncomeTableView.indexPathForSelectedRow
let row = indexPath?.row
v.store = stores[row!]
}else if segue.identifier == "editOtherIncome" {
let t = segue.destination as! AddIncomeViewController
let indexPath = self.otherIncomeTableView.indexPathForSelectedRow
let row = indexPath?.row
t.store = other [row!]
}
}
//
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
//For remove row from tableview & object from array.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
**let task = stores [indexPath.row]**
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
stores = try context.fetch(UserIncome.fetchRequest())
}catch{
print("fail")
}
}
tableView.reloadData()
}
}
As per your Core data fetch request code.
You have to store core data object in your store array than & than you can delete that object directly form store array.
You need to fetch object like this :
// Initialize Fetch Request
let fetchRequest = NSFetchRequest()
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("UserIncome", inManagedObjectContext: self.managedObjectContext)
// Configure Fetch Request
fetchRequest.entity = entityDescription
store = try self.managedObjectContext.executeFetchRequest(fetchRequest)
After getting all data you have to filter your array with your requirement and display it in tableview I have just give example how to show that data in tableview.
Show your data in cell like this :
var data: NSManagedObject = store[indexPath.row] as NSManagedObject
Cell.textLabel?.text = data.valueForKeyPath("Name") as? String
Delete your data as per your code :
let task = stores [indexPath.row]
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
it will help you to understand flow of core data with tableview.

Resources