I am new to Core Data and I have encountered a problem. This app is supposed to store images from the photo library to core data and display those in a collection view.
However, the problem is the pictures display when the app is newly installed on the simulator and you just add pictures. But when you close the app and open again it crashes and shows an error in the console: Thread 1 EXC_BAD_ACCESS
// Loading Setup
@IBOutlet var collection: UICollectionView!
var images = [NSManagedObject]()
override func viewDidAppear(animated: Bool) {
let managedContext = AppDelegate().managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "FullRes")
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
images = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
collection.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
imagePicker.delegate = self
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return images.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! MainCollectionViewCell
let selectedObject = images[indexPath.row]
print(selectedObject)
let image: UIImage = UIImage(data: selectedObject.valueForKey("imageData") as! NSData!)!
cell.imageView.image = image
// Configure the cell
return cell
}
// MARK: UICollectionViewDelegate
/*
// Uncomment this method to specify if the specified item should be highlighted during tracking
override func collectionView(collectionView: UICollectionView, shouldHighlightItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
*/
/*
// Uncomment this method to specify if the specified item should be selected
override func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
*/
/*
// Uncomment these methods to specify if an action menu should be displayed for the specified item, and react to actions performed on the item
override func collectionView(collectionView: UICollectionView, shouldShowMenuForItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return false
}
override func collectionView(collectionView: UICollectionView, canPerformAction action: Selector, forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool {
return false
}
override func collectionView(collectionView: UICollectionView, performAction action: Selector, forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) {
}
*/
// Camera Setup
// the image picker
let imagePicker = UIImagePickerController()
// a queue to save the image without freezing the App UI
let saveQueue = dispatch_queue_create("saveQueue", DISPATCH_QUEUE_CONCURRENT)
// the Managed Object Context where we will save our image to.
let managedContext = AppDelegate().managedObjectContext
@IBAction func imageDidPress(sender: AnyObject) {
imagePicker.sourceType = UIImagePickerControllerSourceType.Camera
presentViewController(imagePicker, animated: true, completion: nil)
}
@IBAction func addDidPress(sender: AnyObject) {
imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
prepareImageForSaving(image)
self.dismissViewControllerAnimated(true, completion: nil)
}
/*
func deletePhotoFromLibrary(info: [String : AnyObject]) {
print("wasrun")
let imageUrl = info[UIImagePickerControllerReferenceURL] as! NSURL
let imageUrls = [imageUrl]
//Delete asset
PHPhotoLibrary.sharedPhotoLibrary().performChanges( {
let imageAssetToDelete = PHAsset.fetchAssetsWithALAssetURLs(imageUrls, options: nil)
PHAssetChangeRequest.deleteAssets(imageAssetToDelete)
},
completionHandler: { success, error in
NSLog("Finished deleting asset. %@", (success ? "Success" : error!))
})
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
}
*/
func prepareImageForSaving(image:UIImage) {
// use date as unique id
let date : Double = NSDate().timeIntervalSince1970
// dispatch with gcd.
dispatch_async(saveQueue) {
// create NSData from UIImage
guard let imageData = UIImageJPEGRepresentation(image, 1) else {
// handle failed conversion
print("jpg error")
return
}
// scale image
let thumbnail = self.scale(image: image, toSize: self.view.frame.size)
guard let thumbnailData = UIImageJPEGRepresentation(thumbnail, 0.7) else {
// handle failed conversion
print("jpg error")
return
}
// send to save function
self.saveImage(imageData, thumbnailData: thumbnailData, date: date)
}
}
func saveImage(imageData:NSData, thumbnailData:NSData, date: Double) {
dispatch_barrier_async(saveQueue) {
// create new objects in moc
guard let fullRes = NSEntityDescription.insertNewObjectForEntityForName("FullRes", inManagedObjectContext: self.managedContext) as? FullRes, let thumbnail = NSEntityDescription.insertNewObjectForEntityForName("Thumbnail", inManagedObjectContext: self.managedContext) as? Thumbnail else {
// handle failed new object in moc
print("moc error")
return
}
//set image data of fullres
fullRes.imageData = imageData
//set image data of thumbnail
thumbnail.imageData = thumbnailData
thumbnail.id = date as NSNumber
//set relationship between the two objects
thumbnail.fullRes = fullRes
// save the new objects
do {
try self.managedContext.save()
} catch {
// implement error handling here
fatalError("Failure to save context: \(error)")
}
// clear the moc
self.managedContext.refreshAllObjects()
}
}
func scale(image image:UIImage, toSize newSize:CGSize) -> UIImage {
// make sure the new size has the correct aspect ratio
let aspectFill = resizeFill(image.size, toSize: newSize)
UIGraphicsBeginImageContextWithOptions(aspectFill, false, 0.0);
image.drawInRect(CGRectMake(0, 0, aspectFill.width, aspectFill.height))
let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
func resizeFill(fromSize: CGSize, toSize: CGSize) -> CGSize {
let aspectOne = fromSize.height / fromSize.width
let aspectTwo = toSize.height / toSize.width
let scale : CGFloat
if aspectOne < aspectTwo {
scale = fromSize.height / toSize.height
} else {
scale = fromSize.width / toSize.width
}
let newHeight = fromSize.height / scale
let newWidth = fromSize.width / scale
return CGSize(width: newWidth, height: newHeight)
}
Update: Based on the comments made below, I have made changes to the code.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! MainCollectionViewCell
if let imagePath = images[indexPath.row].imageData {
cell.imageView.image = UIImage(data: imagePath)
}
// Configure the cell
return cell
}
But now, I get another error: 2015-12-26 20:10:24.464 Collect[480:69113] -[Thumbnail imageData]: unrecognized selector sent to instance 0x13fe9ba00.
The code class AppDelegate: UIResponder, UIApplicationDelegate {
on AppDelegate.swift is highlighted.
It is an interesting thing that the app works until a certain point. For instance, when I uninstall the app from the phone I am running it in and then rerun it again, it works for a while until it crashes.
But still, it does not work perfectly like I hope it would be. Sometimes, one picture will appear only if another picture is added.
Update 2: I made some adjustments based on your comments below:
override func viewDidAppear(animated: Bool) {
authenticateUser()
let managedContext = AppDelegate().managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Thumbnail")
managedContext.performBlockAndWait { () -> Void in
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
self.images = results as! [Thumbnail]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
collection.reloadData()
}
It works fine whenever I delete the app and rerun it. However, when the app is closed, it no longer has the capability to restore the images.
I still get -[Thumbnail imageData]: unrecognized selector sent to instance 0x13762d650
while running it. The highlighted code is class AppDelegate: UIResponder, UIApplicationDelegate {
on AppDelegate.swift