Swift SDWebImage in a closure block - swift

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.

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

Images in table view reload automatically

I have a TabBarController with some viewControllers. In one, I have a UITableview with some images but when I insert a new image and go back in the feed section, I see the new image but when I scroll through the various images, the images reload automatically; they do not remain fixed but are recharged when I run. Solutions?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
cell.postID = self.posts[indexPath.row].postID
cell.userImage.layer.cornerRadius = 25.0
cell.userImage.clipsToBounds = true
cell.author.text = self.posts[indexPath.row].nameuser
cell.userImage.downloadImage(from: self.posts[indexPath.row].userimage)
cell.postImage.downloadImage(from: self.posts[indexPath.row].pathToImage)
cell.caption.text = self.posts[indexPath.row].caption
return cell
}
func loadPosts() {
followpeople()
Database.database().reference().child("posts").observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
let captionText = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let photoimage = dict["profileImageUrl"] as! String
let author = dict["Author"] as! String
let postid = dict["postID"] as! String
let uda = dict["uid"] as! String
let like = dict["likes"] as! Int
let date = dict["date"] as! String
let posst = Post()
posst.nameuser = author
posst.likes = like
posst.caption = captionText
posst.postID = postid
posst.pathToImage = photoUrlString
posst.userimage = photoimage
posst.userID = uda
self.posts.append(posst)
self.tableView.reloadData()
}
}
}
func downloadImage(from imgURL: String!) {
let url = URLRequest(url: URL(string: imgURL)!)
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if (error != nil) {
print(error!)
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data!)
}
}
task.resume()
}
The problem because of these 2 lines
cell.userImage.downloadImage(from: self.posts[indexPath.row].userimage)
cell.postImage.downloadImage(from: self.posts[indexPath.row].pathToImage)
they will fetch the images again even if they just been downloaded when you scroll consider using SDWebImage instead to cache the image after first download
1- install SDWebImage by adding a pod for it
2- replace the 2 lines with
cell.userImage.sd_setImage(with: URL(string: self.posts[indexPath.row].userimage), placeholderImage: UIImage(named: "placeholder.png"))
cell.postImage.sd_setImage(with: URL(string: self.posts[indexPath.row].pathToImage), placeholderImage: UIImage(named: "placeholder.png"))

Swift : Why did my IOS application slow down

I use the WebServices(SOAP) to reach the data. I take the image urls from database , download them and show in TableView. here is my codes.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gorevlerCell",for: indexPath) as! GorevlerCell
cell.contentView.backgroundColor = UIColor(white: 0.95, alpha: 1)
// Fil the gorevler Cell
cell.txtGorevAdi.text = (gorevler.object(at: indexPath.row) as AnyObject).value(forKey: "Gorev_Adi") as? String
cell.txtGoreviAlan.text = (gorevler.object(at: indexPath.row) as AnyObject).value(forKey: "Gorevi_Alan") as? String
cell.txtGoreviVeren.text = (gorevler.object(at: indexPath.row) as AnyObject).value(forKey: "Gorevi_Veren") as? String
cell.txtTarih.text = (gorevler.object(at: indexPath.row) as AnyObject).value(forKey: "Tarih") as? String
cell.txtOncelikDurumu.text = (gorevler.object(at: indexPath.row) as AnyObject).value(forKey: "Oncelik_Durumu") as? String
// The Image File
let imgUrl2 = (gorevler.object(at: indexPath.row) as AnyObject).value(forKey: "Profil_Url") as? String
let trimmedImgUrl = imgUrl2?.trimmingCharacters(in: .whitespaces)
let url = NSURL( string : "http://"+trimmedImgUrl! )
let data = NSData(contentsOf: url! as URL)
let img = UIImage(data: data! as Data)
cell.profileImage.image = img
cell.profileImage.layer.cornerRadius = 35
cell.profileImage.layer.borderWidth = 2.0
cell.profileImage.layer.borderColor = UIColor.lightgray.cgColor
cell.profileImage.clipsToBounds = true
return cell}
So, When i added the image in the TableView and scrolled it , the application began slow down. My question is that why the application is slow down and how to faster it.
Note : i can try with low resolution image but still slow.
Its slow because you are download image Synchronously.
You need to download it Asynchronously. Consider below example:
Add below extension in your app.
extension UIImageView {
func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
contentMode = mode
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.main.async() {
self.image = image
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloadedFrom(url: url, contentMode: mode)
}
}
And you can use it in your case:
cell.profileImage.downloadedFrom(link: "http://"+trimmedImgUrl!)
Replace above line with:
let url = NSURL( string : "http://"+trimmedImgUrl! )
let data = NSData(contentsOf: url! as URL)
let img = UIImage(data: data! as Data)
cell.profileImage.image = img
For more info check original post.

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