UIImage init with real data returns nil - swift

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.

Related

UIImage keeps loading all time when scroll even store in NSCache - swift

I am new in iOS programming. I am creating a simple app which loads image from a particular link ( firestore ). The images are completely downloaded from the server and visible on each cell of collectionview as usual. But the problem is that when when I scroll up or down then those images keeps loading again. I think it starts downloading again because when I turn off internet connection, those images are not being loaded anymore.
Here is how i set images in each cell
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CollectionCell
let explore = dataAppend[indexPath.item]
//cell.imageDisplay.text = explore.title
if let imageUrl = explore.image {
cell.imageDisplay.loadImageWithData(urlString: imageUrl)
}
//print(explore.image)
return cell
}
Here is how loading images look like loadImageWithData(urlString: imageUrl)
let imageCache = NSCache<NSString, UIImage>()
class CustomImageView : UIImageView {
var imageUrlString: String?
func loadImageWithData (urlString: String) {
imageUrlString = urlString
if let imageFromCache = imageCache.object(forKey: urlString as NSString){
self.image = imageFromCache
}
image = nil
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let err = error {
print(err.localizedDescription)
}
if let data = data {
DispatchQueue.main.async {
let imageToCache = UIImage(data: data)
if self.imageUrlString == urlString {
self.image = imageToCache
}
imageCache.setObject(imageToCache!, forKey: urlString as NSString)
}
}
}).resume()
}
}
var imageCache = NSMutableDictionary()
class CustomImageView: UIImageView {
func loadImageUsingCacheWithUrlString(urlString: String) {
self.image = nil
if let img = imageCache.valueForKey(urlString) as? UIImage{
self.image = img
return
}
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(NSURL(string: urlString)!, completionHandler: { (data, response, error) -> Void in
if(error == nil){
if let img = UIImage(data: data!) {
imageCache.setValue(img, forKey: urlString) // Image saved for cache
DispatchQuee.main.asyn{
self.image = img
}
}
})
task.resume()
}
}
}
You can instead use the Kingfisher library , handles the image caching itself you don't need to worry about it. For implementing see :
https://github.com/onevcat/Kingfisher
with just one line of code you can set the image
imgView.kf.setImage(with: ImageResource(downloadURL: URL(string: imgUrl)!))

Swift SDWebImage in a closure block

I am attempting to use SDWebImage in a closure block after fetching userInfo from Firebase. However, doing so will result in the images and cell labels to blink as I scroll the tableView when the cells are attempting to redraw themselves.
let expiredConversationsCell = tableView.dequeueReusableCell(withIdentifier: "expiredConversationsCell", for: indexPath) as! ExpiredConversationsTableViewCell
let conversation = allConversations[0][indexPath.row]
guard let recipientID = conversation.recipientID else {return UITableViewCell()}
FirebaseClient.shared.getUserInfo(recipientID, { (results, error) in
if let error = error {
print(error.localizedDescription)
} else if let results = results {
let username = results["username"] as! String
let profileImageUrl = results["profileImageUrl"] as! String
DispatchQueue.main.async {
expiredConversationsCell.profileImageView.sd_setImage(with: URL(string: profileImageUrl), completed: nil)
expiredConversationsCell.recipientNameLabel.text = username
}
}
})
return expiredConversationsCell
Is there a way to implement SDWebImages under such circumstances? Any advice would be great. Thanks.

Duplicate Download All cached Object Firebase 3x, Swift 3.0

I use Firebase to download all Image to Gallery. After downloaded, It's automatically cached and for being used later, but when I upload new image, Firebase automatically download the whole database again + cached Objects are still there that leads my app to crash. This is how i get downloadUrl from database.
DataService.instance.UsersRef.child(userUID).child("NewImage").observe(.value, with: { (snap) in
if let snap = snap.children.allObjects as? [FIRDataSnapshot] {
for snap in snap {
if let postDict = snap.value as? Dictionary<String, Any> {
let key = snap.key
print(key)
print(postDict)
let originImage = Gallery(postKey: key, postData: postDict )
self.image.append(originImage)
}
}
}
DispatchQueue.main.async {
self.collection.reloadData()
}
})
This is located in ViewDidLoad, I try replace .value with childadded but It couldn't get any new database. I want to load all image in the gallery when user first use, but later when new comes, it should load and get just the new one. This is my download code.
func configureCell(gallery: Gallery, img: UIImage? = nil) {
self.image = gallery
if img != nil {
self.OriginImage.image = img
} else {
// UrlImage.append(image.imageUrl)
let ref = DataService.instance.OriginImageStorageRef.storage.reference(forURL: image.imageUrl)
ref.data(withMaxSize: 2 * 1024 * 1024, completion: { (data, err) in
if err != nil {
print("Unable to download from Storage")
} else {
if let imgData = data {
if let img = UIImage(data: imgData) {
cache.setObject(img, forKey: self.image.imageUrl as NSString)
print(cache.object(forKey: self.image.imageUrl as NSString))
self.OriginImage.image = img
}
}
}
})
}
}
And this is in mycollectionView
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let image = self.image[indexPath.row]
let img = cache.object(forKey: image.imageUrl as NSString)
print(img)
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProfileCell", for: indexPath) as? ProfileCell {
if let img = cache.object(forKey: image.imageUrl as NSString) {
cell.configureCell(gallery: image, img: img)
} else {
cell.configureCell(gallery: image)
}
//self.image.removeAll()
return cell
} else {
return UICollectionViewCell()
}
}
When I use ChildAdded, it doesn't return Dictionary as .value, this is what I get when I use .childAdded
Optional(1)
Optional(2016-09-23T05:32:11.412Z)
Optional(https://firebasestorage.googleapis.com/v0/b/capabdsjsad- a837b.appspot.com/o/x4BL06rXbrOBXnZXLE57ea1WUsU2%2FOrigin%20Image%20Cap%2F59C4F818-946B-414C-9E87-CD3B8C3F47C8?alt=media&token=4d1151e3-2362-4fd6-a9d1-9891bb913887)
It works well without new data in Firebase, even new data comes, collectionView will show all cached object and download the whole data from database again, not the newest one. I guess it should be because of I work with Firebase in the wrong way, not because of the cache. I work with it in more 2 days and really can't find the way to solve it properly. It re-download everything in the database again, which causes the problems. If anyone have any idea, I am very appreciated. Thank you very much

Swift:fatal error: unexpectedly found nil while unwrapping an Optional value In dispatch_async dispatch_get_global_queue

I have table view in which i am showing 3 cell,and depends upon the collection view cell which is working as like paging. So When i want to get the image on table view cell from dispatch_async(dispatch_get_global_queue...) some time i got the
fatal error: unexpectedly found nil while unwrapping an Optional value
My Code is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.videoTableView .dequeueReusableCellWithIdentifier("customTableViewCell") as! CustomTableViewCell
let videoDataObj:VideoData = subVideoArray .objectAtIndex(indexPath.row) as! VideoData
cell.titleLabel.text = videoDataObj.videoTitle as String
cell.descLabel.text = videoDataObj.videoDesc as String
cell.playButton.tag = indexPath.row + (pageIndex * noOfElement)
let imageUrl = NSURL(string: videoDataObj.videoImage as String)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){
let data = NSData(contentsOfURL: imageUrl!)
dispatch_async(dispatch_get_main_queue(), {
cell.videoImageView?.contentMode = .ScaleToFill
cell.videoImageView?.image = UIImage(data: data!)
cell.setNeedsLayout()
cell.layoutIfNeeded()
})
}
return cell
}
Screen Shot Of View Is:enter image description here
Thanks in advance
Yes !!! I review my code and got solution. I was doing the silly mistake.
Try it..
Updated Code.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){
if let data = NSData(contentsOfURL: imageUrl!){
dispatch_async(dispatch_get_main_queue(), {
cell.videoImageView?.contentMode = .ScaleToFill
cell.videoImageView?.image = UIImage(data: data)
cell.setNeedsLayout()
cell.layoutIfNeeded()
})
}

Grabbing object from user in parse

In a collection view I have an array of events and each cell has a background image view of the event image which is saved as a PFFile. That works fine and good until I added this code. The user has a property "profilePicture" which I want to display in the cell as well. Here is my code which is inside of the block which gets the event image.
let eventCreator : PFUser = event?.objectForKey("user") as! PFUser
let creatorImage : PFFile = eventCreator.objectForKey("profilePicture") as! PFFile
creatorImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
cell.creatorImageView.image = UIImage(data: data!)
})
Here is the full method which gets the event and all it's properties (like which I said, worked perfectly fine before I added the above code. Now it throws an "fatal error: unexpectedly found nil while unwrapping an Optional value" error. Any help?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//sets up cell
let cell : EventCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! EventCell
//adds attend action
cell.attendButton.addTarget(self, action: "buttonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
//queries parse for events
let event = events?[indexPath.row]
event?.eventImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
if let data = data, image = UIImage(data: data) {
if cell.isFlipped == false {
cell.eventBackgroundImage.image = image
cell.eventTitleLabel.text = event?.eventTitle
//gets profile picture of events creator
let eventCreator : PFUser = event?.objectForKey("user") as! PFUser
let creatorImage : PFFile = eventCreator.objectForKey("profilePicture") as! PFFile
creatorImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
cell.creatorImageView.image = UIImage(data: data!)
})
//sets correct category for cell image
if event?.category == "" {
cell.categoryImageView.image = nil
}
if event?.category == "The Arts" {
cell.categoryImageView.image = UIImage(named: "University")
}
if event?.category == "The Outdoors" {
cell.categoryImageView.image = UIImage(named: "Landscape")
}
//TODO finish categories
}
else if cell.isFlipped == true {
cell.eventDescriptionLabel.text = event?.eventDescription
}
}
})
Forcefully casting a nil-valued optional variable to a non-optional one will lead to a runtime error. Why are you using as! operator ? You should probably use the as? operator instead and check for any nil values to make sure the cast was successful before doing anything.
[Edit]
Try something like this:
if let eventCreator = event?.objectForKey("user") as? PFUser {
if let creatorImage = eventCreator.objectForKey("profilePicture") as? PFFile {
creatorImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
cell.creatorImageView.image = UIImage(data: data!)
})
}
}