Core Data not fetching in table view - ios

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()
}

Related

Problem with saving data using Core Data in swift

I'm trying to save data to the core data and then display it on another view controller. I have a table view with custom cell, which have a button. I've created a selector, so when we tap on the button in each of the cell, it should save all the data from the cell. Here is my parent view controller:
import UIKit
import SafariServices
import CoreData
class ViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var pecodeTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
var newsTitle: String?
var newsAuthor: String?
var urlString: String?
var newsDate: String?
var isSaved: Bool = false
private var articles = [Article]()
private var viewModels = [NewsTableViewCellViewModel]()
private let searchVC = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
pecodeTableView.delegate = self
pecodeTableView.dataSource = self
pecodeTableView.register(UINib(nibName: S.CustomCell.customNewsCell, bundle: nil), forCellReuseIdentifier: S.CustomCell.customCellIdentifier)
fetchAllNews()
createSearchBar()
loadNews()
saveNews()
countNewsToCategory()
}
#IBAction func goToFavouritesNews(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToFav, sender: self)
}
private func fetchAllNews() {
APICaller.shared.getAllStories { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
private func createSearchBar() {
navigationItem.searchController = searchVC
searchVC.searchBar.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: S.CustomCell.customCellIdentifier, for: indexPath) as! CustomNewsCell
cell.configure(with: viewModels[indexPath.row])
cell.saveNewsBtn.tag = indexPath.row
cell.saveNewsBtn.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
return cell
}
#objc func didTapCellButton(sender: UIButton) {
guard viewModels.indices.contains(sender.tag) else { return }
print("Done")// check element exist in tableview datasource
if !isSaved {
saveNews()
print("success")
}
//Configure selected button or update model
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let article = articles[indexPath.row]
guard let url = URL(string: article.url ?? "") else {
return
}
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
//Search
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text, !text.isEmpty else {
return
}
APICaller.shared.Search(with: text) { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
self?.searchVC.dismiss(animated: true, completion: nil)
}
case .failure(let error):
print(error)
}
}
}
}
extension ViewController {
func loadNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
let savedNews = try context.fetch(request)
//Handle saved news
if savedNews.count > 0 {
isSaved = true
}
} catch {
print("Error fetching data from context \(error)")
}
}
func saveNews() {
//Initialize the context
let news = SavedNews(context: self.context)
//Putting data
news.title = newsTitle
news.author = newsAuthor
news.publishedAt = newsDate
news.url = urlString
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
func countNewsToCategory() {
//Initialize the context
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
])
request.predicate = predicate
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from category \(error)")
}
}
}
I don't know where is the problem, I've created a correct data model, but data could not be saved. Here is my model:
import Foundation
struct APIResponse: Codable {
let articles: [Article]
}
struct Article: Codable {
let author: String?
let source: Source
let title: String
let description: String?
let url: String?
let urlToImage: String?
let publishedAt: String
}
struct Source: Codable {
let name: String
}
And also my model in Core Data:
My second view controller, to which I want display the data:
import UIKit
import CoreData
class FavouriteNewsViewController: UIViewController {
#IBOutlet weak var favTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
override func viewDidLoad() {
super.viewDidLoad()
favTableView.delegate = self
favTableView.delegate = self
loadSavedNews()
favTableView.register(UINib(nibName: S.FavouriteCell.favouriteCell, bundle: nil), forCellReuseIdentifier: S.FavouriteCell.favouriteCellIdentifier)
// Do any additional setup after loading the view.
}
}
extension FavouriteNewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = favTableView.dequeueReusableCell(withIdentifier: S.FavouriteCell.favouriteCellIdentifier, for: indexPath) as! FavouritesCell
print(savedNews)
let article = savedNews[indexPath.row]
if let articleTitle = article.title {
cell.favTitle.text = articleTitle
}
if let articleAuthor = article.author {
cell.favAuthor.text = articleAuthor
}
if let articleDesc = article.desc {
cell.favDesc.text = article.desc
}
return cell
}
}
extension FavouriteNewsViewController {
func loadSavedNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
func deleteNews(at indexPath: IndexPath) {
// Delete From NSObject
context.delete(savedNews[indexPath.row])
// Delete From current News list
savedNews.remove(at: indexPath.row)
// Save deletion
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
}
you did not assign your properties that you are trying to save
newsTitle,newsAuthor,newsDate,urlString
seems these properties have nil value . make sure these properties have valid value before save .

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.

How do I update values stored in Core Data on second screen and display updated values on first screen?

I have three view controllers(two UIViewControllers and 1 UITableViewController). I want to display all the data on the tableView and add/update data on two separate view controllers.
The two UIViewControllers have three textFields each (for name, email and phone number) and one button to save/update the data in CoreData and on the tableViewController.
One section in the table view consists of three/two rows (number text field can be empty). On swiping a row from a section, the user can delete the whole section or edit the data in the section.
I have created "Person" entity and three attributes ("name","email","number", all of String data type).
But I get the following error on the line
let objectUpdate = test[0] as! NSManagedObject
Error: Fatal error: Index out of range
import UIKit
import CoreData
class RootTableViewController: UITableViewController {
//Array to display the data in table:
var array_of_person_data_array : [PersonData] = []
//Variable with index of selected row/section:
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
let rightBarButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(nextScreen))
navigationItem.rightBarButtonItem = rightBarButton
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
}
override func viewWillAppear(_ animated: Bool) {
retrieveData()
tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return array_of_person_data_array.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if array_of_person_data_array[section].number == nil || array_of_person_data_array[section].number == ""
{return 2}
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! TableViewCell
if indexPath.row == 0
{cell.personLabel.text = array_of_person_data_array[indexPath.section].name}
else if indexPath.row == 1
{cell.personLabel.text = array_of_person_data_array[indexPath.section].email}
else
{cell.personLabel.text = array_of_person_data_array[indexPath.section].number}
return cell
}
//Row actions when swiped:
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
index = indexPath.section
//Cancel row action:
let cancelRowAction = UITableViewRowAction(style: .normal, title: "Cancel", handler: {(action : UITableViewRowAction,indexPath : IndexPath) in
})
//Update row action:
let updateRowAction = UITableViewRowAction(style: .default, title: "Update", handler: {(action: UITableViewRowAction, indexPath: IndexPath) in
let sbObj = UIStoryboard(name: "Main", bundle: nil)
let svcObj = sbObj.instantiateViewController(withIdentifier: "UpdateViewControllerSB") as! UpdateViewController
svcObj.index = self.index
svcObj.personDataObject = self.array_of_person_data_array[indexPath.section]
self.navigationController?.pushViewController(svcObj, animated: true)
})
//Delete row action:
let deleteRowAction = UITableViewRowAction(style: .destructive, title: "Delete", handler: {(alert : UITableViewRowAction, indexPath : IndexPath) in
//Delete controller:
let deleteController = UIAlertController(title: "Delete", message: nil, preferredStyle: .actionSheet)
//Delete action:
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: {(UIAlertAction)in
self.deleteData()
})
deleteController.addAction(deleteAction)
//Cancel action:
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
deleteController.addAction(cancelAction)
//Present the controller:
self.present(deleteController,animated: true,completion: nil)
})
return [cancelRowAction,updateRowAction,deleteRowAction]
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 1000, height: 1000))
return view
}
#objc func nextScreen()
{
let sbObj = UIStoryboard(name: "Main", bundle: nil)
let svcObj = sbObj.instantiateViewController(withIdentifier: "AddViewControllerSB") as! AddViewController
self.navigationController?.pushViewController(svcObj, animated: true)
}
//Function to retrieve data from core data:
func retrieveData() {
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Prepare the request of type NSFetchRequest for the entity
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
// fetchRequest.fetchLimit = 1
// fetchRequest.predicate = NSPredicate(format: "username = %#", "Ankur")
// fetchRequest.sortDescriptors = [NSSortDescriptor.init(key: "email", ascending: false)]
//
do {
let result = try managedContext.fetch(fetchRequest)
for data in result as! [NSManagedObject] {
array_of_person_data_array.append(PersonData(personName: data.value(forKey: "name") as! String, personEmail: data.value(forKey: "email") as! String, personNumber: data.value(forKey: "number") as? String))
}
} catch {
print("Failed")
}
}
//Function to delete data from Core Data:
func deleteData(){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name = %#", array_of_person_data_array[index].name!)
fetchRequest.predicate = NSPredicate(format: "email = %#", array_of_person_data_array[index].email!)
fetchRequest.predicate = NSPredicate(format: "number = %#", array_of_person_data_array[index].number!)
do
{
let test = try managedContext.fetch(fetchRequest)
let objectToDelete = test[0] as! NSManagedObject
managedContext.delete(objectToDelete)
array_of_person_data_array.remove(at: index)
do{
try managedContext.save()
}
catch
{
print(error)
}
}
catch
{
print(error)
}
tableView.reloadData()
}
}
Add View Controller:
import UIKit
import CoreData
class AddViewController: UIViewController {
#IBOutlet weak var nameTF: UITextField!
#IBOutlet weak var emailTF: UITextField!
#IBOutlet weak var numberTF: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonAction(_ sender: Any) {
createData()
navigationController?.popToRootViewController(animated: true)
}
func createData(){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Now let’s create an entity and new user records.
let userEntity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
//Get data ready to be set into CORE DATA:
let user = NSManagedObject(entity: userEntity, insertInto: managedContext)
user.setValue(nameTF.text, forKeyPath: "name")
user.setValue(emailTF.text, forKey: "email")
user.setValue(numberTF.text, forKey: "number")
//Save the set data to CORE DATA:
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
Update view controller:
class UpdateViewController: UIViewController {
#IBOutlet weak var nameTF: UITextField!
#IBOutlet weak var emailTF: UITextField!
#IBOutlet weak var numberTF: UITextField!
var index : Int?
var personDataObject=PersonData(personName: "sample", personEmail: "sample#sample", personNumber: "xxxx-xxx-xxx")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func updateButtonAction(_ sender: Any) {
self.updateData()
navigationController?.popToRootViewController(animated: true)
}
//Update the data in CoreData:
func updateData(){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name = %#", "Ankur1")
fetchRequest.predicate = NSPredicate(format: "email = %#", "Ankur1")
fetchRequest.predicate = NSPredicate(format: "number = %#", "Ankur1")
do
{
let test = try managedContext.fetch(fetchRequest)
let objectUpdate = test[0] as! NSManagedObject
objectUpdate.setValue(nameTF.text, forKey: "name")
objectUpdate.setValue(emailTF.text, forKey: "email")
if let no = numberTF.text
{objectUpdate.setValue(no, forKey: "number")}
do{
try managedContext.save()
}
catch
{
print(error)
}
}
catch
{
print(error)
}
}
}
PersonData class is defined as:
class PersonData
{
var name : String?
var email: String?
var number : String?
init(personName : String, personEmail : String, personNumber : String?) {
name = personName
email = personEmail
number = personNumber
}
}
How do I update the existing data or add new data to CoreData and display the new data on the Table View Controller?
You should look into NSFetchedResultsController: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/nsfetchedresultscontroller.html
In essence, you want to create/update/delete values on your second screens in your CoreData context, save those changes, and then the fetched results controller on your main screen is setup (by following the above documentation) to automatically listen for those changes and reflect them in the table.
This is good practice because you don't have to worry about updating the view yourself and keeping state synchronized - it's done automatically by the view retrieving data when it is updated.
Declare a callback property and a property for the person data in AddViewController. Use the NSManagedObject object rather than the custom class PersonData
var callback : ((Person) -> Void)?
var person : Person?
In RootTableViewController assign a closure in nextScreen before presenting the controller
svcObj.callback = { person in
// execute the code you need
}
In AddViewController at some point assign the modified NSManagedObject to person
In viewWillDisappear call the closure
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let person = person { callback?(person) }
}

How can I delete data in coreData and Tableview with one Button?

I'm actually doing an app for recipe, I already did the persistent data to save my ingredients in the list but when I want to delete my ingredients with my button it works at the first time but come back when I restart my app.
Here's my code :
class AddIngredientController: UIViewController, ShowAlert {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var ingredientsTableView: UITableView!
var itemArrayIngredient = [Item]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
print(dataFilePath)
loadItems()
}
func saveItems() {
do {
try context.save()
} catch {
print("Error saving context \(error)")
}
}
func loadItems(with request: NSFetchRequest<Item> = Item.fetchRequest()) {
do {
itemArrayIngredient = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
#IBAction func clearButton(_ sender: UIButton) {
context.delete() //Here the problem
itemArrayIngredient.removeAll()
saveItems()
ingredientsTableView.reloadData()
}
#IBAction func addButton(_ sender: UIButton) {
if textField.text!.isEmpty {
showAlert(title: "No Ingredients", message: "Please, add some ingredients to your list.")
} else {
newIngredientAdded()
}
}
func newIngredientAdded() {
let newItem = Item(context: context)
newItem.title = textField.text!
itemArrayIngredient.append(newItem)
saveItems()
ingredientsTableView.reloadData()
textField.text! = ""
}
}
extension AddIngredientController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemArrayIngredient.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customSearchCell", for: indexPath)
let item = itemArrayIngredient[indexPath.row]
cell.textLabel?.text = item.title
cell.textLabel?.textColor = UIColor.white
cell.textLabel?.font = UIFont(name: "Marker Felt", size: 19)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
saveItems()
tableView.deselectRow(at: indexPath, animated: true)
}
}
context.delete must be called with an argument. For example
#IBAction func clearButton(_ sender: UIButton) {
itemArrayIngredient.forEach { context.delete($0) }
itemArrayIngredient.removeAll()
saveItems()
ingredientsTableView.reloadData()
}
You need to delete a specific object from core data. Please refer below code
let fetchRequest = NSFetchRequest(entityName: "EntityName")
if let result = try? context.fetch(fetchRequest) {
for object in result {
//Please check before delete operation
if object.id == Your_Deleted_Object_ID{
context.delete(object)
}
}
}
You can delete all data from particular entity by using NSBatchDeleteRequest. See following code. The main advantage of NSBatchDeleteRequest is, you don't need to enumerate on array of object.
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "EntityName")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetch)
do {
try context.execute(deleteRequest)
} catch {
print(error.localizedDescription)
}

How do I check app connectivity correctly?

I'm trying to create a function to check the Connectivity when the App loads. If a internet connection is detected, the app should download the JSON data and save the array in UserDefaults, then proceed to the UITableView methods. However, if the internet connection is not found, the app should recover the array on USerDefault to populate another array, then proceed to UItableView methods.
The problem I'm facing, is that when the compiler goes trough UserDefault line to be able to save the array, the app crashes immediately. What I'm doing wrong?
Compiler Error:
) for key backupSaved'
*** First throw call stack: (0x18257ad8c 0x1817345ec 0x18257ac6c 0x1825b1d08 0x1824e730c 0x1824e5a60 0x1825b2080 0x182515cec
0x1825b2080 0x1825b2304 0x182518d6c 0x182518588 0x182518c54
0x1825bc218 0x1825bf8a0 0x182edaaf4 0x102a794c0 0x102a7452c
0x102e8d314 0x102e45b7c 0x103da11dc 0x103da119c 0x103da5d2c
0x182523070 0x182520bc8 0x182440da8 0x184425020 0x18c45d758
0x102a756b0 0x181ed1fc0) libc++abi.dylib: terminating with uncaught
exception of type NSException (lldb)
[FIXED]ViewController:
import UIKit
import Kingfisher
import Alamofire
var arrCerveja = [Cerveja]()
var arrBackup = [Cerveja]()
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
//TableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if Connectivity.isConnectedToInternet {
return arrCerveja.count
} else {
return arrBackup.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID") as! TableViewCell
if Connectivity.isConnectedToInternet {
let model = arrCerveja[indexPath.row]
cell.labelName.text = model.name
cell.labelDetail.text = "Teor alcoólico: \(model.abv)"
let resource = ImageResource(downloadURL: URL(string: "\(model.image_url)")!, cacheKey: model.image_url)
cell.imageViewCell.kf.setImage(with: resource, placeholder: UIImage(named: "icons8-hourglass-48"), options: nil, progressBlock: nil, completionHandler: nil)
return cell
} else {
let model = arrBackup[indexPath.row]
cell.labelName.text = model.name
cell.labelDetail.text = "Teor alcoólico: \(model.abv)"
let resource = ImageResource(downloadURL: URL(string: "\(model.image_url)")!, cacheKey: model.image_url)
cell.imageViewCell.kf.setImage(with: resource, placeholder: UIImage(named: "icons8-hourglass-48"), options: nil, progressBlock: nil, completionHandler: nil)
return cell
}
}
//TableView Delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if Connectivity.isConnectedToInternet {
performSegue(withIdentifier: "segueId", sender:arrCerveja[indexPath.row])
} else {
performSegue(withIdentifier: "segueId", sender:arrBackup[indexPath.row])
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueId" {
let des = segue.destination as? TableViewDetalhes
//.item possui uma propriedade instanciada na TelaDetalheProdutos
des?.item = (sender as? Cerveja)
//Segue para CollectionView Categorias
}
}
struct Connectivity {
static let sharedInstance = NetworkReachabilityManager()!
static var isConnectedToInternet:Bool {
return self.sharedInstance.isReachable
}
}
override func viewDidAppear(_ animated: Bool) {
if Connectivity.isConnectedToInternet {
print("Connected")
getApiData { (cerveja) in
arrCerveja = cerveja
//Backup
do{
let data = try JSONEncoder().encode(arrCerveja)
UserDefaults.standard.set(data, forKey: "backupSaved")
//
self.tableView.reloadData()
}catch{print(error)
}
}
} else {
print("No Internet")
do{
if let savedData = UserDefaults.standard.value(forKey: "backupSaved") as? Data {
arrBackup = try JSONDecoder().decode([Cerveja].self, from: savedData)
self.tableView.reloadData()
}
}catch{
print(error)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//SetupNavBarCustom
navigationController?.navigationBar.setupNavigationBar()
}
}
Model:
struct Cerveja:Decodable{
let name:String
let image_url:String
let description:String
let tagline:String
let abv:Double
let ibu:Double?
}
The array should be endcoded to Data before saving , as it's array of custom objects
do {
....... write
let data = try JSONEncoder().encode(arr)
UserDefaults.standard.set(data, forKey: "backupSaved")
// save as data
....... read
if let savedData = UserDefaults.standard.value(forKey: "backupSaved") as? Data {
let savedArr = try JSONDecoder().decode([Cerveja].self, from: savedData)
// use the array here
}
}
catch {
print(error)
}
//
Since now you encode & decode
struct Cerveja:Codable {--}
//
Also I don't vote for saving in userDefaults consider CoreData

Resources