I am getting a Bad Access error when I try to dismiss my view controller:
(This code is in a view controller named NewTaskViewController)
self.delegate?.newTask(name: taskName!, date:date!,image:self.image, location:self.location) //line 1
self.dismiss(animated: true, completion: nil) //line2
If I remove line 1 I do not get the error.
self.delegate is the view controller (ViewController) that presented this view controller (NewTaskViewController) that I am attempting to dismiss with line 2.
The newTask: method is saving the arguments with Core Data: (inside ViewController)
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "Task",
in: managedContext)!
let task = NSManagedObject(entity: entity,
insertInto: managedContext)
task.setValue(name, forKeyPath: "name")
do {
try managedContext.save()
tasks.append(task)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
Any ideas on why I do not get the bad access error when I remove the newTask: method that saves with Core Data. Is it that the newTask: method does not finish executing before self.dmismiss is called? But I don't understand how this is possible unless it has something to do with the way Core Data saves? Any ideas?
EDIT:
The error occurs in ViewController on the line:
newTaskViewController.delegate = self
Which is before the line that presents the newTaskViewController. How is this possible that the bad access error happens on code that has already been executed?
Related
I'm using mapbox and firebase.
I have a delegate function that updates the user's coordinates(inside of the firebase database) when the user's location changes.
To the best of my knowledge, it functions as it should when signed into the app. The mapviews delegate is the view controller (self.mapView.delegate = self)
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool){
let latitude = mapView.userLocation?.coordinate.latitude
let longitude = mapView.userLocation?.coordinate.longitude
Database.database().reference().child(pathToCoord).updateChildValues(["latitude":latitude, "longitude":longitude], withCompletionBlock: { (err, ref) in
if err != nil { print(err!); return }
}
When I sign out of the app, I would like to stop updating the user location.
Ideally I would just like the View Controller with the map to go away completely and for everything on it to stop running.
I've written this sign out function that try several different methods of making sure that the location is no longer updated.
func signOut(){
for id in Auth.auth().currentUser?.providerData{
if id.providerID == "facebook.com"{
FBSDKLoginManager().logOut()
}
}
do {
try Auth.auth().signOut()
}catch let logoutError {
print(logoutError)
}
self.mapView.delegate = nil
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "SignInViewController") as? SignInViewController{
UIApplication.shared.keyWindow?.rootViewController = vc
self.dismiss(animated: true, completion: nil)
}
}
Sometimes when I'm logged out though, I continuously get the error below in my console. The most logical solution I can think of for why this is happening is that the View Controller is still running. I don't know how to make it stop.
[Firebase/Database] updateChildValues: at `path/to/coord` failed: permission_denied
Error Domain=com.firebase Code=1 "Permission denied" UserInfo={NSLocalizedDescription=Permission denied}
Edit
So it looks like the problem was probably that I had this in my SignInViewController
if let uid = Auth.auth().currentUser?.uid{
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController{
vc.uid = uid
UIApplication.shared.keyWindow?.rootViewController = vc
}
}
And then the delegate would run once for each uid, as if two of the view controllers were running at the same time. When I signed out, I'm guessing the other one didn't sign out and kept running for the other user id.
This is off topic to my original question but I'd like to know what the proper way to check if a user is already signed in, and then sign them in is. Because clearly my method didn't work.
Why don't you try dismiss with Completion Handler block like below.
self.dismiss(animated: true, completion: {
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "SignInViewController") as? SignInViewController{
UIApplication.shared.keyWindow?.rootViewController = vc
}
})
The line containing NSEntityDescription.entity is crashing and giving me the error
Thread 1: SIGABRT
I have seen other people ask this question, the recommended answer is to simply delete and remake the entity from scratch. I have done this many times, I have also "cleaned" the code thoroughly, and imported CoreData in both my AppDelegate.swift files and this ViewController file. Does anyone have any advice?
override func viewDidLoad() {
super.viewDidLoad()
addGesture()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let stringModelEntity = NSEntityDescription.entity(forEntityName: "StringModels", in: managedContext)!
let stringBundle = NSManagedObject(entity: stringModelEntity, insertInto: managedContext)
self.getJSON(stringBundle)
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
EDIT
I've found a solution thanks to finally opening the debugger, the following link's 'best answer' describes and solves this issue: Core data: Failed to load model
This will mean that it cannot find the entity with the name "StringModels". In my experience, the error SIGABRT is caused when something that the program thinks should exist does not.
I would check capitalization and spelling.
I would like to use Ensembles Framework to synch my core data with Swift.
Ensembles Framework
But I have some difficulties..
I try to do it like that: (Similar way to the example on github)
I use a button to launch the tasks:
class ReglagesVC: UIViewController,UITableViewDataSource,UITableViewDelegate,UITextFieldDelegate,CDEPersistentStoreEnsembleDelegate {
#IBAction func IcloudSynch(_ sender: UIButton) {
CDESetCurrentLoggingLevel(CDELoggingLevel.verbose.rawValue)
// Setup Core Data Stack
self.setupCoreData()
// Setup Ensemble
let modelURL = Bundle.main.url(forResource: "Mes_Vide_os", withExtension: "momd")
cloudFileSystem = CDEICloudFileSystem(ubiquityContainerIdentifier: nil)
ensemble = CDEPersistentStoreEnsemble(ensembleIdentifier: "Mes_Vide_os", persistentStore: storeURL, managedObjectModelURL: modelURL!, cloudFileSystem: cloudFileSystem)
ensemble.delegate = self
// Listen for local saves, and trigger merges
NotificationCenter.default.addObserver(self, selector:#selector(localSaveOccurred(_:)), name:NSNotification.Name.CDEMonitoredManagedObjectContextDidSave, object:nil)
NotificationCenter.default.addObserver(self, selector:#selector(cloudDataDidDownload(_:)), name:NSNotification.Name.CDEICloudFileSystemDidDownloadFiles, object:nil)
// Sync
self.sync(nil)
}
//ENSEMBLES
// MARK: Notification Handlers
func localSaveOccurred(_ notif: Notification) {
self.sync(nil)
}
func cloudDataDidDownload(_ notif: Notification) {
self.sync(nil)
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var storeDirectoryURL: URL {
return try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
}
var storeURL: URL {
return self.storeDirectoryURL.appendingPathComponent("store.sqlite")
}
var managedObjectContext : NSManagedObjectContext!
func setupCoreData() {
let modelURL = Bundle.main.url(forResource: "Mes_Vide_os", withExtension: "momd")//"momd"
let model = NSManagedObjectModel(contentsOf: modelURL!)
try! FileManager.default.createDirectory(at: self.storeDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model!)
let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
try! coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: self.storeURL, options: options)
managedObjectContext = appDelegate.persistentContainer.viewContext
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
// MARK: Ensembles
var cloudFileSystem: CDECloudFileSystem!
var ensemble: CDEPersistentStoreEnsemble!
func sync(_ completion: (() -> Void)?) {
//let viewController = self.window?.rootViewController as! ReglagesVC
//self.activityIndicator?.startAnimating()
if !ensemble.isLeeched {
ensemble.leechPersistentStore {
error in
print("LEECH FINI___________________________")
completion?()
}
}
else {
ensemble.merge {
error in
print("MERGE FINI___________________________")
completion?()
}
}
}
func persistentStoreEnsemble(_ ensemble: CDEPersistentStoreEnsemble, didSaveMergeChangesWith notification: Notification) {
managedObjectContext.performAndWait {
self.managedObjectContext.mergeChanges(fromContextDidSave: notification)
}
}
func persistentStoreEnsemble(_ ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [Any]!) -> [Any]! {
let Films = objects as! [BaseFilms]
print("films map")
return Films.map { $0.id }
}
}
And when I login my cloudkit dashboard I see that a container with has been created but there isn't any recors in the datas folder.
And nothing is synchonised between my devices.
Can you tell me where I am wrong??
Thank you.
By putting the setup code in an action, my guess is you are creating a new Ensemble (and Core Data stack) every time you press the button. You should setup the stack and ensemble once, perhaps on launch in viewDidLoad, and keep it in a property.
Note that the first time you call sync, it will "leech". This involves importing your local data, but it does not upload anything. The second time you call sync, it will download data from the cloud and upload. So you need to call sync twice — with the same ensemble object — before you will see any data in the CloudKit web interface.
Note also that you can only see data in CloudKit from the logged in user. So you still won't see anything unless you login there with the account you are testing with.
To solve my issues:
Put the code in the correct place (in the AppDelegate).
Install the latest update of Ensembles (1.7.1 instead of 1.7).
And that's all! Thank you Drew.
Just one thing still strange: When I add an object in one device it is synchronised on the other but when I delete an object it's not deleted on the other device and created again on the first device.
Attached is my code below.
The line that is giving me the problems is let fetchRequest = try moc.executeFetchRequest(fetchRequest) as! [AppSettings] appears to load asynchronously but I want it to load synchronously so that I can ensure it checks properly for a username record.
How do I do this?
I know it loads asynchronously because when I start and stop the program constantly it will find the entity roughly 80% of the time and randomly 20% of the time it will not. Since nothing else is changing the entity (since I'm just starting and stopping the program constantly), it would make sense that the code is being run asynchrnously so when I use the command
guard let appSettingsArrayItem = fetchRequest.first where fetchRequest.count>0 else {
print ("no entities found...")
return false
}
It fails to find any entities sometimes.
Check Login Function
func checkIfLoggedInAlready() -> Bool{
let fetchRequest = NSFetchRequest(entityName: "AppSettings")
//let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) //Deletes ALL appsettings entities
do {
let fetchRequest = try moc.executeFetchRequest(fetchRequest) as! [AppSettings]
guard let appSettingsArrayItem = fetchRequest.first where fetchRequest.count>0 else {
print ("no entities found...")
return false
}
guard let username = (appSettingsArrayItem as AppSettings).username else{
print ("username not found")
return false
}
print("number Of AppSetting Entities =\(fetchRequest.count)")
print(username)
//The following code deletes ALL the entities!
//try moc.persistentStoreCoordinator!.executeRequest(deleteRequest, withContext: moc)
//To delete just '1' entry use the code below.
//moc.deleteObject(appSettingsArrayItem)
//try moc.save()//save deletion change.
//print("deleted particular entity item")
return true
} catch{
fatalError("bad things happened \(error)")
}
}
Entire LoginViewController including Check Login Function
import UIKit
import CoreData
class LoginViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var usernameField: UITextField!
#IBOutlet weak var passwordField: UITextField!
var isLoggedIn = false
let moc = DataController().managedObjectContext
#IBAction func SignUpButtonPressed(sender: UIButton) {
print("sign up")
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
view.addGestureRecognizer(tap)
print("view loaded, check if already signed in here")
let loggedIn = checkIfLoggedInAlready() //checks database to see
if(loggedIn){
print("was logged in!")
isLoggedIn = true
self.performSegueWithIdentifier("loginSegue", sender: self)
}
}
func checkIfLoggedInAlready() -> Bool{
let fetchRequest = NSFetchRequest(entityName: "AppSettings")
//let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) //Deletes ALL appsettings entities
do {
let fetchRequest = try moc.executeFetchRequest(fetchRequest) as! [AppSettings]
guard let appSettingsArrayItem = fetchRequest.first where fetchRequest.count>0 else {
print ("no entities found...")
return false
}
guard let username = (appSettingsArrayItem as AppSettings).username else{
print ("username not found")
return false
}
print("number Of AppSetting Entities =\(fetchRequest.count)")
print(username)
//The following code deletes ALL the entities!
//try moc.persistentStoreCoordinator!.executeRequest(deleteRequest, withContext: moc)
//To delete just '1' entry use the code below.
//moc.deleteObject(appSettingsArrayItem)
//try moc.save()//save deletion change.
//print("deleted particular entity item")
return true
} catch{
fatalError("bad things happened \(error)")
}
}
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
print("prepare seque")
}
func displayErrorMessage(errorMessage: String){
print("show error console with Error:"+errorMessage)
let alert = UIAlertController(title: "Error", message: errorMessage, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
switch(identifier){
case "loginSegue":
print("Is the user already logged in?")
if(isLoggedIn){
print("Detected as YES")
return true
}
print("Detected as NO, so checking username and password fields next...")
guard let password = passwordField.text!.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet()) where !password.isEmpty else {
displayErrorMessage("Password can not be empty!")
return false
}
guard let username = usernameField.text!.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet()) where !username.isEmpty else{
displayErrorMessage("Username can not be empty!")
return false
}
let url = "http://distribution.tech/restapi/v1/userlogin?email="+username+"&password="+password
print(url)
let json = JSON(url:url)
print(json)
if(json["status"].asInt==1){
let entity = NSEntityDescription.insertNewObjectForEntityForName("AppSettings", inManagedObjectContext: moc) as! AppSettings
entity.setValue(username, forKey: "username")
entity.setValue(password, forKey: "password")
entity.setValue(json["tokenid"].asString, forKey: "token")
entity.setValue(json["roleid"].asInt, forKey: "roleid")
entity.setValue(json["role"].asString, forKey: "role")
entity.setValue(json["companyid"].asInt , forKey: "companyid")
entity.setValue(json["isdev"].asInt, forKey: "isdev")
//save token and other details to database.
do {
try moc.save()
print("saved to entity")
}catch{
fatalError("Failure to save context: \(error)")
}
// token
// roleid int
// role
// companyid int
//
// {
// "companyid": 3,
// "userid": 2,
// "tokenid": "804febae26ddbd0292b3d2c66b30afd5028d5ba9",
// "status": 1,
// "roleId": 1,
// "role": "super_admin",
// "isdev": 0
// }
//Save to disk using our own method, as COREDATA is unreliable!
return true //login succesfull
}else{
displayErrorMessage("Incorrect Username or Email")
return false//failed
}
default:
displayErrorMessage("Unknown Error Related To Segue Not Found")
}
return false //if it gets to this point assume false
}
}
The managed object is created in the DataController its file is here below.
import UIKit
import CoreData
class DataController: NSObject {
var managedObjectContext: NSManagedObjectContext
override init() {
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = NSBundle.mainBundle().URLForResource("AppSettings", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
self.managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
self.managedObjectContext.persistentStoreCoordinator = psc
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.URLByAppendingPathComponent("AppSettings.sqlite")
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch {
fatalError("Error migrating store: \(error)")
}
}
}
}
Image Reference Of Entity & Console Error That Can Happen Sometimes
Image Reference Of Entity & Console When It Does Find Entity Most Of Time
ManagedObjectContext.ExecuteFetchRequest already runs synchronously but it looks like you are setting up your persistent store coordinator asynchronously in a background priority thread.
If this fetch request happens immediately when the app starts up, and you do it over and over again, it may not be finished setting up some of the times.
Okay the answer above was correct, so what I did was created a new project single view, selected core data option, and copied code from its AppDelegate over my own AppDelegate to get the proper CoreData Init Code, and in such a way that when the project terminates it saves the context correctly and so forth. The code looks like this.
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> 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 throttle down OpenGL ES frame rates. 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 inactive 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 applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "com.distribution.tech.Test" in the application's documents Application Support directory.
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1]
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("AppSettings", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added 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.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("AppSettings.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() 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.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() 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
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
Its key when you do this that you change the reference to your own xcdatamodeld or this won't work. In my case it was changing this line to the correct sqlite based on my previous work.
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("AppSettings.sqlite")
and this line...
let modelURL = NSBundle.mainBundle().URLForResource("AppSettings", withExtension: "momd")!
which is the actual name of the xcdatamodeld file.
Hope this helps someone who had same issue as me. Oh...and apple if you are reading this...please add 'core data' option for tab based projects in the future... and not just single view.
I am building an app that has a "Playlist" feature. Users can create new, empty playlists and then add contents to them.
I decided to use Core Data to do this. So I did some research and created this object model:
where the Utterance entity represents an item in a playlist.
The view controller that I used to display the playlist is UITableViewController. Here is part of the class:
var playlists: [Playlists] = []
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
override func viewDidLoad() {
if dataContext != nil {
let entity = NSEntityDescription.entityForName("Playlist", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let playlists = try? dataContext.executeFetchRequest(request)
if playlists != nil {
for item in playlists! {
self.playlists.append(item as! Playlists)
print((item as! Playlists).name)
}
}
}
}
Playlists is a NSManagedObject subclass generated by Xcode. In viewDidLoad, I get all the playlists and put them in self.playlists. Also please note that the tableView are implemented correctly.
Now I am writing the action method when the user taps on the add playlist button. I want to show an alert asking the user for the name of the playlist. If he/she doesn't enter anything, failAlert will be displayed. Otherwise, I create a new Playlist object and set its name to the textfield's text and save it in the database. Here's the code:
#IBAction func addPlaylist(sender: UIBarButtonItem) {
let alert = UIAlertController(title: "新播放列表", message: "请输入播放列表的名字", preferredStyle: .Alert)
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.placeholder = "名字"
})
alert.addAction(UIAlertAction(title: "确定", style: .Default, handler: { (action) -> Void in
if alert.textFields?.first?.text == "" || alert.textFields?.first?.text == nil {
let failAlert = UIAlertController(title: "失败", message: "播放列表名不能为空", preferredStyle: .Alert)
failAlert.addAction(UIAlertAction(title: "确定", style: .Default, handler: nil))
self.presentViewController(failAlert, animated: true, completion: nil)
return
}
let newPlaylist = Playlists(entity: NSEntityDescription.entityForName("Playlist", inManagedObjectContext: self.dataContext)!, insertIntoManagedObjectContext: self.dataContext)
newPlaylist.name = alert.textFields?.first?.text
if let _ = try? self.dataContext.save() {
print("Error occured")
}
self.tableView.reloadData()
}))
self.presentViewController(alert, animated: true, completion: nil)
}
As you can see, I wrote this:
newPlaylist.name = alert.textFields?.first?.text
if let _ = try? self.dataContext.save() {
print("Error occured")
}
self.tableView.reloadData()
So if an error occurred in saving process, it would print Error occurred. When I tested the app by clicking on the add playlist button, it asked me for the name of the playlist. So I entered some random letters like ggg and unfortunately it prints Error occured! Also, the table view remained empty.
I really didn't understand why and thought that the data is not saved. But when I ran the app again, I see ggg in the table view! This is so weird! An error occurred but it saved the data successfully! Why is this? What is the error?
Edit:
A lot of the answers says that save returns a Bool. But Xcode says it does not:
That's clearly the word Void!
NSManagedObject's save: method returns a boolean:
Return Value
YES if the save succeeds, otherwise NO.
Therefore, the if let statement is not the right way to go, as the method will always return something (a boolean) even if the save succeeds, causing the statements within the if to be run.
You should use Swift's error handling capabilities with a do-catch statement:
do {
try self.dataContext.save()
} catch let error as NSError {
print("Error: \(error)")
}
More about error handling in Swift 2.0
But this only fixes your problem with the save, not the fact that the new objects aren't appearing until you restart the app. To fix that, you need to look at the way you go about reloading data.
Right now, you are calling self.tableView.reloadData(), except the code you included shows that you are fetching the objects from the database in your viewDidLoad. Given that viewDidLoad is only called when the view is first loaded, the new object you added will not be included in the self.playlists array.
You should add the new playlist to self.playlists in the addPlaylist function, right after the save:
self.playlists.append(item as! Playlists)
According to Apple document, NSManagedObjectContext:save() returns true if the save succeeds, otherwise false.
Will you see error with the following code when saving the data:
do {
try self.dataContext.save()
} catch let error as NSError {
print("Error occurred")
}