Store image in Core Data in swift - swift

i'm trying to save a local image in my app to the core data model. I have created the entity and created an attribute for the image. But when i run the app it crashes with this error, Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "image"; desired type = NSData; given type = UIImage; value = <UIImage: My code to save image is,
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Saloon", in: context)
let newUser = NSManagedObject(entity: entity!, insertInto: context)
newUser.setValue(nameLbl.text, forKey: "name")
newUser.setValue(addressLbl.text, forKey: "address")
newUser.setValue(distanceLbl.text, forKey: "distance")
newUser.setValue(rating.text, forKey: "rating")
newUser.setValue(imageView.image, forKey: "image") as? UIImage
do {
try context.save()
print("saved")
} catch {
print("Failed saving")
}
This is how i'm fetching the image,
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Saloon")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "name") as! String)
let image: Data = data.value(forKey: "image")! as! Data
let decodedimage = UIImage(data: image)
imageView2.image = decodedimage
name2Lbl.text = data.value(forKey: "name") as? String
address2Lbl.text = data.value(forKey: "address") as? String
distance2Lbl.text = data.value(forKey: "distance") as? String
rating2Lbl.text = data.value(forKey: "rating") as? String
}
} catch {
print("Failed")
}

You cannot save a UIImage directly in Core Data. Instead, you’ll have to convert the image to NSData (as in “desired type = NSData” in your error message). Here’s a UIImage extension for that:
extension UIImage {
func jpegData(withCompressionQuality quality: CGFloat) -> Data? {
return autoreleasepool(invoking: {() -> Data? in
return UIImageJPEGRepresentation(self, quality)
})
}
}
The quality is a value from 0.0 through 1.0 with the latter representing the maximum quality of the JPEG compressed image. The use of autoreleasepool helps avoid occasional memory leakage problems observed when using UIImageJPEGRepresentation.
You can then save the image using this code:
if let imageData = imageView.image .jpegData(withCompressionQuality: 1.0) {
newUser.setValue(imageData, forKey: "image")
}
BTW, you can also convert to PNG instead of JPEG which is lossless if this is what you need. Use UIImagePNGRepresentation instead of UIImageJPEGRepresentation.

Deferring from the previous answer, it is possible to store images in Core Data as UIImage. Although this is not considered to be good practice as Databases are not meant for storing files. We don't use core data to store the images directly instead we use the file path to where the image data is stored on your phone.
Anyways, the best method I found out while developing a side-project is demonstrated below
Set the entity codeGen to Manual/None
Select the attribute you want to be of Image type and choose transformable as it's type
Enter Custom class identifier as UIImage
Go to editor and select Create NSManagedObject Subclass
The Process will create 2 swift files depending on the number of your entities(I only had 1 entity). Now, select the <EntityName> + CoreDataProperties.swift file and import UIKit
If, on clicking Jump to Definition for the #NSManaged public var <attributeName: UIImage? you are able to land on the UIImage definition your work is done.
Perform actions on the entities just like you would and you will be able to fetch, save, edit the image attribute by down-casting <EntityName>?.value(forKey: "<attributeName>") as? UIImage.
I had to use the entity as NSManagedObject type and was for some reason not able to access the image directly from the created subclass.

Related

UIImage saved in CoreData is not the same after loading

I have a CoreData entity that has a uiImage property, of type 'binary data' that is supposed to hold a Data object representing a UIImage.
I am saving the image in CoreData before making sure that it's the correct image, but debugging and looking at it with the preview button:
This is how I save the image in CoreData:
let myEntity = NSEntityDescription.insertNewObject(forEntityName: "MyEntity", into: context) as! MyEntity
let uiImage = ... // The image that I was able to inspect with a preview
myEntity.uiImage = uiImage.jpegData(compressionQuality: 1.0)
...
try context.save()
Then I fetch the image later with this code:
let context = PersistentContainer.shared.container.viewContext
let request = NSFetchRequest<SaliencyImage>(entityName: "MyEntity")
request.predicate = NSPredicate(
// This is the key that I use to identity my entity
format: "assetId == %#", assetId
)
let myEntity = try context.fetch(request).first
let uiImage = UIImage(data: myEntity.uiImage!)!
But if I inspect the image, this is what I get in a preview:
Like you can see, only the top left corner is saved/fetched. Even though I made sure that the image was the correct one before saving it in CoreData. Does anybody have an explanation/solution for this?
Your issue is at below line.
let myEntity = try context.fetch(request).first
You are just taking first record of the image. There are more. So you have to for loop for the same.
Code should be as below.
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: 'Your Entity Name')
do {
result = try managedContext.fetch(fetchRequest)
for data in result as! [NSManagedObject] {
storedImageData.append(data.value(forKey: "storedImage") as! Data)
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
let image = UIImage(data: storedImageData)
Reference

Batch updating CoreData without performance issues

I have a tableView which I populate with cells. The data is being fetched as json from a private api and stored as CoreData. As the cells are being reused I fetched their imageView via a delegate method (from web URL). The problem occurs when I try to save that image data into that managed object. So when the image data is fetched, I fetch the managedObject, add the image data and re-save it. However this has a tremendous impact on the tableview performance (lagging tableview scrolling). I tried fetching and saving in background (privateQueueConcurrencyType) but nothing changes. Any recommendation is greatly appreciated.
func updateCoreDataImage(image:Data, for Url:String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let priMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
priMOC.parent = context
priMOC.perform {
do{
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "DB")
request.predicate = NSPredicate(format: "url = %#", Url)
request.includesSubentities = false
request.returnsObjectsAsFaults = false
let result = try? context.fetch(request)
let object = result![0] as! NSManagedObject
object.setValue(image, forKey: "image")
try priMOC.save()
print("save")
} catch {
print(error.localizedDescription)
}
}
}

Saving UIImage Into Core Data Always Returns Nil when using ImagePickerView - Swift 3

I've been trying to save a single picture to an entity containing a single property titled "prof" and configured as a Binary Data type.
I go through the hoops to select a picture from UIImagePickerViewController, then I call up my method that handles saving the picture in Core Data in the desired NSData format.
My issue stems from loading the picture, in my loadImage method the entity for the image is not nil, meaning it does exist. However, I get nil when I try to parse the fetched NSData to a UIImage format to recreate the picture and then be able to use it.
Now i am using Swift 3 and Xcode 8, so far all the troubleshooting questions on here have the solution of casting the NSData to UImage like so:
let image : UIImage = UIImage(data: imageData)
however, xcode gives me a compiler error when I do this, and instead forces me to cast it as:
let image : UIImage = UIImage(data: (imageData as Data?)!)
which is where i get the nil that's throwing up my flow in the air... i've tried saving the data in many different ways, but still nothing.
if anyone could go through my following methods, see if i might be doing something wrong in the saving part, or the formating of NSData on the fetch method... anything would help.
My configuration:
-the prof property has "Allow external storage" set to true
-my persistent store is seeded blank at the app installation, meaning all the needed properties are already set up when the app is launched for the first time, but obviously set to nil until changed or modified by my various data flows.
-There is no other picture entity in my data model, this is the only one.
func saveProfilePicture(_ pic: UIImage){
let picData = UIImagePNGRepresentation(pic)
let request: NSFetchRequest<UsePics> = UsePics.fetchRequest()
do {
let records = try coreDataManager.managedObjectContext.fetch(request) as [UsePics]
let first = (records.first)
first?.setValue(picData, forKey: "prof")
try context.save()
} catch let err {
print(err)
}
}
func getProfilePicture() -> UIImage? {
let request: NSFetchRequest<UsePics> = UsePics.fetchRequest()
var image : UIImage?
do {
let records = try coreDataManager.managedObjectContext.fetch(request) as [UsePics]
let first = (records.first?.prof) as NSData
if let parsedImage : UIImage = UIImage(data: (first as Data?)!) as? UIImage {
image = parsedImage
}
} catch let err {
print(err)
}
return image
}
EDIT
The solution was found by noticing that in Swift 3, the UIImage class adheres to the Transformable protocol. Swapping my property type for the image from Binary Data to Transformable actually made it possible to save the UIImage as UIImage directly into Core Data without parsing it to another data type.
func saveProfilePicture(_ image: UIImage){
let request: NSFetchRequest<UsePics> = UsePics.fetchRequest()
do {
let records = try coreDataManager.managedObjectContext.fetch(request) as [UsePics]
let first = (records.first)
first?.prof = image
print(first)
coreDataManager.saveData()
} catch let err {
print(err)
}
}
func loadProfilePicture() -> UIImage? {
var image : UIImage?
let request: NSFetchRequest<UsePics> = UsePics.fetchRequest()
do {
let records = try coreDataManager.managedObjectContext.fetch(request) as [UsePics]
let first = records.first
if let img = first?.prof {
image = img as? UIImage
} else {
print("no image")
}
} catch let err {
print(err)
}
return image
}

Core Data Image with Custom TableViewCell in Swift

I am new to Swift and iOS Development, and have not found a specific answer to my question.
I am attempting to populate a custom tableview with 3 Labels and 2 Images using Core Data.
I read up on optionals and have even built something similar to what I am trying to do now using the Subtitle option when creating the cell in the storyboard. That works.
My Core Data set up looks like this at the top of the class:
// MARK: - Managed Object Context
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
// Initialize Fetch Results
var fetchedResultsController = NSFetchedResultsController()
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Custom")
let sortDescriptor = NSSortDescriptor(key: "customFourImages", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
// MARK: - Retrieve Request
func getFetchRequest() -> NSFetchedResultsController {
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc!, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultsController
}
My viewDidLoad and viewDidAppear look like this:
// MARK: - Fetch
fetchedResultsController = getFetchRequest()
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch {
print("Failed to Perform Initial Fetch")
return
}
self.tableView.reloadData()
Finally, my cellForRowAtIndexPath looks like this:
// MARK: - Dequeue Reusable Cell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
// MARK: - Initiate Fetch Results for Index Path
let custom = fetchedResultsController.objectAtIndexPath(indexPath) as! Custom
let customLabel = custom.customLabels
let customTwoLabel = custom.customTwoLabels
let customThreeLabel = custom.customThreeLabels
let image = UIImage(named: "Custom Food")
let imageData = UIImagePNGRepresentation(image!)
custom.customFourImages = imageData
let images = UIImage(named: "Custom Drink")
let imagesData = UIImagePNGRepresentation(images!)
custom.customFiveImages = imagesData
// MARK: - Set Items in Cell to Returned Fetched Results
cell.customLabel?.text = "\(customLabel)"
cell.customTwoLabel?.text = "\(customTwoLabel)"
cell.customThreeLabel?.text = "\(customThreeLabel)"
cell.imageView?.image = UIImage(data: custom.customFourImages!)
cell.imageView?.image = UIImage(data: custom.customFiveImages!)
return cell
}
I have tried every permutation that I can think of and have scoured the Internet and StackOverflow for a specific answer. I would also prefer not to use Strings. I am using imagePickerController. But even with images in my imageAssets, e.g. "Custom Food", "Custom Drink", image is still found nil.
I can't crack it!
From your comments I can't workout where you are actually saving your managed objects to Core Data. you mention that you use try.moc?.save() but are you saving your individual images to core data? Furthermore, if your images are stored locally in the device, and not fetched from an URL, then you don't really need core data for this.
In the case that you are fetching your images from a URL or whatever, and assuming that you have added an entity MyImage with a imageData property of type NSData, you need to save the object like so:
class func createInManagedObjectContext(moc: NSManagedObjectContext, imgData: NSData) ->MyImage {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("MyImage", inManagedObjectContext: moc) as! MyImage
newItem.imageData = imagData
return newItem
}
Add that function to your MyImage.Swift file (generated by Core Data), so that when you need to save a new image to core data you just do:
func saveNewImage(data: {DATA}){
MyImage.createInManagedObjectContext(self.managedObjectContext, imgData: data);
}
Then, to fetch your objects:
func getSavedImages() -> [MyImage]? {
fetchRequest.sortDescriptors = [sortDescriptor]
do {
return try self.managedObjectContext.executeFetchRequest(fetchRequest) as? [MyImage]
} catch {
print("no records found")
return nil
}
}
If you are already doing this, can you please update your code with more details? pointing out where your error is happening (line number).
If your Image is failing here:
cell.imageView?.image = UIImage(data: custom.customFourImages!)
cell.imageView?.image = UIImage(data: custom.customFiveImages!)
Your fetch result is not getting records from Core Data, which is probably an issue with the above.
if your code is failing here:
let images = UIImage(named: "Custom Drink")
let imagesData = UIImagePNGRepresentation(images!)
You should be unwraping your images object with an if let img = images { ... } and most likely the error would be related to the name of the UIImage that you are getting from memory.
I hope this helps.
Cheers.
EDIT 1:
In reply to your comments:
try.moc.save will not save your images, it will persist any changes pending in the data model.
You need to actually create the managed object in core data, and you need to make sure that your fetch request is getting the relevant data out of Core Data.
Your code is failing because customFourImages is nil. Which in term comes from:
let image = UIImage(named: "Custom Food")
let imageData = UIImagePNGRepresentation(image!)
custom.customFourImages = imageData
....
let images = UIImage(named: "Custom Drink")
let imagesData = UIImagePNGRepresentation(images!)
custom.customFiveImages = imagesData
...
cell.imageView?.image = UIImage(data: custom.customFourImages!)
cell.imageView?.image = UIImage(data: custom.customFiveImages!)
This all seems a bit redundant.
Try this:
let image = UIImage(named: "Custom Food")
let imageData = UIImagePNGRepresentation(image!)
cell.imageView?.image = image
custom.createInManagedObjectContext(moc, imgData: imageData)
....
let images = UIImage(named: "Custom Drink")
let imagesData = UIImagePNGRepresentation(images!)
cell.imageView?.image = images //this is called in the same block? you are overwriting your imageView in the cell, maybe you have two?
custom.createInManagedObjectContext(moc, imgData: imagesData)
This will save the images to Core data and assign the right image to your cell view (I'm unsure if this will address your needs of saving images data to core data). Make sure you understand why you need to use core data and save the appropriate managed object that suits your requirements.

Error when trying to save image in NSUserDefaults using Swift

When i try to save an image in NSUserDefaults, the app crashed with this error.
Why? Is it possible to save an image with NSUserDefaults? If not, then how do I save the image?
Image...
Code...
var image1:UIImage = image1
var save1: NSUserDefaults = NSUserDefaults.standardUserDefaults()
save1.setObject(Info.Image1, forKey: "Image1")
save1.synchronize()
Log error...
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
NSUserDefaults isn't just a big truck you can throw anything you want onto. It's a series of tubes which only specific types.
What you can save to NSUserDefaults:
NSData
NSString
NSNumber
NSDate
NSArray
NSDictionary
If you're trying to save anything else to NSUserDefaults, you typically need to archive it to an NSData object and store it (keeping in mind you'll have to unarchive it later when you need it back).
There are two ways to turn a UIImage object into data. There are functions for creating a PNG representation of the image or a JPEG representation of the image.
For the PNG:
let imageData = UIImagePNGRepresentation(yourImage)
For the JPEG:
let imageData = UIImageJPEGRepresentation(yourImage, 1.0)
where the second argument is a CGFloat representing the compression quality, with 0.0 being the lowest quality and 1.0 being the highest quality. Keep in mind that if you use JPEG, each time you compress and uncompress, if you're using anything but 1.0, you're going to degrade the quality over time. PNG is lossless so you won't degrade the image.
To get the image back out of the data object, there's an init method for UIImage to do this:
let yourImage = UIImage(data:imageData)
This method will work no matter how you converted the UIImage object to data.
In newer versions of Swift, the functions have been renamed, and reorganized, and are now invoked as:
For the PNG:
let imageData = yourImage.pngData()
For the JPEG:
let imageData = yourImage.jpegData(compressionQuality: 1.0)
Although Xcode will autocorrect the old versions for you
In order to save UIImage in NSUserDefaults, you need to convert UIImage into NSData using UIImageJPEGRepresentation(image, 1) then save it in NSUserDefaults. And on the other side, while retrieving it on another ViewController (within that same application) you need to get NSData and then convert it into UIImage.
Here i am writing small code snippet in swift to demonstrate this. I tried this in XCODE 6.4
/*Code to save UIImage in NSUserDefaults on viewWillDisappear() event*/
override func viewWillDisappear(animated: Bool)
{
super.viewWillDisappear(animated)
//Get some image in image variable of type UIImage.
var image = ....
let defaults = NSUserDefaults.standardUserDefaults()
var imgData = UIImageJPEGRepresentation(image, 1)
defaults.setObject(imgData, forKey: "image")
}
/*Code to get Image and show it on imageView at viewWillAppear() event*/
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
let defaults = NSUserDefaults.standardUserDefaults()
if let imgData = defaults.objectForKey("image") as? NSData
{
if let image = UIImage(data: imgData)
{
//set image in UIImageView imgSignature
self.imgSignature.image = image
//remove cache after fetching image data
defaults.removeObjectForKey("image")
}
}
}
Updated for Swift 3:
If you want to save image in UserDefault used below lines of code save and retrieve the image;
To Save image in UserDefault:
if let image = response.result.value {
UserDefaults.standard.register(defaults: ["key":UIImageJPEGRepresentation(image, 100)!])
UserDefaults.standard.set(UIImageJPEGRepresentation(image, 100), forKey: "key")
}
To Retrieve the image from UserDefault and set it to ImageView:
var mLogoImageView = UIImageView()
if let imageData = UserDefaults.standard.value(forKey: "key") as? Data{
let imageFromData = UIImage(data: imageData)
mLogoImageView.image = imageFromData!
}
Enjoy..!
In Swift 4 - 5
Set:
setImage(image: UIImage(named: "12")!)
func setImage(image : UIImage) {
UserDefaults.standard.set(image.jpegData(compressionQuality: 100), forKey: "key")
}
Get
func getImage() -> UIImage? {
if let imageData = UserDefaults.standard.value(forKey: "key") as? Data{
if let imageFromData = UIImage(data: imageData){
return imageFromData
}
}
return nil
}