iOS 8 Core Data stack - fatal error: found nil whi

2019-03-30 12:52发布

问题:

I'm relatively new to iOS Development and decided to implement my own Core Data stack, replacing Apple's default stack.

I've had to make changes in my code (obviously) and have been able to figure it out, however in this instance I cannot. Here is my code:

import UIKit
import CoreData

class AddTableViewController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    @IBOutlet weak var nameTextField:UITextField!
    @IBOutlet weak var locationTextField:UITextField!
    @IBOutlet weak var imageView:UIImageView!
    @IBOutlet weak var notesView:UITextView!

    var coreDataStack = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack

    var backpackerSpot:BackpackerSpot!
    var managedContext: NSManagedObjectContext!

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // TODO Give user the choice of the Photo Library or the Camera
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row == 0 {
            if UIImagePickerController.isSourceTypeAvailable(.Camera) {
                let imagePicker = UIImagePickerController()
                imagePicker.allowsEditing = false
                imagePicker.delegate = self
                imagePicker.sourceType = .Camera

                self.presentViewController(imagePicker, animated: true, completion: nil)
            }
        }

        tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }

    // FIXME image is being displayed in landscape if it is taken in portrait mode by default
    func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
        imageView.image = image
        imageView.contentMode = UIViewContentMode.ScaleAspectFill
        imageView.clipsToBounds = true

        dismissViewControllerAnimated(true, completion: nil)
    }

    @IBAction func save() {

        //validation
        var errorField = ""

        // TODO have placeholder text in the NOTES field match up with the placholder text in the NAME and LOCATION fields.
        if nameTextField.text == "" {
            errorField = "name"
        } else if locationTextField.text == "" {
            errorField = "location"
        } else if notesView.text == "" {
            errorField = "notes"
        }

        if errorField != "" {

            let alertController = UIAlertController(title: "Error", message: "You must fill in \(errorField).", preferredStyle: .Alert)
            let doneAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
            alertController.addAction(doneAction)

            self.presentViewController(alertController, animated: true, completion: nil)

            return
        }

        // If all fields are correctly filled in, extract the field value
        // Create Restaurant Object and save to data store
//        if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack.context {

            let entityBackpackerSpot = NSEntityDescription.entityForName("BackpackerSpot", inManagedObjectContext: coreDataStack.context)

            backpackerSpot?.spotName = nameTextField.text
            backpackerSpot?.spotLocation = locationTextField.text
            backpackerSpot?.spotImage = UIImagePNGRepresentation(imageView.image)
            backpackerSpot?.spotNote = notesView.text

            var error: NSError?
            if !managedContext.save(&error) {
                println("insert error: \(error!.localizedDescription)")
                return
            }
        // Execute the unwind segue and go back to the home screen
        performSegueWithIdentifier("unwindToHomeScreen", sender: self)
    }

}

The app starts up fine, but when I click on the UIButton attached to the save function, it crashes and I get the following error:

fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)

The line the debugger highlights is:

  if !managedContext.save(&error) {

Admittedly, I have to spend some more time working with Optionals in Swift because they seem to often be a source of trouble for me, though usually I am able to figure it out. If anyone could point me in the right direction that would be great. Thank you.

EDIT: Here is my Core Data Stack:

import Foundation
import CoreData

class CoreDataStack {

    let model: NSManagedObjectModel
    let storeCoordinator: NSPersistentStoreCoordinator
    let context: NSManagedObjectContext
    let store: NSPersistentStore?

    let dbName = "BackpackerSpots"
    // these options do the migration for us
    let options = [NSInferMappingModelAutomaticallyOption:true,
        NSMigratePersistentStoresAutomaticallyOption: true]

    init() {

        //1 loading model from the file
        let bundle = NSBundle.mainBundle()
        let modelURL = bundle.URLForResource(dbName, withExtension: "momd")
        model = NSManagedObjectModel(contentsOfURL: modelURL!)!

        //2 store coordinator created using that model
        storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)

        //3 context
        context = NSManagedObjectContext()
        context.persistentStoreCoordinator = storeCoordinator

        //4. store
        let documentsURL = MyDocumentDirectory()
        let storeURL = documentsURL.URLByAppendingPathComponent(dbName)

        var error:NSError?

        store = storeCoordinator.addPersistentStoreWithType(NSSQLiteStoreType,
            configuration: nil,
            URL: storeURL,
            options: nil,
            error: &error)

        if store == nil {
            println("error adding persistent store: \(error)")
            abort()
        }
    }

    func saveContext() {
        var error: NSError?

        if context.hasChanges && !context.save(&error) {
            println("Could not save. \(error), \(error?.description)")
        }
    }

}

EDIT: This is my root view controller:

import UIKit
import CoreData

class BackpackerSpotTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

    var coreDataStack = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack

//    var backpackerSpot:BackpackerSpot!
    var managedContext: NSManagedObjectContext!

    var backpackerSpots:[BackpackerSpot] = []

    var fetchResultController:NSFetchedResultsController!

    // Let's add some animations!
    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        // initial cell state
        cell.alpha = 0

        // state after animations
        UIView.animateWithDuration(1.0, animations: { cell.alpha = 1})
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        var fetchRequest = NSFetchRequest(entityName: "BackpackerSpot")
        let sortDescriptor = NSSortDescriptor(key: "spotName", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]

//        if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack {

        let entityBackpackerSpot = NSEntityDescription.entityForName("BackpackerSpot", inManagedObjectContext: coreDataStack.context)

            fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
            fetchResultController.delegate = self

            var error: NSError?

            var result = fetchResultController.performFetch(&error)
            backpackerSpots = fetchResultController.fetchedObjects as [BackpackerSpot]

            if result != true {
                println(error?.localizedDescription)
            }
        }





    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.backpackerSpots.count
    }


    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cellIdentifier = "Cell"
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as CustomTableViewCell


        let backpackerSpot = backpackerSpots[indexPath.row]
        cell.nameLabel.text = backpackerSpot.spotName
        cell.thumbnailImageView.image = UIImage(data: backpackerSpot.spotImage)
        cell.locationLabel.text = backpackerSpot.spotLocation


        return cell
    }

    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {


    }


    override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {


        var deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete",handler: {
            (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in


//            if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
           let entityBackpackerSpot = NSEntityDescription.entityForName("BackpackerSpot", inManagedObjectContext: self.coreDataStack.context)

                let backpackerSpotToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as BackpackerSpot
                self.managedContext.deleteObject(backpackerSpotToDelete)

                var error: NSError?
                if !self.managedContext.save(&error) {
                    println("Could not save \(error), \(error?.description)")
                }


        })
        deleteAction.backgroundColor = UIColor(red: 169.0/255.0, green: 37.0/255.0, blue:
            50.0/255.0, alpha: 1.0)
        return [deleteAction]
    }

    func controllerWillChangeContent(controller: NSFetchedResultsController!) {
        tableView.beginUpdates()
    }

    func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {

        switch type {
        case .Insert:
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        case .Delete:
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        case .Update:
            tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

        default:
            tableView.reloadData()
        }

        backpackerSpots = controller.fetchedObjects as [BackpackerSpot]
    }

    func controllerDidChangeContent(controller: NSFetchedResultsController!) {
        tableView.endUpdates()
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
        if segue.identifier == "showBackpackerSpotDetails" {
            if let row = tableView.indexPathForSelectedRow()?.row {
                let destinationController = segue.destinationViewController as DetailViewController
                destinationController.backpackerSpot = backpackerSpots[row]
            }
        }
    }

    @IBAction func unwindToHomeScreen(segue: UIStoryboardSegue) {

    }

}

回答1:

You haven't initialized your NSManagedContext variable. With the default Core Data set up you can do this:

 override func viewDidLoad() {
      if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
          managedContext = appDelegate.managedObjectContext
      }
 }

EDIT: Given your core data setup, you should do this:

 override func viewDidLoad() {
      managedContext = coreDataStack.context
 }