Reputation: 1584
I'm implementing a Swift version of a Stanford CS193p assignment (PhotoMania).
I'm confused as to why my NSObjectContext save command is not saving to the persistent store I believe I've created.
The aim of the app is to have two tab bars: - Tab bar #1 contains recent photos from Flickr. - Tab bar #2 contains my most recently viewed photos. That means that every time a user taps on a Flickr photo cell to view the image, I should be storing that image's info in a persistent storage structure to access it in tab bar #2.
Here's how the app is structured:
1. The AppDelegate contains the storage setup instructions:
import UIKit
import CoreData
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var objectContext:NSManagedObjectContext?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
self.objectContext = self.createMainQueueManagedObjectContext()
self.testModel()
return true
}
func testModel(){
var photoModel: Photo? = NSEntityDescription.insertNewObjectForEntityForName("Photo", inManagedObjectContext: self.objectContext!) as? Photo
photoModel!.title = "hi"
photoModel!.subtitle = "subtitle"
self.objectContext!.insertObject(photoModel)
self.objectContext!.save(nil)
println(self.objectContext!.persistentStoreCoordinator.persistentStores[0].objects)
}
func createManagedObjectModel()-> NSManagedObjectModel{
var managedObjectModel:NSManagedObjectModel?
let modelURL:NSURL = NSBundle.mainBundle().URLForResource("PhotoModel", withExtension: "momd")
managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL)
return managedObjectModel!
}
func createPersistentStoreCoordinator()-> NSPersistentStoreCoordinator{
var persistentStoreCoordinator:NSPersistentStoreCoordinator?
var managedObjectModel:NSManagedObjectModel = self.createManagedObjectModel()
let storeURL:NSURL = self.applicationDocumentsDirectory().URLByAppendingPathComponent("MOC.sqlite")
var error:NSError?
persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
if (persistentStoreCoordinator?.addPersistentStoreWithType(NSSQLiteStoreType as String, configuration: nil, URL: storeURL, options: nil, error: &error) == nil){
NSLog("unresolved error %@, %@", error!, error!.userInfo)
abort()
}
return persistentStoreCoordinator!
}
func applicationDocumentsDirectory()-> NSURL{
var hello:NSURL = NSFileManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as NSURL
return hello
}
func createMainQueueManagedObjectContext()->NSManagedObjectContext{
var managedObjectContext:NSManagedObjectContext?
var coordinator:NSPersistentStoreCoordinator = self.createPersistentStoreCoordinator()
if (coordinator != nil){
managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
managedObjectContext!.persistentStoreCoordinator = coordinator
}
return managedObjectContext!
}
}
2. There is a UITableView subclass that gets the context from the AppDelegate:
class MyViewController: UITableViewController {
@IBOutlet weak var refresher: UIRefreshControl?
let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
var objectContext:NSManagedObjectContext?{
didSet{
self.fetchDatabasePhotos()
}
}
var fetchController:NSFetchedResultsController?{
didSet{
self.tableView.reloadData()
}
}
override func viewDidLoad() {
self.objectContext = self.appDelegate.objectContext
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fetchDatabasePhotos(){
let request:NSFetchRequest = NSFetchRequest(entityName: "Photo")
request.predicate = nil
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
self.fetchController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.objectContext, sectionNameKeyPath: nil, cacheName: nil)
println(self.fetchController!)
// println(self.fetchController!.fetchedObjects)
}
}
3. There is a "JustPostedFlickr" subclass of MyViewController which manages the Flickr downloads. The Flickr downloading part works fine. But I try to insert a photo object in storage from a method called in prepareforsegue between the table view controller and the Image View controller. Here is the method that gets called:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let indexPath:NSIndexPath = self.tableView.indexPathForCell(sender as UITableViewCell)
if (segue.identifier == "Display Photo"){
if (segue.destinationViewController.isKindOfClass(ImageViewController)){
self.prepareImageViewController(segue.destinationViewController as ImageViewController, photo: self.photos[indexPath.row] as NSDictionary)
}
}
}
func prepareImageViewController(ivc:ImageViewController, photo:NSDictionary){
ivc.imageURL = FlickrFetcher.URLforPhoto(photo, format: FlickrPhotoFormatLarge)
ivc.title = photo.valueForKeyPath(FLICKR_PHOTO_TITLE) as String
var photoModel: Photo! = NSEntityDescription.insertNewObjectForEntityForName("Photo", inManagedObjectContext: self.objectContext) as Photo
photoModel!.title = photo.valueForKeyPath(FLICKR_PHOTO_TITLE) as String
photoModel!.subtitle = photo.valueForKeyPath(FLICKR_PHOTO_DESCRIPTION) as String
photoModel!.photoURL = FlickrFetcher.URLforPhoto(photo, format: FlickrPhotoFormatLarge).absoluteString
photoModel!.created_date = NSDate()
println(self.objectContext!)
var theVar:Bool = self.objectContext!.save(nil)
println(theVar)
self.fetchDatabasePhotos()
}
self.objectContext!.save(xx)
always returns true.
self.objectContext.registeredObjects
returns the correct photo model structure and values
However when I fetch the photos and print the fetched objects list, it always returns nil.
The controller for tab bar #2 (the recent photos) which is supposed to take the content from the fetchController. The fetchcontroller.fetchedObjects
method returns nil every time even after I tap on an image (which should populate the storage):
import UIKit
class RecentPhotosController: MyViewController {
override func viewDidAppear(animated: Bool) {
self.objectContext = self.appDelegate.createMainQueueManagedObjectContext()
super.viewDidAppear(animated)
println("I just appeared")
}
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
var sections:Int = 0
let fetchSections = self.fetchController!.sections
if (fetchSections != nil){
sections = fetchSections.count
}
return sections
}
override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
var rows:Int = 0
let fetchRows = self.fetchController!.fetchedObjects
if (fetchRows != nil){
rows = fetchRows.count
}
return rows
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Recent Photo", forIndexPath: indexPath) as UITableViewCell
let photo:Photo = self.fetchController!.objectAtIndexPath(indexPath)! as Photo
cell.textLabel.text = photo.title as String
cell.detailTextLabel.text = photo.subtitle as String
return cell
}
}
And FINALLY: here is the Photo model:
import Foundation
import CoreData
class Photo: NSManagedObject {
@NSManaged var title: String
@NSManaged var subtitle: String
@NSManaged var created_date: NSDate
@NSManaged var photoURL: String
}
My question is: why are the objects in the NSObjectContext not saving to storage?
Finally: I am testing on the simulator right now BUT I don't expect this to be the source of my issues because I implemented a Swift version of CS193p's Photomania (which uses core Data and persistent storage) and that worked fine.
Upvotes: 1
Views: 818
Reputation: 1584
I figured it out. The reason it failed is that I had not called the performfetch method after defining my fetchController.
let request:NSFetchRequest = NSFetchRequest(entityName: "Photo")
request.predicate = nil
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
var fetchController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.objectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchController.performFetch(nil)
The code for this (for which I drew inspiration from Stanford's obj-C code) was hidden in the course's predelivered coredata controller.
If anyone is stumped by this problem, a possible solution is make sure to call performfetch after defining your fetchcontroller in the UITableView or any other time you need to call the data.
Here's how you know the error is happening:
1) debug your Sqlite while running the simulator by adding this script: -com.apple.CoreData.SQLDebug 3 2) (level 3 allows you to check for updates to objects, level 1 will not)
3) Is your data getting inserted but you still can't see it? Make sure you are performing a fetch!
To be honest I suspect bot many people would get caught for 2 days on this mistake but in case we are legion, this should help.
Upvotes: 1
Reputation: 17958
You appear to have two controller classes which each call self.appDelegate.createMainQueueManagedObjectContext()
createMainQueueManagedObjectContext
creates a new context for each controller. That's fine but it also calls createPersistentStoreCoordinator
which creates a new persistent store coordinator for each context. That is a problem as now you have two store coordinators, each with their own persistent store, racing to write to the same file on disk. Who knows what's going to happen depending on when each store reads or writes the file relative to the other store.
What you probably want to be doing is either:
Upvotes: 2