I am unable to delete data from core data. Below is my code. When I delete, the data gets deleted from table but once I navigate back it shows up again as it does not get deleted from core data. Thanks
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let data = (fav[indexPath.row])
if editingStyle == .delete {
DataSave.deleteData(dataToDelete: data)
fav.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
self.tableView.reloadData()
}
**My deleteData function** -
class func deleteData(dataToDelete:String) {
let context = getContext()
let fetchRequest = NSFetchRequest<City>(entityName: "City")
if let fetchResults = try!context.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as? [City] {
for result in fetchResults{
if result.city_name == [dataToDelete] {
context.delete(result)
print("deleted from cored data")
do {
try context.save()
} catch {
print("could not delete from core data")
}
}
}
}
}
Related
I found two ways to get data asynchronously to avoid the main thread being stuck.How does the main thread refresh data, after using NSAsynchronousFetchRequest?
plan A:
#objc public static func fetchObjects(completed: #escaping (_ results: [DraftNote]?) -> Void) {
let taskContext = NotePersistentContainer.shared.newBackgroundContext()
taskContext.perform {
do {
let fetchRequest = DraftNote.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(DraftNote.lastModifiedDate), ascending: false)]
fetchRequest.predicate = NoteFiltrateType.predicateCurrent(by: #keyPath(DraftNote.userID))
let objects = try taskContext.fetch(fetchRequest)
DispatchQueue.main.async {
completed(objects.compactMap { $0 })
}
} catch {
completed(nil)
print("\(#function) error is: \(error as NSError)")
}
}
}
plan B:
#objc static func retrieveObjects(completed: #escaping (_ results: [DraftNote]?) -> Void) {
let fetchRequest = DraftNote.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(DraftNote.lastModifiedDate), ascending: false)]
fetchRequest.predicate = NoteFiltrateType.predicateCurrent(by: #keyPath(DraftNote.userID))
fetchRequest.fetchLimit = .max
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { fetchResult in
if let values = fetchResult.finalResult {
DispatchQueue.main.async {
completed(values)
}
} else {
completed(nil)
}
}
do {
let taskContext = NotePersistentContainer.shared.newBackgroundContext()
try taskContext.execute(asyncFetchRequest)
} catch {
let err = error as NSError
print("\(#function) error: \(err), \(err.userInfo)")
}
}
Both methods are great for fetching data, they don't block the main thread. But the problem is that I can't manipulate the final result, if I call back the final result using DispatchQueue.main.async{} , the main thread can't refresh the UI. How to solve this problem please?
I am working on save image to core data. I used image pick to select image,
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let currentDateTime = Date()
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
self.imagePickedBlock?(image,currentDateTime)
} else {
print("Something went wrong")
}
viewController?.dismiss(animated: true, completion:{
if let addPhotoViewController = self.completionViewController as? AddPhotoViewController {
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else {return }
addPhotoViewController.photoViewModel.image = image
self.viewController?.present(addPhotoViewController, animated: true, completion: nil)
}
})
}
and in addPhotoViewController, I have an confirme button to call the following update database method.
The Photo is an NSManagedObject.
private func updateDatabase(with photoViewModels: [PhotoViewModel]) {
container?.performBackgroundTask { [weak self] context in
for photoViewModel in (self?.photoViewModels)! {
_ = try? Photo.findOrCreatePhoto(matching: photoViewModel, in: context)
}
try? context.save()
self?.printDatabaseStatistics()
}
}
And this is the create NSManagedObject method.
static func findOrCreatePhoto(matching photoViewModel: PhotoViewModel, in context: NSManagedObjectContext) throws -> Photo {
let request : NSFetchRequest<Photo> = Photo.fetchRequest()
// request.predicate if Needed
do {
let matches = try context.fetch(request)
if matches.count > 0 {
return matches[0]
}
} catch {
throw error
}
let photo = Photo(context:context) // the crash line
photo.image = UIImageJPEGRepresentation(photoViewModel.image!, 1)
photo.uploadDate = photoViewModel.createDate
photo.text = photoViewModel.description
// photo.group = try? Group.findOrCreateGroup(matching: photoViewModel, in: context)
return photo
}.
It marks "Enqueued from com.apple.main-thread", I don't really understand where is the problem exactly about the thread, anyone has idea? Don't hesitate if I didn't explain clear enough :)
Thank you for your time.
I need to insert records to the coredata for 2 entities from one push notification, when application is in background, but when I try to insert, it stops execution while fetching data from existing records and continues after another notification occurs or when user clicks on notification.
I want data to be inserted when the user did not click on the push notification and when app is in background state.
Here is my code part to fetch record and to insert:
static func insertFromNotificationMessage(_ context: NSManagedObjectContext, message: [AnyHashable: Any]) -> myData{
var myData : myData?
context.performAndWait {
myData = NSEntityDescription.insertNewObject(
forEntityName: "myData", into: context) as? myData
myData?.guid = message["id"] as? String
myData?.title = message["ttl"] as! String
let anotherData = anotherData.getByUUID(context, UUID: message["id"] as! String) ?? anotherData.insertFromNotificationMessage(context, details: message)
myData?.author = author
do{
try context.saveContextAndWait()
}catch let error{
print("Error\(error)")
}
}
return myData!
}
static func getByUUID(_ context: NSManagedObjectContext, UUID : String)-> anotherData?{
let fetchSingleRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "anotherData")
fetchSingleRequest.predicate = NSPredicate(format: "guid =[c] %#", UUID)
do{
let fetchedUser = try context.fetch(fetchSingleRequest) as! [anotherData]
if fetchedUser.count > 0{
return fetchedUser.first
}
}catch{
print("Failed to fetch: \(error)")
}
return nil
}
Do it in the app delegate:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
YOUR OBJECT CLASS.saveMessage(userInfo) // code to save into core data
completionHandler(.NewData)
}
Here is my example function to save the data
static func saveMessage(userInfo: [NSObject : AnyObject]) -> Message {
let alertInfo = userInfo["aps"]?["alert"]!
let title:String?
let body:String?
if let alertTitle = alertInfo!["title"] as? String{
title = alertTitle
body = alertInfo!["body"] as? String
} else {
title = userInfo["title"] as? String ?? "CUSTOM TITLE"
body = alertInfo as? String
}
let message = Message(date: NSDate(), title: title!, message: body!, isReaded: false)
DBUtil.saveContext()
return message
}
I am building an app with Core Data. it has always worked for me so far. Recently,
I get not result. no error. it seems that no data is persisted. has anyone ever encountered this weird malfunction?
My viewcontroller: to display contacts list
import UIKit
import CoreData
class ContactsTableViewController: UITableViewController {
#IBAction func addContactAction(_ sender: AnyObject) {
alertDialog()
}
let identifier = "contactCell"
var contacts:[String] = [String]()
var managedContext:NSManagedObjectContext?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
addContacts(numContacts: 30);
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fetchContacts("") { (list) in
for i in 0..<list.count {
contacts.append(list[i].value(forKey:"name")! as! String)
}
tableView.reloadData()
}
}
func alertDialog() {
//It takes the title and the alert message and prefferred style
let alertController = UIAlertController(title: "Add Contact", message: "", preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.placeholder = "contact"
}
let defaultAction = UIAlertAction(title: "Add", style: .default) { (UIAlertAction) in
let textField = alertController.textFields![0]
self.addContact(name: textField.text!)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
//now we are adding the default action to our alertcontroller
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
//and finally presenting our alert using this method
present(alertController, animated: true, completion: nil)
}
}
extension ContactsTableViewController {
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
// Configure the cell...
cell.textLabel?.text = contacts[indexPath.row]
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let contactToDelete = contacts[indexPath.row]
deleteContact(contactToDelete)
contacts.remove(at: (indexPath as NSIndexPath).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
}
}
}
extension ContactsTableViewController {
func addContacts(numContacts:Int) {
for i in 1..<numContacts {
let contact = NSEntityDescription.insertNewObject(forEntityName: "Contact", into: managedContext!) as! Contact
contact.setValue("name \(i)", forKeyPath: "name")
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
try managedContext?.save()
print("\(contact.value(forKeyPath: "name") as! String)) successfully saved")
} catch {
fatalError("Failure to save context: \(error)")
}
}
self.tableView.reloadData()
}
func addContact(name:String) {
let contact = NSEntityDescription.insertNewObject(forEntityName: "Contact", into: managedContext!) as! Contact
contact.setValue(name, forKeyPath: "name")
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
try managedContext?.save()
print("\(contact.value(forKeyPath: "name") as! String)!) successfully saved")
} catch {
fatalError("Failure to save context: \(error)")
}
self.tableView.reloadData()
}
func fetchContacts(_ predicate:String, completion:(_ array:[Contact]) -> ()) {
var arr:[Contact] = [Contact]()
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Contact")
request.predicate = NSPredicate(format: "name = %#", predicate)
do {
let results = try managedContext?.fetch(request) as! [Contact]
for result in results {
let name = (result as AnyObject).value(forKey: "name") as? String
arr.append(result)
} //for
print(results)
completion(arr as [Contact])
} catch {
print("error fetching results")
} //do
}
func deleteContact(_ name:String) {
fetchContacts(name) { (array) -> () in
for result in array {
let aContact = (result as AnyObject).value(forKey: "name") as? String
if aContact == name {
//delete
self.managedContext?.delete(result)
//save
do {
try self.managedContext!.save()
print("\(aContact) deleted")
} catch {
print("error deleting contact")
} //do
} // if
} //for
}
}
}
My AppDelegate.swift
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "ContactLists_coreData")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Generated entity class
import Foundation
import CoreData
extension Contact {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Contact> {
return NSFetchRequest<Contact>(entityName: "Contact");
}
#NSManaged public var name: String?
}
the data model. very simple
<img src="https://drive.google.com/file/d/0B1Usy68B1DzYLUduNTlCY092VEk/view" width="1970" height="1084">
datasource and cell identifier are connected properly
fetchContacts("") always returns an empty list because you have no contacts with a name of "". Also whenever you add or insert to core-data you do not see those changes because you are not doing another fetch and updating the contacts array.
Other more general problems with your code:
You should treat the persistentContainer's viewContext as readonly. To write to core-data use performBackgroundTask(_:)
after you create the persistentContainer set container.viewContext.automaticallyMergesChangesFromParent = true
use a fetchedResultsController to sync core-data with your view.
If you are doing lots of changes or inserts to core data like you are doing in addContacts do a single save at the end, not after every insert in the loop.
I found the following issue in my memory. I couldn't understand it.
error evaluating expression “(CAListenerProxy::DeviceAggregateNotification *)0x7cee3d60”: error: use of undeclared identifier 'CAListenerProxy'
error: expected expression
I used two notification centers - one for sending the object to another view and the other in another view to send the dictionary which contain objects after deleting one from it.
My code is :
// only for delegate method for the downloading videos
extension WebViewController {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// her i need to get the data from the movie which i download it so i can save it in the document directory
if let fetchDataFromDownloadFile = try? Data(contentsOf: location) {
// generate the fileName randamlly
let createFileName = UUID().uuidString
// generate object for save file
let operationDocumentDirectory = OperationDocumentDirectory()
operationDocumentDirectory.saveMovie(movieName: createFileName, data: fetchDataFromDownloadFile)
}// end the if let for the fetch data from download file
// her for fetch the download video object when i save it to set it to ni for free the memory
if let fetchURL = downloadTask.originalRequest?.url {
var fetchObject = operationObject?.dictionaryOfDownloadVideo?.removeValue(forKey: fetchURL)
// for stop the downloadtask when finish download
if fetchObject?.videoURL == downloadTask.originalRequest?.url {
downloadTask.cancel()
fetchObject = nil
}
// for update the badge after finish download movie
DispatchQueue.main.async {[weak self] in
if let mySelf = self {
// set badge for nil if the objects zero
if operationObject.dictionaryOfDownloadVideo?.count == 0 {
self?.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = nil
}else{
// if the object not zero update the badge
mySelf.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = "\(operationObject.dictionaryOfDownloadVideo!.count)"
}
}// end the if for myself
}// end the dispatchqueue.main
// update the data in table view
NotificationCenter.default.post(name: NOTIFICATION_UPDATE_TABLEVIEW, object: nil)
}// end the fetch url
}
// and this code in another view for updating table view when i currently downloading movie
extension MovieDownloadingViewController {
// data source
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? DownloadingTableViewCell
if let cell = cell {
cell.movieObject = arrayOfObjects?[indexPath.row]
cell.movieDeleteButton.tag = indexPath.row
cell.movieDeleteButton.addTarget(self, action: #selector(self.deleteCurrentDownloadingMovie(sender:)), for: .touchUpInside)
}
return cell!
}
func deleteCurrentDownloadingMovie(sender:UIButton){
displayAlertDeleteMovie(arrayOfObject: arrayOfObjects!, index: sender.tag)
}
func displayAlertDeleteMovie(arrayOfObject:[DownloadVideo],index:Int) {
let alertController = UIAlertController(title: "Delete Movie", message: "Do You Want Delete Movie \(arrayOfObject[index].videoName)", preferredStyle: .actionSheet)
let alertDelete = UIAlertAction(title: "Delete", style: .default) {[weak self] (alertAction:UIAlertAction) in
var fetchObjectMovie = self?.arrayOfObjects?.remove(at: index)
// set the notification for update the number of element in dict and array
NotificationCenter.default.post(name: NOTIFICATION_UPDATE_NUMBER_OF_ARRAY_DICT, object: fetchObjectMovie?.videoURL)
if fetchObjectMovie != nil {
fetchObjectMovie = nil
}
// update table view
// self?.tableView.reloadData()
// update the badge in the tab bar controller
if operationObject.dictionaryOfDownloadVideo?.count == 0 {
self?.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = nil
}else{
self?.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = "\(operationObject.dictionaryOfDownloadVideo!.count)"
}
}
let alertCancel = UIAlertAction(title: "Cancel", style: .cancel) { [weak self](alertAction:UIAlertAction) in
self?.dismiss(animated: true, completion: {})
}
alertController.addAction(alertDelete)
alertController.addAction(alertCancel)
present(alertController, animated: true, completion: nil)
}
please any help
thanks a lot