I am able to pick and display an image from the photo library, but my goal is to be able to save that picked image or file path to core data so that when that saved record is chosen that image will display also.
I have CoreData working and I am able to display text from CoreData fine it is only the image holding me up.
@IBAction func addPic(sender: AnyObject) { pickerController.delegate = self pickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary // 2 self.presentViewController(pickerController, animated: true, completion: nil) // Displays image func imagePickerController(picker: UIImagePickerController!,didFinishPickingMediaWithInfo info: NSDictionary!){ image.image = info[UIImagePickerControllerOriginalImage] as? UIImage self.dismissViewControllerAnimated(true, completion: nil)
Creating the Model In that entity , create one attribute . Name it img and make sure the attribute type is Binary Data, then click on the img attribute and go to Data Model Inspector. Check the box Allows External Storage. By checking this box, Core Data saves a reference to the data which will make for faster access.
To save an object with Core Data, you can simply create a new instance of the NSManagedObject subclass and save the managed context. In the code above, we've created a new Person instance and saved it locally using Core Data.
It is, but it isn't possible to store an image as is in the user's defaults database. The defaults system only supports strings, numbers, Date objects, and Data objects. This means that you need to convert the image to a Data object before you can store it in the user's defaults database.
Skip to Processing the Image to find out how to convert UIImage
to NSData
(which is what Core Data uses)
Or download from github
Core Data Setup:
Set up two entities : Full Resolution and Thumbnail. Full Resolutions is to store the original image. Thumbnail to store a smaller version to be used inside the app. You might use a smaller version in a UICollectionView
overview for example.
Images are stored as Binary Data
in Core Data
. The corresponding type in Foundation
is NSData
. Convert back to UIImage
with UIImage(data: newImageData)
Check the Allows External Storage box for the Binary Data fields. This will automatically save the images in the file system en reference them in Core Data
Connect the two entities, creating a one to one relationship between the two.
Go to Editor en select Create NSManagedObjectSubclass. This will generate files with Classes representing your Managed Object SubClasses. These will appear in your project file structure.
Basic ViewController Setup:
Import the following :
import UIKit import CoreData
UIButtons
and an UIImageView
in the Interface Builderclass ViewController: UIViewController { // imageview to display loaded image @IBOutlet weak var imageView: UIImageView! // image picker for capture / load let imagePicker = UIImagePickerController() // dispatch queues let convertQueue = dispatch_queue_create("convertQueue", DISPATCH_QUEUE_CONCURRENT) let saveQueue = dispatch_queue_create("saveQueue", DISPATCH_QUEUE_CONCURRENT) // moc var managedContext : NSManagedObjectContext? override func viewDidLoad() { super.viewDidLoad() imagePickerSetup() // image picker delegate and settings coreDataSetup() // set value of moc on the right thread } // this function displays the imagePicker @IBAction func capture(sender: AnyObject) { // button action presentViewController(imagePicker, animated: true, completion: nil) } @IBAction func load(sender: AnyObject) { // button action loadImages { (images) -> Void in if let thumbnailData = images?.last?.thumbnail?.imageData { let image = UIImage(data: thumbnailData) self.imageView.image = image } } } }
This function sets a value to managedContext
on the correct thread. Since CoreData needs all operations in one NSManagedObjectContext
to happen in the same thread.
extension ViewController { func coreDataSetup() { dispatch_sync(saveQueue) { self.managedContext = AppDelegate().managedObjectContext } } }
Extend the UIViewController
so it conforms to UIImagePickerControllerDelegate
and UINavigationControllerDelegate
These are needed for the UIImagePickerController
.
Create a setup function and also create the delegate function imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?)
extension ViewController : UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerSetup() { imagePicker.delegate = self imagePicker.sourceType = UIImagePickerControllerSourceType.Camera } // When an image is "picked" it will return through this function func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) { self.dismissViewControllerAnimated(true, completion: nil) prepareImageForSaving(image) } }
Immediately dismiss the UIImagePickerController
, else the app will appear to freeze.
Processing the Image:
Call this function inside imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?)
.
First get the current date with timeIntervalSince1970
. This returns an NSTimerInterval
in seconds. This converts nicely to a Double
. It will serve as a unique id for the images and as a way to sort them.
Now is a good time to move to the separate queue and free up the main queue. I used dispatch_async(convertQueue)
first to do the heavy lifting on a separate thread.
Then you need to convert the UIImage
to NSData
this is done with UIImageJPEGRepresentation(image, 1)
. The 1
represents the quality where 1
is the highest and 0
is the lowest. It returns an optional so I used optional binding.
Scale the image to a desired thumbnail size and also convert to NSData
.
Code:
extension ViewController { func prepareImageForSaving(image:UIImage) { // use date as unique id let date : Double = NSDate().timeIntervalSince1970 // dispatch with gcd. dispatch_async(convertQueue) { // create NSData from UIImage guard let imageData = UIImageJPEGRepresentation(image, 1) else { // handle failed conversion print("jpg error") return } // scale image, I chose the size of the VC because it is easy let thumbnail = image.scale(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) } } }
This function does the actual saving.
dispatch_barrier_sync(saveQueue)
do try catch
to attempt a saveBy using dispatch_barrier_sync(saveQueue)
we are sure that we can safely store a new image and that new saves or loads will wait until this is finished.
Code:
extension ViewController { func saveImage(imageData:NSData, thumbnailData:NSData, date: Double) { dispatch_barrier_sync(saveQueue) { // create new objects in moc guard let moc = self.managedContext else { return } guard let fullRes = NSEntityDescription.insertNewObjectForEntityForName("FullRes", inManagedObjectContext: moc) as? FullRes, let thumbnail = NSEntityDescription.insertNewObjectForEntityForName("Thumbnail", inManagedObjectContext: moc) 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 thumbnail.fullRes = fullRes // save the new objects do { try moc.save() } catch { fatalError("Failure to save context: \(error)") } // clear the moc moc.refreshAllObjects() } } }
To load an image :
extension ViewController { func loadImages(fetched:(images:[FullRes]?) -> Void) { dispatch_async(saveQueue) { guard let moc = self.managedContext else { return } let fetchRequest = NSFetchRequest(entityName: "FullRes") do { let results = try moc.executeFetchRequest(fetchRequest) let imageData = results as? [FullRes] dispatch_async(dispatch_get_main_queue()) { fetched(images: imageData) } } catch let error as NSError { print("Could not fetch \(error), \(error.userInfo)") return } } } }
The functions used to scale the image:
extension CGSize { func resizeFill(toSize: CGSize) -> CGSize { let scale : CGFloat = (self.height / self.width) < (toSize.height / toSize.width) ? (self.height / toSize.height) : (self.width / toSize.width) return CGSize(width: (self.width / scale), height: (self.height / scale)) } } extension UIImage { func scale(toSize newSize:CGSize) -> UIImage { // make sure the new size has the correct aspect ratio let aspectFill = self.size.resizeFill(newSize) UIGraphicsBeginImageContextWithOptions(aspectFill, false, 0.0); self.drawInRect(CGRectMake(0, 0, aspectFill.width, aspectFill.height)) let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } }
Core Data isn't meant to save big binary files like images. Use Document Directory in file system instead.
Here is sample code to achieve that.
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true).first as! String // self.fileName is whatever the filename that you need to append to base directory here. let path = documentsDirectory.stringByAppendingPathComponent(self.fileName) let success = data.writeToFile(path, atomically: true) if !success { // handle error }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With