我对 iOS 开发比较陌生,决定实现自己的 Core Data 堆栈,替换 Apple 的默认堆栈。
我必须对我的代码进行更改(显然)并且能够弄清楚它,但在这种情况下我不能。这是我的代码:
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)
}
}
该应用程序启动正常,但当我单击附加到保存功能的 UIButton 时,它崩溃并出现以下错误:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
调试器突出显示的行是:
if !managedContext.save(&error) {
不可否认,我必须花更多的时间在 Swift 中使用可选值,因为它们似乎经常给我带来麻烦,尽管通常我能够解决它。如果有人能指出我正确的方向,那就太好了。谢谢。
编辑:这是我的核心数据堆栈:
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)")
}
}
}
编辑:这是我的根视图控制器:
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) {
}
}