Store sequence of images in CoreData - ios

I tried to store images using CoreData. It is fine and I can store a single image. However how can I turn it to multiple images?
Currently, I have set the image field to Binary Data.
I save the image into the object by:
let imageData = UIImage(imageLiteralResourceName: "Dummy1").jpegData(compressionQuality: 1)
item.image = imageData
try? self.viewContext.save()
How can I turn it into an array of imageData that can store in CoreData?
I tried to do this but it fails:
let imageData1 = UIImage(imageLiteralResourceName: "Dummy1").jpegData(compressionQuality: 1)
let imageData2 = UIImage(imageLiteralResourceName: "Dummy2").jpegData(compressionQuality: 1)
item.image = [imageData1, imageData2]
try ? self.viewContext.save()
The compiler said that the attribute 'image' is Data? but not [Data]? type.
I have also tried to use the Type Transformable:
However, there is warning:
warning: Misconfigured Property: Items.imageArray is using a nil or insecure value transformer. Please switch to NSSecureUnarchiveFromDataTransformerName or a custom NSValueTransformer subclass of NSSecureUnarchiveFromDataTransformer
Any idea on the warning and how to resolve it?
On using the Transformable type, actually I can achieve this:
let imageData1 = UIImage(imageLiteralResourceName: "Dummy1").jpegData(compressionQuality: 1)
let imageData2 = UIImage(imageLiteralResourceName: "Dummy2").jpegData(compressionQuality: 1)
item.image = imageData1
item.imageArray = [imageData1!, imageData2!]
However, a few issues here:
It force to add ! to imageData, which indeed, should be optional in my case, I have no way to use ?? properly to give it a dummy imageData if that is found to be nil.
The same problem appear when I tried to display the array:
Image(uiImage: UIImage(data: (self.item.imageArray![0]))!)
.frame(width:300, height:300)
You can see that I have added ! to both imageArray! which can be nil and also the UIImage!
I would like to, instead provide default values for both cases, but I failed to use ?? to provide default value. Any idea?

You're trying to jam a square peg in a round hole. You can't assign an array of data into a data property or a transformable property because an array of data is neither data nor a transformable.
What you can do is to make your property a one-to-many relationship with another entity, let's call it ImageContainer, this other entity would have an imageData property. Now you could add as many images (within an image container) to your object.
Another alternative is to use a transformable value, which as I understand uses NSSecureCoding to transform your values into something CoreData can understand.

You should not save images directly in database, you will hit big performance issues.
Instead save the image name in the database and save the actual image with FileManager in the storage.
Then you can load the image/s from the file manager.

Related

UIImagePickerController - how to save image to core data? (Swift)

I want to save the image that I have picked from the ImagePicker and I want to save it in my CoreData. I create Attribute in my Entity with Binary Data type.
First of all, you need to convert UIImage to Data:
let data = UIImageJPEGRepresentation(image, 1.0)
After that set this data to property in the Entity in the perform method of NSManagedObjectContext:
context.perform {
entity.image = data
// Save context
}
I don't recommend you save UIImage to CoreData it's bad way. Just use https://github.com/Alamofire/AlamofireImage to cache an UIImage correct.

Issue with setting didSet

I am getting image from a url using SDWebImage and assigning it to an array like so...
let imgUrl = arrProduct?[indexPath.section].images[indexPath.row].url
let placeholderImage = UIImage(named: "appLogo.jpg")
cell.prdImgView.sd_setImage(with:imgUrl,
placeholderImage:placeholderImage,
options: []) { (image, error, imageCacheType, imageUrl) in
arrayOfSelectedImages.append(image!)
}
Now I just don't want to add to an array like this. Instead, after adding the image to arrayOfSelectedImages I want to update this array value in didSet and empty the arrayOfSelectedImages array so that every time the array gets a new value, it updates that value in didSet and & arrayOfSelectedImages is emptied. So finally my array in didSet will have all the images I need and I can pass those images on to some other view...How can I achieve this..?
Not entirely sure if this is what you want, but didSet will fire on a property that is an array if you modify the array, not just if you assign the array. Here is an example:
struct A
{
var anArray = [1, 2, 3]
{
didSet
{
print("Hi, there!")
anArray.remove(at: 0)
}
}
}
var a = A()
a.anArray.append(4)
// Prints Hi there!
print(a.anArray)
// prints [2, 3, 4]
The task is quite straight-forward to accomplish. You need a valid criteria to compare appended objects and, what's important, criteria you apply before appending object to an array, not after that. Using didSet to verify appended object and delete it if unsuitable, is bad design.
If your UIImage objects are not encapsulated within any other object or struct to uniquely id these objects and if you don't have an option whether or not particular image should be downloaded at all (which is the best and most proper practice), you can compare two UIImage objects by comparing underlying image data. This could previously be accomplished by obtaining PNG representation of an image and comparing that data, but now there's a good simple method.
Comparing Images
The isEqual(:) method is the only reliable way to
determine whether two images contain the same image data. The image
objects you create may be different from each other, even when you
initialize them with the same cached image data. The only way to
determine their equality is to use the isEqual(:) method, which
compares the actual image data. Listing 1 illustrates the correct and
incorrect ways to compare images.
https://developer.apple.com/documentation/uikit/uiimage
Usage
if !arrayOfSelectedImages.contains(where: { $0.isEqual(image) }) {
arrayOfSelectedImages.append(image)
}

Updating a property in a struct inside an array

In my app I download a load of JSON.
I then store that as an array of structs and use that to populate a UITableView.
One of the properties of the struct is an NSURL for an image. Another property is an optional UIImage.
The struct has a mutating function downloadImage which uses the URL to download the image and store it in its property.
Like this...
struct SearchItem {
// other properties...
let iconURL: NSURL
var icon: UIImage?
mutating func downloadImage() -> Task<UIImage> {
let tsc = TaskCompletionSource<UIImage>()
NSURLSession.sharedSession().downloadTaskWithURL(iconURL) {
(location, response, error) in
if let location = location,
data = NSData(contentsOfURL: location),
image = UIImage(data: data) {
self.icon = image
tsc.setResult(image)
return
}
tsc.setError(NSError(domain: "", code: 1, userInfo: nil))
}.resume()
return tsc.task
}
}
The problem I'm having is this. (and I have been stumped by this in the past).
I have an array [SearchItem] that I use to populate the tableview.
In cellForRow I have the code... if let searchItem = items[indexPath.row]...
It then checks if the image is nil and downloads...
if let image = searchItem.icon {
cell.imageView.image = image
} else {
searchItem.downloadImage().continueOnSuccessWith(Executor.MainThread) {
_ in
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
}
But this never goes through to put the image into the cell. This is because the SearchItem is struct and so pass-by-value. So the search item that I am downloading the image for is not the same SearchItem as the one stored in the array.
How can I ensure that the image that is downloaded is then stored into the SearchItem inside the actual array?
Use classes.
You're getting a copy of searchItem in your cellForRow method. Whatever you do to this, will be done only to that copy. What you actually want is for the changes you make to that copy to be applied to the version in the array.
Therefore you want reference semantics, therefore use classes.
You could dance around re-inserting the updated copy into the original array if you liked, but what does that gain you besides a line of extra code and probably some other problems.
Structs are lightweight data objects that are not passed by reference, but instead copies itself as needed when you a) pass it to a new function, b) try and access it in a block. Arrays in Swift also work slightly differently than their Obj-C counterparts. When you have an Array of class objects the array will be a reference type, and you'll be able to achieve what you're trying to achieve here. But on the other hand if the Array is of Structs the array looses its reference semantics and uses copy-by-value instead.
This difference is really powerful when used appropriately, you can greatly optimise your code, make it run faster, have less errors produced by mutable object references having changes happen in multiple parts of your code, etc. But it's up to you as a developer to see where the gains of these optimisations are useful or where it makes sense to use objects instead.

Get-only property stays in memory

I have an enum with get-only property image. It seems like every time I read it, it keeps this image in memory and deletes it only after memory warning (since I use multiple enum instances and display their images in a sequence) even after I set another image to the UIImageView it was displayed on.
How do I force them to be removed from memory?
Well, the best way to not overload the memory is not to cache UIImages in the first place, which can be done with:
extension UIImage{
static func getUcachedImage(named name: String) -> UIImage?{
let path = NSBundle.mainBundle().pathForResource(name, ofType: nil)!
let image = UIImage(contentsOfFile: path)
return image
}
}
This is how I solved my problem.

What's the best way to assert on a UIImage in a unit test?

Say I'm writing a unit test for a tableView:cellForRowAtIndexPath: delegate method on a view controller. This method could return a couple of different configurations of cells depending on the index path I pass in.
I can easily assert on the cell.textLabel.text property. But how can I assert that the cell.imageView.image property contains the correct image? Neither the image or the imageView have (public API) properties I can use to find out the image name or file name.
The best I've come up with is creating the smallest possible valid .png (using [UIImage imageWithData:] so I don't touch the disk in my unit tests) and asserting the byte array I get from cell.imageView.image is the one I expect. I've created an OCHamcrest matcher to make this a little nicer but it's an unsatisfying and inflexible approach.
Has anyone got a better idea?
If you're using [UIImage imagedNamed:], the images are cached. Excerpt from the UIImage class reference for imagedNamed::
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.
This means you can assert on cell.imageView.image == [UIImage imagedName:#"my_image"] as this is a pointer comparison and since the images are cached multiple calls to imageNamed: with the same name will return the same pointer. Even if memory gets tight, you should still be fine, as the UIImage class reference explains:
In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. This purging behavior affects only the image data stored internally by the UIImage object and not the object itself.
Converting the images to Data and then comparing the Data. Since the image is just a pointer to memory location.
guard let data1 = image1?.pngData(), let data2 = image2.pngData() else {
XCTFail("Data should not be nil")
return
}
XCTAssertEqual(data1, data2)
swift5
You can compare the contents of UIImage directly using the isEqual method on a UIImage which will compare that the two images are like for like.
So in your tests, you can do:
let expectedImage = UIImage(named: "my_picture")
let returnedImage = SomeImageReturnedFromFunction()
XCTAssertEqualObjects(expectedImage, returnedImage) // will return true if SomeImageReturnedFromFunction returns my_picture
Reference: https://developer.apple.com/documentation/uikit/uiimage#overview
You can indeed compare with the "isEqual" method but rather like this :
let expectedImage = UIImage(named: "an_image")
let returnedImage = myFunctionRet() // optional ?
XCTAssert(returnedImage?.isEqual(expectedImage) == true)

Resources