How to delete a row from UITableView swift 4 wherein data is stored using CoreData? - ios

I am a beginner trying to learn the basic concepts of iOS and swift. So, I am making a demo app wherein I store names of people using UITableView. For making the app storage persistent I used core data with only one entity named 'Person' with a single attribute as 'name' which is a string value. To level up this application I need to add delete functionality. As we see in lot of iOS apps, the delete feature on swiping left, I want to implement the same functionality in this app. I have looked up on various solutions but I can't really get a hold of how I'll be able to delete a row and update the table in core data at the same time. I have been stuck at this problem since hours trying to delete a row in UITableView using swipe gesture. Any help would be appreciated.
Below is my code:
ViewController.swift
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var people: [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
title = "The List"
tableView.register(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
}
// Implement the addName IBAction
#IBAction func addName(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Name", message: "Add a new name", 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: .cancel)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
// MARK: - SAVING TO CORE DATA
// CoreData kicks in here!
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: "Person", in: managedContext)!
let person = NSManagedObject(entity: entity, insertInto: managedContext)
// 3
person.setValue(name, forKeyPath: "name")
// 4
do {
try managedContext.save()
people.append(person)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
// MARK: - FETCHING FROM CORE DATA
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 1
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
// 2
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
// 3
do {
people = try
managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let person = people[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = person.value(forKeyPath: "name") as? String
return cell
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
// handle delete (by removing the data from your array and updating the tableview)
}
}
}
App Screen:
Data model:

You have to delete the managed object by calling delete(_:) on the managed object context, e.g.:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let person = people[indexPath.row]
managedContext.delete(person)
And to commit the change, call save() on the managed object context:
try? managedContext.save()
Then, don't forget to also update the person cache in your view controller:
people.remove(at: indexPath.row)
And finally, delete the row in your table view:
tableView.deleteRows(at: [indexPath], with: .automatic)
This should accomplish what you want.
The missing implementation could therefor be:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// delete the person in core data
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let person = people[indexPath.row]
managedContext.delete(person)
try? managedContext.save()
// remove the person from cache
people.remove(at: indexPath.row)
// delete row from table view
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}

Related

Database.database() Error cannor find in scope>

I am trying to delete a table view cell in swift and when I delete the cell I also need to delete the data in firebase. So I use Database.database().child("tweet") this is where the error is it says cannot find Database in scope.
import UIKit
import Firebase
import Firestore
class TableViewController: UITableViewController {
var db:Firestore!
var tweetArray = [tweet]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true
db = Firestore.firestore()
loadData()
checkForUpdates()
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
Database.database().child("tweet") "Error cannot find Database in scope"
}
func loadData() {
db.collection("tweet").getDocuments() {
querySnapshot, error in
if let error = error {
print("\(error.localizedDescription)")
}else{
self.tweetArray = querySnapshot!.documents.compactMap({tweet(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
func checkForUpdates() {
db.collection("tweet").whereField("timeStamp", isGreaterThan: Date())
.addSnapshotListener {
querySnapshot, error in
guard let snapshots = querySnapshot else {return}
snapshots.documentChanges.forEach {
diff in
if diff.type == .added {
self.tweetArray.append(tweet(dictionary: diff.document.data())!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
#IBAction func composeSweet(_ sender: Any) {
let composeAlert = UIAlertController(title: "Add Employee", message: "Add Employee", preferredStyle: .alert)
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Name"
}
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Adress"
}
composeAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
composeAlert.addAction(UIAlertAction(title: "Add Employee", style: .default, handler: { (action:UIAlertAction) in
if let name = composeAlert.textFields?.first?.text,
let content = composeAlert.textFields?.last?.text {
let newTweet = tweet(name: name, content: content,timeStamp: Date())
var ref:DocumentReference? = nil
ref = self.db.collection("tweet").addDocument(data: newTweet.dictionary) {
error in
if let error = error {
print("Error adding document: \(error.localizedDescription)")
}else{
print("Document added with ID: \(ref!.documentID)")
}
}
}
}))
self.present(composeAlert, animated: true, completion: nil)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tweetArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let tweet1 = tweetArray[indexPath.row]
cell.textLabel?.text = "\(tweet1.name) \(tweet1.content)"
cell .detailTextLabel?.text = "\(tweet1.timeStamp) "
return cell
}
}
I tried importing different firebase imports and that didn't work i'm not sure if it has something to do with my cocoa pods. these are the cocapods I have downloaded: pod 'Firebase/Core
pod 'Firebase/Auth' pod 'Firestore'
Your code is reading data with:
db.collection("tweet").whereField("timeStamp", isGreaterThan: Date())
This is the API for Cloud Firestore, a NoSQL document database that is part of Firebase.
You then try to also access data with:
Database.database().child("tweet")
This is using the API for the Realtime Database, which is also a part of Firebase but it is a completely separate product with its own API.
To prevent more problems, use only the API for the database you're actually using, which seems to be Firestore. Have a look at the documentation on deleting data from Firestore

Swift: TableView with initializer returns nil. Trying to dataPass via protocol

rookie here. I created a two tableviews and one gets its data from the previous one. So it has init objects. And I have a view controller where I have a scrollview to display some scrollable images like a catalog. It is just like a menu with categories eventually leading to brands and you tap on the brand so its content comes after.
I do the API call in didSelectRow (for a reason) and use the protocol func. to get images from API to an array then into the scrollview and its subviews. But I can't properly initialize the VC where that tableview that holds the data exists. How can I properly initialize categoriesListVC then use its delegate func?
First VC where I pass data firstly.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let category = data[indexPath.row]
let vc = CategoriesList(brand: category.brand, url: category.url, brandName: category.brandSlugName)
vc?.title = category.category
navigationController?.pushViewController(vc ?? self, animated: true)
}
categoriesList VC with the TableView with init
init?(brand: [String], url: [String], brandName: [String]) {
self.brand = brand
self.url = url
self.brandName = brandName
super.init(nibName: nil, bundle: nil)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
selectedRow2 = indexPath.row
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "CatalogView2") as! ScrollVC2
navigationController?.pushViewController(vc, animated: true)
dataCatalog() //API call - this works I checked. }
And this is how I tried to use its data on the final VC. Would this work? I printed categoriesList here and it gives nil. I probably don't understand something.
var categoriesList: CategoriesList?
override func viewDidLoad() {
super.viewDidLoad()
categoriesList?.delegate = self }
I am trying to use a delegate method in that final VC to use its object to pass data:
func catalogViewModelFromCategories(catalogViewModel: CatalogViewModel) {}
I didn't write the whole code to make it as short as possible. But delegate functions and other things are all in the real code.
In dataCatalog func there is only url session and decoder:
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) {(data, response, error) in
if error == nil {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CatalogViewData.self, from: data!)
let catalogFromCategories = CatalogViewModel(images: decodedData.catalogueimages, title: decodedData.cataloguename!)
let catalogViewModel = catalogFromCategories
DispatchQueue.main.async {
self.delegate?.catalogViewModelFromCategories(catalogViewModel: catalogViewModel)
}
} catch {
print(error)
}
return
}
}
task.resume()
}

Background Refresh not working in iCloudKit/CoreData, Swift 5, UIKit

iCloudKit/CoreData won't update the UI on any other iCloud connected devices after making a change on the device in hand.
I have one ViewController which holds a UITableView and 2 buttons (Add and Refresh). The Add button successfully adds a new row to the UITableView as well as to the iCloudKit container. Any changes to the UITableView are reflected on the device in hand (immediately) and in the iCloudKit container (eventually - ie, within 10-15 seconds).
The problem is that I can't figure out how to get any other devices' UI to update automatically. I wait several minutes, and there's never any update. I can verify that my other devices are signed in to the same iCloud account by waiting a few minutes and then hitting the Refresh button on those devices. I designed the Refresh button to force a NSFetchRequest from iCloudKit and then use that data to reload the UITableView. If I manually instigate the NSFetchRequest, the data is shared amongst all my devices - but there is never any automatic update based on some background notification.
The code below represents my best attempt to make sense of a wide array of opinions (and quite-possibly out-dated strategies) - and I'm not sure if I may be mixing metaphors in the code I'm posting here. I'm using XCode 12.4, Swift 5, UIKit, iCloudKit and CoreData. In the main.storyboard, I control-dragged to establish the ViewController as the UITableView's delegate. I've also added Background Modes -> Remote notifications and Push Notifications to my project's Signing & Capabilities tab.
Here is the AppDelegate code:
import UIKit
import CoreData
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// note: is this needed? - only one tutorial/answer mentioned this
application.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// note: this print() statement never executes - so I'm guessing this func is superfluous
print("AppDelegate.application(didRegisterForRemoteNotificationsWIthDeviceToken) - deviceToken=\(deviceToken)")
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
// note: this print() statement never executes - so I'm guessing this func is superfluous
print("AppDelegate.application(didFailToRegisterForRemoteNotificationsWithError) - error=\(error)")
}
// MARK: - UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let dataContainer = NSPersistentCloudKitContainer(name: "coreData_1")
// turn on persistent history tracking
// https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
// note: I've seen SwiftUI tutorials where this wasn't needed
// - the background refreshes seem to be handled entirely by the SwiftUI's implementation of UITableView
let id = "iCloud.iCloud.org.anon.CoreData1"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: id)
let description = dataContainer.persistentStoreDescriptions.first
description?.cloudKitContainerOptions = options
description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
// this is supposed to make background updates from iCloud available to the context.
dataContainer.viewContext.automaticallyMergesChangesFromParent = true
dataContainer.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
// call this LAST, after the persistentStoreDescriptions configuration.
dataContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return dataContainer
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Here is the ViewController code:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var movies: [NSManagedObject] = []
lazy var managedContext = NSManagedObjectContext(concurrencyType:.mainQueueConcurrencyType)
lazy var cloudContainer = NSPersistentCloudKitContainer()
var notificationCount = 0 // for debugging
override func viewDidLoad() {
super.viewDidLoad()
title = "The List"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
// set up core data accessors once - these are used later
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let mgdCtx = appDelegate.persistentContainer.viewContext
managedContext = mgdCtx
cloudContainer = appDelegate.persistentContainer
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// I never receive these notifications - actOnNotification() is never called
NotificationCenter.default.addObserver(self, selector: #selector(actOnNotification), name: .NSPersistentStoreRemoteChange, object: cloudContainer)
updateTableView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// these notifications aren't working for me in UIKit
NotificationCenter.default.removeObserver(self, name: .NSPersistentStoreRemoteChange, object: cloudContainer)
}
#IBAction func refresh(_ sender: UIBarButtonItem) {
// manually refresh UITableView after giving up on background refresh
updateTableView()
// and just display how many times we've recieved notifications
let alert = UIAlertController(title: "NotificationCount", message: "notificationCount=\(notificationCount)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
#objc func actOnNotification() {
// this function never gets called
notificationCount += 1
updateTableView()
}
#objc func updateTableView() {
// load data using iCloudKit + CoreData
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Movie")
let sort = NSSortDescriptor(key: "fileName", ascending: true)
fetchRequest.sortDescriptors = [sort]
do {
movies = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
// then update UI
tableView.reloadData()
}
#IBAction func addName(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Name", message: "Add a new name", preferredStyle: .alert)
alert.addTextField(configurationHandler: { textField in textField.placeholder = "Enter name..." })
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)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
func save(name: String) {
// update iCloudKit data
guard let entity = NSEntityDescription.entity(forEntityName: "Movie", in: managedContext) else { return }
let person = NSManagedObject(entity: entity, insertInto: managedContext)
person.setValue(name, forKey: "fileName")
do {
try managedContext.save()
movies.append(person)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
print("movies=\(movies)")
// then update UI
tableView.reloadData()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection: Int) -> Int {
return movies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// select cellForRowAt in UI
print("ViewController.tableView(cellForRowAt)")
let person = movies[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = person.value(forKeyPath: "fileName") as? String
print("ViewController.tableView.cellForRow(\(indexPath)")
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// update iCloudKit data
managedContext.delete(movies[indexPath.row])
do {
try managedContext.save()
// then update UI
movies.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.reloadData()
} catch let error as NSError {
print("Could not delete. \(error), \(error.userInfo)")
}
} else {
print("ViewController.tableView.commit(\(editingStyle)")
}
}
}
I have followed a tutorial using SwiftUI - and the NotificationCenter.default.addObserver() and .removeObserver() calls in the viewWillAppear() and viewWillDisappear() functions do not appear in that tutorial. In fact, all that tutorial did was to add the background Signing & Capabilities to the project - and it worked perfect. I'm guessing that SwiftUI has default behavior for the master/view classes used in the tutorial that are NOT present in my use of UIKit's UITableView.
Any help will be greatly appreciated...

Update Table View in iOS (Swift)

I am making a cocktail iOS application.
I am adding strings to a tableview (an ingredient to the "cabinet"). The user enters an ingredient and then presses the button ADD. It successfully adds it to the Core Data but it does not appear right away. What am I doing wrong?
Below is my code, thanks!
ViewController:
import UIKit
import CoreData
class CabinetViewController: UIViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
var ingredientArray = [String]()
var display = [String]()
var dbIngredients = [String]()
let ingredientFetch = NSFetchRequest(entityName: "Cabinet")
var fetchedIngredient = [Cabinet]()
#IBOutlet weak var TextUI: UITextField!
#IBOutlet weak var Button: UIButton!
#IBOutlet weak var TableView: UITableView!
let moc = DataController().managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
TextUI.delegate = self
TextUI.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
TableView.delegate = self
TableView.dataSource = self
TableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
// fetch Core Data
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
} catch {
fatalError()
}
let postEndpoint: String = "http://www.thecocktaildb.com/api/json/v1/1/list.php?i=list"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on www.thecocktaildb.com")
print(error)
return
}
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
}
var count = 1
if let drinks = post["drinks"] as? [NSDictionary] {
for drink in drinks {
if let strIngredient = drink["strIngredient1"] as? String {
print(String(count) + ". " + strIngredient)
self.dbIngredients.append(strIngredient)
count++
}
}
}
})
task.resume()
TableView.reloadData()
}
func textFieldDidChange(textField: UITextField) {
search(self.TextUI.text!)
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
Button.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
return true
}
func buttonPressed(sender: UIButton!) {
//ingredientArray.append(TextUI.text!)
let entity = NSEntityDescription.insertNewObjectForEntityForName("Cabinet", inManagedObjectContext: moc) as! Cabinet
entity.setValue(TextUI.text!, forKey: "ingredient")
do{
try moc.save()
}catch {
fatalError("failure to save context: \(error)")
}
showAlertButtonTapped(Button)
// dispatch_async(dispatch_get_main_queue(), { () -> Void in
// self.TableView.reloadData()
// })
}
#IBAction func showAlertButtonTapped(sender: UIButton) {
// create the alert
let alert = UIAlertController(title: "Added!", message: "You've added " + TextUI.text! + " to your cabinet", preferredStyle: UIAlertControllerStyle.Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
// show the alert
self.presentViewController(alert, animated: true, completion: nil)
}
func search(str:String) {
display.removeAll(keepCapacity: false)
for s in dbIngredients{
if s.hasPrefix(str){
display.append(s)
print(s)
}
}
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.capacity
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
do{
let fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
} catch {
fatalError("bad things happened: \(error)")
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let alert = UIAlertController(title: "Remove " + fetchedIngredient[indexPath.row].ingredient!,
message: "No more " + fetchedIngredient[indexPath.row].ingredient! + " in your cabinet?",
preferredStyle: .Alert)
let deleteAction = UIAlertAction(title: "Remove",
style: .Default,
handler: { (action:UIAlertAction) -> Void in
self.fetchedIngredient.removeAtIndex(indexPath.row)
do{
let fetchedResults = try self.moc.executeFetchRequest(self.ingredientFetch)
if let result = fetchedResults[indexPath.row] as? NSManagedObject {
self.moc.deleteObject(result)
try self.moc.save()
}
}catch{
fatalError()
}
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addAction(cancelAction)
alert.addAction(deleteAction)
presentViewController(alert,
animated: true,
completion: nil)
TableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Since your problem isn't Core Data you need to use Table View beginUpdates and EndUpdates to insert the row. At the end of your buttonPressed function put this:
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
self.tableView.beginUpdates()
let totalIngredients = fetchedIngredient.count
let newItemIndexPath = NSIndexPath(forRow: totalIngredients-1, inSection: 0)
self.tableView.insertRowsAtIndexPaths([newItemIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
} catch {
fatalError()
}
On your number of rows in section:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.count
}
And on the cell for row at index path
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
return cell
}
There are a couple of problems with your code. Firstly, since you're fetching records into an array, calling reloadData will not have any impact unless you update the array. There is no automatic connection between adding a new core data record and your fetchedIngredient array.
There are a few ways to solve this, although the most common is probably to just refetch the records into the same array whenever core data is updated. Alternatively you can change your code to us NSFetchedResultsController instead of an array, which will automatically update the tableView when core data is updated (based on the predicate you provide it). This class provides the automatic connection to core data for you.
Another problem is that you are refetching the records in cellForRowAtIndexPath and didSelectRowAtIndexPath. This should not be done. Instead you should just be referring to the class-level fetchedIngredient array (or the NSFetchedResultsController if you choose to use that).
Furthermore, the call to dataTaskWithRequest runs in the background. It's not clear from the code how you're using it, but the fact that you have reloadData afterwards suggests it should impact the tableView. However because the task runs in the background, the completion handler will run after the table is reloaded. Therefore you should be calling reloadData inside the completion handler. And since it would then be running on another thread, you would have to dispatch it to the main queue, using:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}

Update a core data database on selecting a particular row in iOS9 swift [duplicate]

This question already has an answer here:
Core Data Update in swift while selecting any row in list table view not working?
(1 answer)
Closed 7 years ago.
I have 2 VC in my core data project. In VC1 I have a table view for displaying the names in my coredata database. And in my VC2 I enter the details for the entity. Now I want to update my database when user selects a particular row. When user selects a row it would move to the VC2 and display the name in the textfield and if the user updates anything in it,it would update. I have my code below. Please suggest what I should do next.
Entering the details-
import UIKit
import CoreData
class EnterDetailViewController: UIViewController {
#IBOutlet weak var nametextfield: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func savedata(sender: AnyObject)
{
let appdelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedcontext = appdelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedcontext)
let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedcontext)
person.setValue(self.nametextfield.text, forKey: "name")
do
{
try managedcontext.save()
print("SAVED")
}
catch let error as NSError
{
print("could not save \(error), \(error.userInfo)")
}
self.navigationController?.popToRootViewControllerAnimated(true)
}
}
VC1- Displaying in table view
import UIKit
import CoreData
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
var people = [NSManagedObject]()
let manai = UIApplication.sharedApplication().delegate as! AppDelegate
//var person :NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool)
{
print("CALLING FETCHDATA")
self.fetchData()
print("RELOADING DATA")
self.tableview.reloadData()
}
func fetchData()
{
let appdelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appdelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Person")
do
{
people = try managedContext.executeFetchRequest(fetchRequest) as! [NSManagedObject]
print(people)
print("FETCHING DATA")
}
catch let error as NSError
{
print("could not fetch \(error), \(error.userInfo)")
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return people.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = self.tableview.dequeueReusableCellWithIdentifier("cellreuse", forIndexPath: indexPath)
let person = people[indexPath.row]
cell.textLabel?.text = person.valueForKey("name") as? String
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
let appdelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appdelegate.managedObjectContext
if editingStyle == .Delete
{
managedContext.deleteObject(people[indexPath.row])
}
do
{
try managedContext.save()
print("SAVED")
}
catch let error as NSError
{
print("could not save \(error), \(error.userInfo)")
}
}
fetchData()
self.tableview .reloadData()
print("reload after delete")
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "segueupdate"
{
}
}
}
The designated initializer of NSManagedObject is
initWithEntity:insertIntoManagedObjectContext:. You can't call the generic initializer NSManagedObject().
In your case you could declare managedobjectt as (implicit unwrapped) optional
var managedobjectt = NSManagedObject!
To make sure that accessing the variable doesn't happen too soon in VC2 you could also move the code in viewDidLoad() to viewWillAppear()

Resources