Swift 3 - Saving images to Core Data - ios

For some reason I can't figure out how to save images to core data and fetch them again. I have a feeling it's something about my types but have a look:
I get my data from an api call to my server. It returns a base64 string.
Here is where I get the data:
updateAccessTokenOnly(newAccessToken: aToken!)
saveImageToDB(brandName: imageBrandName, image: data! )
Here I save it to my DB:
func saveImageToDB(brandName: String, image: Data) {
dropImages(){tableDropped in
let managedContext = getContext()
let entity = NSEntityDescription.entity(forEntityName: "CoffeeShopImage", in: managedContext)!
let CSI = NSManagedObject(entity: entity, insertInto: managedContext)
CSI.setValue(image, forKey: "image")
CSI.setValue(brandName, forKey: "brandName")
do {
try managedContext.save()
print("saved!")
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
then to fetch it:
func getImageFromDB(callback: #escaping (_ image: UIImage)-> ()) {
let fetchRequest: NSFetchRequest<NSManagedObject> = NSFetchRequest(entityName: "CoffeeShopImage")
do {
let searchResults = try getContext().fetch(fetchRequest)
for images in searchResults {
print("vi når her ned i get image")
if (images.value(forKey: "brandName")! as! String == "Baresso"){
print(images.value(forKey: "brandName")! as! String)
let image: Data = images.value(forKey: "image")! as! Data
let decodedimage = UIImage(data: image)
callback(decodedimage!)
}
}
} catch {
print("Error with request: \(error)")
}
}
Full error log:
https://docs.google.com/document/d/1gSXE64Sxtzo81eBSjv4bnBjBnnmG4MX2tuvNtnuJDIM/edit?usp=sharing
Hope someone can help. Thanks in advance!
UPDATED
So I uninstalled the app and then the code above worked. However the pictures come out blue? (yes I've checked that the pictures sent from the database are correct).
Any solution?

replace
let image: Data = images.value(forKey: "image")! as! Data
let dataDecoded : Data = Data(base64Encoded: image, options: [])!
let decodedimage = UIImage(data: dataDecoded)
with
let image: Data = images.value(forKey: "image")! as! Data
let decodedimage = UIImage(data: image)
Base64 is a way to to convert data to a string. There is no reason to use it here. You already have the data from the database you just want to convert it to a UIImage.
also change
let image = data?.base64EncodedData()
saveImageToDB(brandName: imageBrandName, image: image!)
to
saveImageToDB(brandName: imageBrandName, image: data!)
base64EncodedData is turning the data from image data into a utf-8 encoded based64encoded string. There is no reason for that.
You should get the base64 encoded string from server, convert it to data and then you never need base64 again. Read and write data to your database, and after you read it convert it to a UIImage. Base64 is an encoding method to transfer data. If you are not talking to the server there is no reason to use base64.

After the suggested corrections from Jon Rose all I needed was to add
.withRenderingMode(.alwaysOriginal)
to where I was showing my picture and the code worked.

Saving Image:
guard let managedObjectContext = managedObjectContext else { return }
// Create User
let user = User(context: managedObjectContext)
// Configure User
user.name = "name"
user.about = "about"
user.address = "Address"
user.age = 30
if let img = UIImage(named: "dog.png") {
let data = img.pngData() as NSData?
user.image = data
}
Fetching Image:
// Create Fetch Request
let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let users = try! managedContext.fetch(fetchRequest)
let user: User = users.first as! User
if let imageData = user?.image {
imgView.image = UIImage(data: imageData as Data)
}

Related

storing background images in userdefaults

I need to be able to set the background image for this button. I need to store this so after the app closes the background image is the same.
eventsFirstButton.backgroundColor = UIColor(patternImage: UIImage(named: "events")!)
You could just save the state:
Correct answer:
UserDefaults.standard.set(true, forKey: "TestAnswer1")
//If the answer is incorrect set to false
On load:
if UserDefaults.standard.bool(forKey: "TestAnswer1") {
view.backgroundColor = UIColor.green
// or any other logic
} else {
view.backgroundColor = UIColor.red
// or any other logic
}
It's better to save it as base64string, you don't want to store large value to UserDefaults.
To encode UIImage use this:
let image = UIImage()
let data = image.pngData()
let imageBase64String = data?.base64EncodedString()
UserDefaults.standard.set(imageBase64String, forKey: "encodedImage")
And for decoding and retrieving UIImage use this:
if let imageBase64String = UserDefaults.standard.value(forKey: "encodedImage"),
let url = URL(string: String(format:"data:application/octet-stream;base64,%#",imageBase64String))
{
do
{
let data = try Data(contentsOf: url)
let image = UIImage(data: data)
}
catch let error
{
print("Error decoding image")
}
}
If you really need to save the PNG, JPEG images locally, use CoreData to store them on the device.
You can use UserDefaults to save your image
Save
if let image = eventsFirstButton.imageView?.image {
let imageData = image.pngData()
UserDefaults.standard.set(imageData, forKey: "imageData")
}
Retrieve
if let imageData = UserDefaults.standard.data(forKey: "imageData") {
print("IMG data: ", imageData)
// your code here
}

Why am I getting Incorrect argument label in call

Getting "Incorrect argument label in call (have 'rest:', expected 'restaurant:')" error. Here is the code. The parameter is correct and I am passing the correct type? Is this because they are class methods?
class func save(restaurant: Restaurant, toCloud: Bool) -> Bool {
var rest:RestaurantMO
var saved:Bool = false
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
rest = RestaurantMO(context: appDelegate.persistentContainer.viewContext)
rest.name = restaurant.name
rest.item = restaurant.item
rest.location = restaurant.location
rest.isVisited = restaurant.isVisited
// Core Data Exercise - Solution
rest.phone = restaurant.phone
let entity =
NSEntityDescription.entity(forEntityName: "Restaurant",
in: appDelegate.persistentContainer.viewContext)!
_ = NSManagedObject(entity: entity,
insertInto: appDelegate.persistentContainer.viewContext)
print("Saving data to context ...")
appDelegate.saveContext()
saved = true
}
if toCloud {
saveRecordToCloud(rest:RestaurantMO) <--- ERROR: Incorrect argument label in call (have 'rest:', expected 'restaurant:')
}
}
class func saveRecordToCloud(restaurant:RestaurantMO!) -> Void {
// Prepare the record to save
let record = CKRecord(recordType: "Restaurant")
record.setValue(restaurant.name, forKey: "name")
record.setValue(restaurant.item, forKey: "item")
record.setValue(restaurant.location, forKey: "location")
record.setValue(restaurant.phone, forKey: "phone")
let imageData = restaurant.image! as Data
// Resize the image
let originalImage = UIImage(data: imageData)!
let scalingFactor = (originalImage.size.width > 1024) ? 1024 / originalImage.size.width : 1.0
let scaledImage = UIImage(data: imageData, scale: scalingFactor)!
// Write the image to local file for temporary use
let imageFilePath = NSTemporaryDirectory() + restaurant.name!
let imageFileURL = URL(fileURLWithPath: imageFilePath)
try? UIImageJPEGRepresentation(scaledImage, 0.8)?.write(to: imageFileURL)
// Create image asset for upload
let imageAsset = CKAsset(fileURL: imageFileURL)
record.setValue(imageAsset, forKey: "image")
// Get the Public iCloud Database
let publicDatabase = CKContainer.default().publicCloudDatabase
// Save the record to iCloud
publicDatabase.save(record, completionHandler: { (record, error) -> Void in
// Remove temp file
try? FileManager.default.removeItem(at: imageFileURL)
})
}
The parameter is correct and I am passing the correct type
No, both are wrong.
The method is declared as
class func saveRecordToCloud(restaurant:RestaurantMO!)
The parameter label is restaurant rather than rest (stated clearly by the error message)
The parameter type is RestaurantMO rather than RestaurantMO.type (an instance is expected)
The correct syntax is
saveRecordToCloud(restaurant: rest)
Note: As the parameter is non-optional remove the exclamation mark in the method declaration:
func saveRecordToCloud(restaurant:RestaurantMO)

Can not get info out of generated QRCode

I am trying to generate QRCode with multiple strings. It is working, however the generated image QRCode is too small inside the imageView so it is impossible to read it in(At least I think that is why I can't get info out of it).
This is how it looks:
And like this I generate it:
func generateQRWithInfo(){
var aggregateData = [String: NSData]()
if let firstName = firstName?.data(using: String.Encoding.isoLatin1, allowLossyConversion: false) {
aggregateData.updateValue(firstName as NSData, forKey: "firstName")
}
if let lastName = lastName?.data(using: String.Encoding.isoLatin1, allowLossyConversion: false) {
aggregateData.updateValue(lastName as NSData, forKey: "lastName")
}
if let job = job?.data(using: String.Encoding.isoLatin1, allowLossyConversion: false) {
aggregateData.updateValue(job as NSData, forKey: "job")
}
if let organization = organization?.data(using: String.Encoding.isoLatin1, allowLossyConversion: false) {
aggregateData.updateValue(organization as NSData, forKey: "organization")
}
if let mobilePhone = mobilePhone?.data(using: String.Encoding.isoLatin1, allowLossyConversion: false) {
aggregateData.updateValue(mobilePhone as NSData, forKey: "mobilePhone")
}
if let workPhone = workPhone?.data(using: String.Encoding.isoLatin1, allowLossyConversion: false) {
aggregateData.updateValue(workPhone as NSData, forKey: "workPhone")
}
if let email = email?.data(using: String.Encoding.isoLatin1, allowLossyConversion: false) {
aggregateData.updateValue(email as NSData, forKey: "email")
}
let archived = NSKeyedArchiver.archivedData(withRootObject: aggregateData)
let filter = CIFilter(name: "CIQRCodeGenerator")
filter?.setValue(archived, forKey: "inputMessage")
filter?.setValue("Q", forKey: "inputCorrectionLevel")
let qrCodeImage = filter?.outputImage
let context = CIContext(options: nil)
//let cgImage = context.createCGImage(qrCodeImage!, from: (qrCodeImage?.extent)!)
let transform = CGAffineTransform(scaleX: 50,y: 50)
let output = filter?.outputImage?.applying(transform)
let newImage = UIImage(ciImage: output!)
qrImageView.image = newImage
}
I do not know if it is how it should be but I can't get info out of it. What I am doing wrong?
QR Codes holds lots of data based on these parameters.
Data type
Size a.k.a pixels
Error correction level
Data type can be Numeric, Alphanumeric and Binary.
Error correction level can be categorised as Type L,M,Q and H based on loss recovery possible.
so as per your case you want to generate 30*30 alphanumeric so obviously you cant store more then allowed values. So make it bigger or reduce the data. To make a note all the QR code readers are not same.
For more info check this table

Core Data Memory Crash [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am using Core Data for the first time in my project and I feel there is a serious problem in my approach. What I am doing is that I fetch data from server (data includes pngs as well). Save it in core data locally. Then at app launch, I load the entire data in an array. Then this array is used where ever I need it. I think I am following a very bad approach. Can anyone guide me what should be a better approach? Should I only query Core Data when data is needed instead of loading everything in memory at start?
When data is being populated to the array, I can see memory increasing in Xcode and after a certain value, it crashes.
Here is my code for saving data:
func saveDataLocally () {
let moContext = ((UIApplication.shared.delegate) as! AppDelegate).managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: "FoodPlace", in: moContext)
for foodPlaceData in self.downloadedData_ {
let foodPlace = NSManagedObject(entity: entity!, insertInto: moContext) as! FoodPlace
foodPlace.objectId = foodPlaceData.objectId_
foodPlace.name = foodPlaceData.name_
foodPlace.address = foodPlaceData.address_
foodPlace.keywords = foodPlaceData.keywords_
foodPlace.baseFavourites = Int64(foodPlaceData.baseFavourites_)
foodPlace.startingTime = foodPlaceData.startingTime_
foodPlace.endingTime = foodPlaceData.endingTime_
foodPlace.category = foodPlaceData.category_
foodPlace.basePrice = foodPlaceData.basePrice_
foodPlace.dealTitle = foodPlaceData.dealTitle_
foodPlace.versionNumber = Int64(foodPlaceData.versionNumber_)
foodPlace.menuItems = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.menuItems_)
foodPlace.location = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.location_)
foodPlace.deals = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.deals_)
foodPlace.foodPlacePhotos = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.foodPlacePhotos_)
moContext.insert(foodPlace)
}
do {
try moContext.save()
}
catch let error {
print("error saving = \(error.localizedDescription)")
}
}
where menuItems is a Dictionary which contains text as well as png images. Also, deals and foodPlacePhotos only contain png images.
Here is the code for fetching:
func loadDataLocally () {
let moContext = ((UIApplication.shared.delegate) as! AppDelegate).managedObjectContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "FoodPlace")
do {
let results = try moContext.fetch(request)
let savedFoodPlaceData = results as! [FoodPlace]
downloadedData_ = []
for foodPlace in savedFoodPlaceData {
let objectId = foodPlace.objectId
let name = foodPlace.name
let address = foodPlace.address
let keywords = foodPlace.keywords
let baseFavourites = foodPlace.baseFavourites
let startingTime = foodPlace.startingTime
let endingTime = foodPlace.endingTime
let category = foodPlace.category
let menuItems = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.menuItems!) as? [Dictionary<String,AnyObject>]
let location = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.location!) as? Dictionary<String,Double>
let deals = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.deals!) as? [UIImage]
let basePrice = Float(foodPlace.basePrice)
let dealTitle = foodPlace.dealTitle
let versionNumber = foodPlace.versionNumber
let foodPlacePhotos = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.foodPlacePhotos!) as? [UIImage]
let data = FoodPlaceData(objectId: objectId!, name: name!, address: address!, category: category!, keywords: keywords!, baseFavourites: Int(baseFavourites), startingTime: startingTime!, endingTime: endingTime!, menuItems: menuItems!, location: location!, deals: deals!,basePrice: basePrice,dealTitle: dealTitle!,versionNumber: Int(versionNumber),foodPlacePhotos: foodPlacePhotos!)
downloadedData_.insert(data, at: downloadedData_.count)
}
}
catch let error {
print("error fetching = \(error.localizedDescription)")
}
}
and here is the code for deleting data:
func deleteAllLocalData () {
let moContext = ((UIApplication.shared.delegate) as! AppDelegate).managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "FoodPlace")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try moContext.fetch(fetchRequest)
for managedObject in results {
let managedObjectData : NSManagedObject = managedObject as! NSManagedObject
moContext.delete(managedObjectData)
}
try moContext.save()
} catch let error {
print("Delete all data in FoodPlace error : \(error) \((error as NSError).userInfo)")
}
}
Difficult to be prescriptive without a lot more detail about your code. But a few thoughts:
rather than storing the PNG data in CoreData itself, consider storing it in the file system directly, and using CoreData to store only the filename for the PNG.
Alternatively, if you really want the PNGs in CoreData, consider adding a separate entity for the PNG, and adding a one-one relationship from your current entity to the new one.
Either of the above will avoid all the PNG data being loaded into memory when you load the array. You can then load/unload the PNGs (either from the file system or from the related entity) as needed.
In addition, consider using:
NSFetchedResultsController and/or
fetchBatchSize
These will help to avoid all the objects being loaded into memory.

Getting image from Core Data

I'm working on a Cocktailapp and want to save my image data to Core Data.
I watched some tutorials and did the same but it's still not working an I don't know why.
I have an Array with all titles for the images:
let imagesAperitif: [String] = ["americano.jpg", "daiquiri.jpg",
"frozen daiquiri.jpg",
"banana frozen daiquiri.jpg", "bronx.jpg", "kir.jpg",
"hugo.jpg", "Manhattann.jpg", "manhattan dry.jpg", "manhattan medium.jpg", "margarita.jpg",
"martini dry.jpg",...
Thats where I call my method for saving the images to Core Data:
setCocktails(nameInsert, zutaten: zutatenInsert, zubereitung: zubereitungInsert, dekoration: dekorationInsert, stil: stilInsert, bild: UIImage(named: imagesAperitif[index])!)
That's a part from the code of saveCocktails method:
let imageData = NSData(data: UIImageJPEGRepresentation(bild, 1.0)!)
eintrag.setValue(imageData, forKey: "bild")
do {
try managedContext.save()
That's part of the fetching method:
let fetchRequest = NSFetchRequest(entityName: "Cocktail")
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
cocktails = results as! [NSManagedObject]
And here I want to get my image back from NSData:
imagesAperitif.append(UIImage(data: eintrag.valueForKey("bild") as! NSData)!)
But the App crashes with this line and I get a "fatal error: unexpectedly found nil while unwrapping an Optional value" error.
Is anybody able to explain this to me because I don't know what to change. Everything I tried went also wrong.
You may want to check nil for eintrag.valueForKey("bild") before case it to NSData
As
func valueForKey(_ key: String) -> AnyObject?
And it's always save to check nil before you append UIImage(data: eintrag.valueForKey("bild") as! NSData)
(I like using guard so here goes :) )
So what I would do here since it looks like the inserting of the NSData object fails:
// We are simultaniously unwrapping objects and checking if objects are nil.
guard let imgAsNSData: NSData = UIImageJPEGRepresentation(bild, 1.0),
let entity: Cocktail = eintrag as? Cocktail else {
// Stop executing the method here, there is no point in going further. Handle any errors here! Either imgAsNSData is nil, or could not cast to your class. A guard statement handles it's errors here.
return
}
// At this point we know we have an NSData object. Assign it to the entity.
entity.bild = imgAsNSData
do {
// Save our entity.
try managedContext.save()
} catch {
// Handle error
}
Fetching:
let fetchRequest = NSFetchRequest(entityName: "Cocktail")
do {
let results: [NSManagedObject] = try managedContext.executeFetchRequest(fetchRequest)
guard let cocktails = results as? [Cocktail] else {
// Could not cast to an array of Cocktail objects.
return
}
// Do stuff with the cocktails object.
Add to your array:
// 1: Check if entity not is nil
// 2: Check if entity's bild property not is nil.
// 3: Check if we can create an image using the NSData
guard let cocktail: Cocktail = eintrag as? Cocktail,
let imgAsNSData: NSData = cocktail.bild,
let image: UIImage = UIImage(data: imgAsNSData) else {
// Required values are nil. Cannot proceed.
return
}
imagesAperitif.append(image)
Untested code so be careful :)

Resources