Duplicate Download All cached Object Firebase 3x, Swift 3.0 - swift

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

Related

CollectionViewCell loading with nil value after reloadData func

I made a function to fetch data for an empty array that I'm using for a collectionView. I'm pulling the information from two different child nodes. The first being the "users" tree and the second being the "profile_images", using the UID from users to find the corresponding images. The cell populates when the view loads. My issue is that when the cell populates, I'm getting a nil value for one of the values.
I tried to add the array to the collectionViewCell instead of the view controller. I've also been reading the developer notes on prefetching data but it makes it seems like it's used for cells that have yet to be loaded.
var matches = [MatchData]()
// function to retrieve firebase data
private func populateInbox() {
if let uid = Auth.auth().currentUser?.uid {
// Supply Matches for users first
let match = MatchData()
Database.database().reference().child("users").observe(.childAdded) { (snapshot) in
let matichUID = snapshot.key
if matichUID != uid {
Database.database().reference().child("profile_images").child(matichUID).observeSingleEvent(of: .value, with: { (data) in
if let imageDict = data.value as? [String: AnyObject] {
match.matchImage = imageDict["imageOne"] as? String
print(match.matchImage)
}
})
if let dictionary = snapshot.value as? [String: AnyObject] {
print(uid, dictionary)
match.matchName = dictionary["firstName"] as? String
self.matches.append(match)
}
}
DispatchQueue.main.async {
self.matchList.reloadData()
print(self.matches.count)
}
}
}
}
// function to convert image url into UIImage
private func icon(_ imageURL: String, imageView: UIImageView) {
let url = URL(string: imageURL)
var image: UIImage?
var imageData:Data?
if url == nil {
print("Code failed here...")
imageView.image = #imageLiteral(resourceName: "ic_person_outline_white_2x")
} else {
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("error")
DispatchQueue.main.async {
imageView.image = UIImage(imageLiteralResourceName: "ic_person_outline_white_2x")
}
} else {
DispatchQueue.main.async {
imageData = data
image = UIImage(data: imageData!)
imageView.image = image!
}
}
}.resume()
}
}
// Data model
class MatchData: NSObject {
var matchImage: String?
var matchName: String?
}
// additional details
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InboxCell", for: indexPath) as! InboxCell
let matchInfo = matches[indexPath.row]
cell.userLabel.text = matchInfo.matchName
icon(matchInfo.matchImage ?? "", imageView: cell.userImage)
//icon always returns nil value but Userlabel returns name value
return cell
}
The expected result is to have a cell that displays images along with the name of the user the image belongs too. The actual results is the name of the users profile and a nil value for the image.
It looks like you append match to your matchlist before your observeSingleEventOf callback completes. Match updates when the image is received, but has already been added.
if let dictionary = snapshot.value as? [String: AnyObject] {
match.matchName = dictionary["firstName"] as? String
}
if matchUID != uid {
Database.database().reference().child("profile_images").child(matichUID).observeSingleEvent(of: .value, with: { (data) in
if let imageDict = data.value as? [String: AnyObject] {
match.matchImage = imageDict["imageOne"] as? String
}
self.matches.append(match)
DispatchQueue.main.async {
self.matchList.reloadData()
}
})
} else {
self.matches.append(match)
DispatchQueue.main.async {
self.matchList.reloadData()
}
}

Why URL not working correctly when tried to turn image?

I have question about firebase. I have image's URL in my database and I show them in collectionView cells but in this point I have some problem which is some image not loaded correctly their URLs are different but images are same. I tried lots of things but I can't solve it. My URLs starting with 'https' and App Transport Security Setting, Allow Arbitrary Loads = YES. So these are not solved my problem. Here my code which are firebase and adding this URL to imageViews. Please help me! Thanks!
func firebaseCon() {
let ref = Database.database().reference().child("cells")
ref.observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? [String: AnyObject] {
let dataCon = ItemCellImage()
dataCon.itemImageName = dict["itemimagename"] as? String
dataCon.itemTitleLabel = dict["itemimagelabel"] as? String
//print(dataCon.itemImageName, dataCon.itemTitleLabel)
self.itemler.append(dataCon)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
}
And here from data to image:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! itemsCell
let dataGelen = itemler[indexPath.row]
cell.titleLabel.text = dataGelen.itemTitleLabel
if let cellDataImage = dataGelen.itemImageName {
let url = URL(string: cellDataImage)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
cell.itemsimageView.image = UIImage(data: data!)
self.imageDeneme = UIImage(data: data!)!
}
}.resume()
}
return cell
}
When I tried to change image url from firebase some of them working correctly and some of them show just previous image. Is there any way to show exactly correct image in every change?
For this kind of problem due to Reusing concept you can use prepareForReuse() function provide by cells.
override func prepareForReuse() {
yourImageView.image = nil //Or any placeholder image
}
This function is called when OS is about to reuse your cell, thus you can use it as a kind of reset to your cell appearance.

Swift 4 program showing data after downloading images but data is not related

I am making a swift app where i am downloading data from API. which gives a JSON.And from there i am putting image url in an imageArray and movieTiteUrl in movieTitleArray. but when i am showing them to collection view they are showing data but that data is not related. To download images i am using AlamofireImage Below code will help you to understand my problem better.
inside ViewDidLoad
var imageUrlArray = [String]()
var imageArray = [UIImage]()
var movieTitleArray = [String]()
UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "movieCell", for: indexPath) as? MovieCell else { return UICollectionViewCell() }
cell.movieName.image = imageArray[indexPath.row]
cell.movieNameLbl.text = movieTitleArray[indexPath.row]
return cell
}
An extension which download data and download images
func downloadImages(handler: #escaping (_ status: Bool)-> ()){
imageArray = []
movieTitleArray = []
for url in imageUrlArray{
Alamofire.request(url).responseImage(completionHandler: { (response) in
guard let image = response.result.value else { return }
self.imageArray.append(image)
if self.imageArray.count == self.imageUrlArray.count {
handler(true)
}
})
}
}
func retriveData(handler : #escaping (_ status: Bool) -> ()){
print(getPopularMovies(pageNumber: 1))
Alamofire.request(getPopularMovies(pageNumber: 1)).responseJSON { (response) in
guard let json = response.result.value as? Dictionary<String, AnyObject> else { return }
let dataDictArray = json["results"] as! [Dictionary<String, AnyObject>]
for data in dataDictArray {
guard let imageUrl = data["poster_path"] as? String else { return }
guard let name = data["original_title"] as? String else { return }
let updatedImageUrl = getFullImageUrl(imageUrl: imageUrl)
self.imageUrlArray.append(updatedImageUrl)
self.movieTitleArray.append(name)
}
handler(true)
}
}
func updateDataToCollectionView(){
retriveData{(finished) in
if finished{
self.downloadImages(handler: { (finishedDownloadingImage) in
if finishedDownloadingImage{
self.movieCollectionView.reloadData()
}
})
}
}
}
I have found two observation ( problems ).
As you are using Async request to download the images, it is not guaranteed that you will get the response in the requested order. for example, if you request movie1Image,movie2Image....it is not guarntee that first you will receive movie1Image and the movie2Image in the same order. So definitely it is not reliable. use Dictionary to solve this. [String:Data] string -> ImageUrl, Data -> ImageData
Why did you wait till all images get downloaded? any specific scenario or any business requirement. Ideally, for a greater User Experience and for interaction, it is not recommended to wait till all the images get downloaded.

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.

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