Core Data Image with Custom TableViewCell in Swift - 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.

Related

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)
}
}
}

UITableViewCell shows the wrong image while images load

I have UITableView that lists social media posts with images in them.
Once all the post details have loaded and the images cached it looks great but while it loads it often shows the wrong image with the wrong post.
I have been struggling and coming back to this issue for months. I don't think it is a loading issue it almost looks like iOS dumps the image an any old cell until it finds the right one but honestly I'm out of ideas.
Here is my image extension that also takes care of the caching:
let imageCache = NSCache<NSString, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(_ urlString: String) {
self.image = UIImage(named: "loading")
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
self.image = cachedImage
return
}
//No cache, so create new one and set image
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let error = error {
print(error)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
self.image = downloadedImage
}
})
}).resume()
}
}
And this is a shortened version of my UITableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let postImageIndex = postArray [indexPath.row]
let postImageURL = postImageIndex.postImageURL
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedItem", for: indexPath) as! FeedItem
cell.delegate = self
cell.postHeroImage.loadImageUsingCacheWithUrlString(postImageURL)
cell.postTitle.text = postArray [indexPath.row].postTitle
cell.postDescription.text = postArray [indexPath.row].postBody
return cell
}
FeedItem Class includes prepareForReuse() and looks like this:
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
self.postHeroImage.image = UIImage(named: "loading")
}
EDIT: Here is my method for retrieving data from Firebase:
func retrievePosts () {
let postDB = Database.database().reference().child("MyPosts")
postDB.observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary <String,AnyObject>
let postID = snapshotValue ["ID"]!
let postTitle = snapshotValue ["Title"]!
let postBody = snapshotValue ["Description"]!
let postImageURL = snapshotValue ["TitleImage"]!
let post = Post()
post.postTitle = postTitle as! String
post.postBody = postBody as! String
post.postImageURL = postImageURL as! String
self.configureTableView()
}
}
UITableView only uses a handful of cells (~ the max number of visible cells on screen) when displaying a collection of items, so you'll have more items than cells. This works because of the table view reusing mechanism, which means that the same UITableViewCell instance will be used for displaying different items. The reason why you are having problems with the images is because you aren't handling the cell reusing properly.
In the cellForRowAt function you call:
cell.postHeroImage.loadImageUsingCacheWithUrlString(postImageURL)
While you scroll the table view, in different invocations of cellForRowAt this function will be called for the same cell, but (most probably) displaying the content of different items (because of the cell reusing).
Let's X be the cell you are reusing, then these are roughly the functions that will be called:
1. X.prepareForReuse()
// inside cellForRowAt
2. X.postHeroImage.loadImageUsingCacheWithUrlString(imageA)
// at this point the cell is configured for displaying the content for imageA
// and later you reuse it for displaying the content of imageB
3. X.prepareForReuse()
// inside cellForRowAt
4. X.postHeroImage.loadImageUsingCacheWithUrlString(imageB)
When the images are cached, then you will always have 1, 2, 3 and 4 in that order, that's why you don't see any issues in that case. However, the code that downloads an image and set it to the image view runs in a separate thread, so that order isn't guaranteed anymore. Instead of only the four steps above, you will have something like:
1. X.prepareForReuse()
// inside cellForRowAt
2. X.postHeroImage.loadImageUsingCacheWithUrlString(imageA)
// after download finishes
2.1 X.imageView.image = downloadedImage
// at this point the cell is configured for displaying the content for imageA
// and later you reuse it for displaying the content of imageB
3. X.prepareForReuse()
// inside cellForRowAt
4. X.postHeroImage.loadImageUsingCacheWithUrlString(imageB)
4.1 X.imageView.image = downloadedImage
In this case, because of concurrency, you could end up with the following cases:
1, 2, 2.1, 3, 4, 4.1: Everything is displayed properly (this will happen if you scroll slowly)
1, 2, 3, 2.1, 4, 4.1: In this case the first image finishes downloading after the call to reuse the cell finishes, so the old image will be displayed (wrongly) for a short period of time while the new one is downloaded, and then replaced.
1, 2, 3, 4, 2.1, 4.1: Similar to the case above.
1, 2, 3, 4, 4.1, 2.1: In this case the old image finishes downloading after the new one (there is no guaranty the downloads finish in the same order they started) so you will end up with the wrong image. This is the worst case.
For fixing this problem, let's turn our attention to the problematic piece of code inside the loadImageUsingCacheWithUrlString function:
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
// this is the line corresponding to 2.1 and 4.1 above
self.image = downloadedImage
}
})
}).resume()
As you can see, you are setting self.image = downloadedImage even when you aren't displayed the content associated to that image anymore, so what you need is some way to check if that's still the case. Since you define loadImageUsingCacheWithUrlString in an extension for UIImageView, then you don't have much context there to know whether you should display the image or not. Instead of that, I propose to move that function to an extension of UIImage that will return that image in a completion handler, and then call that function from inside your cell. It would look like:
extension UIImage {
static func loadImageUsingCacheWithUrlString(_ urlString: String, completion: #escaping (UIImage) -> Void) {
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
completion(cachedImage)
}
//No cache, so create new one and set image
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let error = error {
print(error)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
completion(downloadedImage)
}
})
}).resume()
}
}
class FeedItem: UITableViewCell {
// some other definitions here...
var postImageURL: String? {
didSet {
if let url = postImageURL {
self.image = UIImage(named: "loading")
UIImage.loadImageUsingCacheWithUrlString(url) { image in
// set the image only when we are still displaying the content for the image we finished downloading
if url == postImageURL {
self.imageView.image = image
}
}
}
else {
self.imageView.image = nil
}
}
}
}
// inside cellForRowAt
cell.postImageURL = postImageURL
Another way to deal with this problem will be by using tableView(_:willDisplay:forRowAt:) for loading downloaded images from the cache and tableView(_:didEndDisplaying:forRowAt:) for removing the image from the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedItem", for: indexPath) as! FeedItem
cell.delegate = self
cell.postTitle.text = postArray [indexPath.row].postTitle
cell.postDescription.text = postArray [indexPath.row].postBody
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let feedCell = cell as! FeedItem
if downloadImages.count > 0 {
cell.postHeroImage.image = downloadImages[indexPath.row]
}
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let feedCell = cell as! FeedItem
cell.postHeroImage.image = nil
}
If you are using CocoaPods, I would strongly recommend using Kingfisher for dealing with image downloads for your project
You need to download and cached the image based on the image URL itself and use it when load the table with that url.
Let's say you have an array of image URLs load the number of rows with this array and download image should be mapped to indexPath and cached. Later you can use it based on the indexPath with array.
The issue which you are facing is not sync row with mapped data in downloaded image. As TableViewCell deque and reuse the cell.
That is because tableView reuses it cells. So one cell could be responsible for multiple images with different urls.
So there is a simple solution for this:
Instead of passing the reference to the reusable cell, you should pass the IndexPath. It's value type and would not reuse.
Then when you have got your image from the async task, you can ask the TableView for the actual cell with .cellForRow(at: indexPath) function.
So, get rid of this line:
cell.postHeroImage.loadImageUsingCacheWithUrlString(postImageURL)
and replace it with a function that takes the actual indexPath and maybe a reference to the tableView.
Watch this WWDC 2018 session for more information. It's about UICollectionView but same as UITableView.
Also you can get the indexPath and the tableView from the cell itself like this answer but make sure you done it BEFORE calling the async function.
you are using in your cellForRowAt function with a reusable cells, although the cell is ever load and unload information, we both know that when a picture is downloading, the downloading is not quick, you need download your images in any function except cellForRowAt. for example
if you have an array of urls
let arrayImages = ["url1", "url2", "url3"]
let downloadImages = [UIImage]()
var dispatchGroup = DispatchGroup()
extension for UIImage
import Foundation
import UIKit
extension UIImage {
func downloaded(from url: URL, completion: ((UIImage,String) -> Void)?) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.global().async() {
completion?(image,url.absoluteString)
}
}.resume()
}
func downloaded(from link: String, completion: ((UIImage,String) -> Void)?) {
guard let url = URL(string: link) else { return }
downloaded(from: url, completion: completion)
}
}
code for your view
override func viewWillAppear()
{
super.viewWillAppear(true)
for url in arrayImages
{
dispatchGroup.enter()
let imageDownloaded = UIImage()
imageDownloaded.downloaded(from: url) { (image, urlImage2) in
DispatchQueue.main.async {
self.downloadImages.append(image)
self.tableView.reloadData()
self.dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedItem", for: indexPath) as! FeedItem
cell.delegate = self
if downloadImages.count > 0 {
cell.postHeroImage.image = downloadImages[indexPath.row]
}
cell.postTitle.text = postArray [indexPath.row].postTitle
cell.postDescription.text = postArray [indexPath.row].postBody
return cell
}
if you have any doubts, please tell me. I will hope that this can help you

Store image in Core Data in 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.

How do you load a image via array index into UIImage from coreData

I have searched all over the place and am unable to figure out how would you load one image to a UIIMage using array index from coredata.
I have tried this:
imgDisplay.image = NSData["Image"][index]
but obviously I am getting errors as the above syntax is not correct.
You first need to creat an array of type NSManagedObject. You will fetch the data and append the fetched data to it.
var results = [NSManagedObject]()
Then, add the following method to perform the fetch request, call it from viewDidAppear() or wherever you want.
func loadData() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "MY_ENTITY_NAME")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "created", ascending: true)]
do {
let results = try context.fetch(fetchRequest)
results = results as! [NSManagedObject]
} catch let err as NSError {
print("Error in fetching reuqest error: \(err)")
}
tableView.reloadData() // if you are adding the fetched data to a table view.
}
When you want to add a specific UIImage at indexPath to the UIImageView, call this:
if (results[3] as! MY_ENTITY_NAME).image != nil {
my_UIImageView.image = UIImage(data: (results[3] as! Task).image! as Data)
}
The .image here is the attribute name. And the 3 is just a random indexPath, you could use indexPath.row

UIImage init with real data returns nil

Good day! It makes me mad, 'cos I do not understand what's going on.
I've got 2 TableViewControllers with some similar logics.
I work with 1 Data Model. This model contains user info and each entity always has a photo. To init it, I use the code below :
var partnerPhoto: UIImage? = nil
if let imageData = partners[indexPath.item].userPhoto {
partnerPhoto = UIImage(data: imageData as! Data)
}
In debug partners[indexPath.item].userPhoto has real data and even imageData shows 4213 bytes.But, app crashes with typical error:
fatal error: unexpectedly found nil while unwrapping an Optional value
EDIT:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if dialogs[indexPath.item].fromID == profileID {
let cell = tableView.dequeueReusableCell(withIdentifier: "dialogMeCell", for: indexPath) as! CellForDialogMe
var partnerPhoto: UIImage? = nil
if let imageData = partners[indexPath.item].userPhoto {
partnerPhoto = UIImage(data: imageData as! Data)
}
cell.fillWithContent(partnerPhoto: partnerPhoto!, selfPhoto: profilePhoto!)
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "dialogHimCell", for: indexPath) as! CellForDialogHim
var partnerPhoto: UIImage? = nil
if let imageData = partners[indexPath.item].userPhoto {
partnerPhoto = UIImage(data: imageData as! Data)
}
cell.fillWithContent(partnerPhoto: partnerPhoto!)
return cell
}
SOLUTION:
In fact I have not found concrete solution for this problem, I have just moved some logic from model to view controller. In model I did load from URL to data. Now I do it right in ViewController and it works perfect, but it is odd, very odd for me, 'cos I just did cmd-x cmd-v.
Cast your imageData before the scope:
if let imageData = partners[indexPath.row].userPhoto as? Data {
partnerPhoto = UIImage(data: imageData)
} else {
partnerPhoto = UIImage() //just to prevent the crash
debugPrint("imageData is incorrect")
//You can set here some kind of placeholder image instead of broken imageData here like UIImage(named: "placeholder.png")
}
cell.fillWithContent(partnerPhoto: partnerPhoto)
return cell
Also, it would be helpful if you'll provide more details - how and when profilePhoto is init-ed and
cell.fillWithContent(partnerPhoto: partnerPhoto!, selfPhoto: profilePhoto!) code.
Also, you can set breakpoint on your cell.fillWithContentand check if partnerPhoto and/or profilePhoto is nil before functions is called.
Try this:
var partnerPhoto: UIImage?
guard let imageData = partners[indexPath.item].userPhoto else {return}
guard let image = UIImage(data: imageData) else {return}
partnerPhoto = image
In my case return nil because I try to download image from FirebaseStorage by Alamofire and than save to Core Data. Into Core Data save correct and my photoData is not nil, but UIImage(data: photoData as Data) return nil.
I resolve this problem by retrieving image native method FirebaseStorage:
func retrieveUserPhotoFromStorage(imageUID: String?, completion: #escaping (Result<Data, DomainError>) -> Void) {
guard let imageID = imageUID else { return }
let storageRef = storage.reference(withPath: DatabaseHierarhyKeys.users.rawValue).child(imageID)
storageRef.getData(maxSize: Constant.maxSize) { data, error in
guard let error = error else {
if let data = data {
completion(.success(data))
return
}
completion(.failure(.domainError(value: "errorRetrieveImage data nil")))
return
}
completion(.failure(.domainError(value: "errorRetrieveImage \(error.localizedDescription)")))
}
}
Than save it to Core Data and UIImage(data: photoData as Data) already is not nil.