I am using Haneke to download and show images asynchronously in CollectionView.
I want to show a smaller thumbnail type of image while the larger image is being downloaded for each cell.
Here's the code:
let imageUrl = NSURL(string: "imgUrlString")
let miniImageUrl = NSURL(string: "miniImgUrlString")
func onSuccess(image: UIImage?) {
cell!.imageView.hnk_setImageFromURL(imageUrl!)
}
cell!.imageView.hnk_setImageFromURL(miniImageUrl!, placeholder: nil, format: nil, failure: onFailure, success: onSuccess)
Also there is preloader image set on ImageView of Cell just after dequeing it.
cell?.imageView.image = UIImage(named: "preloader")
The Problem
The onSuccess handler is called, but the mini image is never visible. The image changes from "preloader" to "image", while I want the mini image to load soon and then change to original image.
What am I missing?
Related
Question
I am trying to download a large image from the web with the code below. I want to show how much download has been done using the progress bar. Currently, it is only possible to know whether the download was successful or not.
func downloadBigImg() -> UIImage? {
let imgURL = URL(string: "https://picsum.photos/1024")!
guard let data = try? Data(contentsOf: imgURL) else { return nil }
let image = UIImage(data: data)
return image
}
What I did
Using asynchronous operation, the progress bar was raised during the download of the image. But that Progress Bar is a fake. It's not really a progress. It just adds a very small number which isn't related to the actual progress. When the image download is completed, it will be changed to 100%.
I am stumped and hoping someone can give me some break crumbs to research. I searched but couldn't find the same question, so I apologize if it has been asked and answered before.
I have a simple app where a user can upload photos to a post on Parse-Server using:
#IBAction func SelectImage(_ sender: Any) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary
imagePicker.allowsEditing = false
self.present(imagePicker, animated: true, completion: nil)
}
If it is the first image I set a UIImageView to the selected image and add it to an array of PFFiles. If its not the first I just add it to the array. I then set a collection view to show all the images in the PFFile array.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
if !image1Set {
blipImage.image = image
imagePicked = true
image1Set = true
} else if !image2Set {
image2Set = true
} else if !image3Set {
image3Set = true
}
// Build an array of the files for the blip
if let imageData = UIImagePNGRepresentation(image) {
if let blipFile = PFFile(name: "image.png", data: imageData) {
newBlipFiles.append(blipFile)
blipFiles.append(blipFile)
}
}
blipFileCollectionView.reloadData()
}
dismiss(animated: true, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:BlipMainCell = collectionView.dequeueReusableCell(withReuseIdentifier: "fileCell", for: indexPath) as! BlipMainCell
blipFiles[indexPath.row].getDataInBackground { (data, error) in
if let imageData = data {
if let imageToDisplay = UIImage(data: imageData) {
cell.image.image = imageToDisplay
}
}
}
return cell
}
When I run this on the simulator everything looks fine. When I test this on the phone though the photo in the collection view flips it on its side. Once the photo is read into the server and the previously uploaded image is seeded into the same view controller the image is flipped on the collection view AND in the UIImageView that originally showed it correctly. When the image is pulled from the server and used as the icon in a table view it is also flipped.
My hypothesis is that something happens when the image is converted into data using UIImagePNGRepresentation(image) such that the orientation is stripped away. This would explain why all the times its read as a PFFile it is flipped but not the original setting, yet when it seeds that same UIImageView that was fine it gets flipped.
Can anyone help me understand this? In my heart of hearts I am hoping this is as simple as adding somekind of argument to the data encoding of the PFFile such that the UIImagePNGRepresentation(image) retains the correct orientation. Very frustrating though because there is no way I can have an app where the orientation doesn't math what the user expects because that is what they see in their camera roll.
Thanks, dan
This is because when you convert your UIImage object to PNG data representation it discards the image exif metadata including the image orientation information. The solution is to use JPEG data representation which preserves the image orientation and also has the advantage of reducing the image data size. If you really need to upload a PNG you can redraw your image in a new image context and then convert the new image to data.
The problem is you had the correct data but you threw it away when you fetched the UIImagePickerControllerOriginalImage. That is the wrong key if you intend to upload the image data. You wanted the UIImagePickerControllerImageURL. That is the data, on disk. Upload it without messing with it first!
I'm editing a record which has some text information and images. As I get images URL so I'm using SDWebImage to download images which are then displayed in a collectionView and it's vertical scrollable. So when the view is loaded I'm doing this in viewDidLoad:
for (index, _) in mediaFiles.enumerated()
{
let img = UIImageView()
if let url = NSURL(string: "\(baseURLGeneral)\(mediaFiles[index].imageFile ?? "")")
{
print(url)
img.sd_setShowActivityIndicatorView(true)
img.sd_setIndicatorStyle(.gray)
img.sd_setImage(with: url as URL, placeholderImage: nil, options: .refreshCached, completed: { (loadedImage, error, cache, url) in
self.imagesArray.append(loadedImage!)
DispatchQueue.main.async
{
self.photoCollection.reloadData()
}
})
}
}
This code as per my understanding is downloading the image from web and when the image is loaded it add the image in an imageArray which I've declared as var imagesArray: [UIImage] = [] and then reload the collection view.
As this is the edit screen so user can also add more images in imagesArray which will show with the downloaded images in the same array and can also remove images.
As per collectionView delegate and dataSource is concerned so I'm returning return imagesArray.count in numberOfItemsInSection.
In cellForItemAt after making a cell variable I've cell.imageAddedByUser.image = imagesArray[indexPath.row].
THE ISSUE which I'm having is that after downloading images in viewDidLoad() collectionView is not getting refreshed. But If I pop and then push view controller it shows the images.
Try to call it in viewDidAppear()
DispatchQueue.main.async { self.photoCollection.reloadData() }
Try to dispatch it in main Queue
DispatchQueue.main.async
{
self.photoCollection.reloadData()
}
I have a Table View that presents comments. Each comment includes a label and a UIImage that presents the authors profile image. There is no pattern to the way images are appearing when the table is loaded. Sometimes they load fine. Other times, the placeholder image appears and the image never loads at all. If I scroll, the behavior varies. I'm using AlamofireImage.
After some research I came across this post where the recommendation is to call
self.tableView.beginUpdates()
self.tableView.endUpdates()
in AlamofireImage's completion handler. This had no effect at all.
Here is my original code:
func tableView(_ tableView: UITableView, cellForRowAt.....{
...
let myURL = Comment.profileImageURL
if Comment.profileImageURL != nil {
profileImage.af_setImage(withURL: myURL!, placeholderImage: #imageLiteral(resourceName: "Profile Image"))
} else {
profileImage.image = UIImage(named: "Profile Image")
}
And here is the update based on this question's recommendation (last answer on page):
let myURL = comment.profileImageURL
if comment.profileImageURL != nil {
cell.profileImage.af_setImage(
withURL: myURL!,
placeholderImage: #imageLiteral(resourceName: "Profile Image"),
filter: nil,
imageTransition: UIImageView.ImageTransition.crossDissolve(0.5),
runImageTransitionIfCached: false) {
// Completion closure
response in
// Check if the image isn't already cached
if response.response != nil {
// Force the cell update
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
} else {
cell.profileImage.image = UIImage(named: "Profile Image")
}
====EDIT====
Adding additional information in the hopes it will help:
I have tried setting the profileImage.image = nil as suggested below. I have also cancel the download in prepareForReuse() as suggested below to no avail.
Before calling the image, I am doing some configuration on the UIImageView
profileImage.layer.cornerRadius = profileImage.bounds.size.width / 2
profileImage.clipsToBounds = true
profileImage.layer.borderWidth = 2
profileImage.layer.borderColor = UIColor(red:222/255.0, green:225/255.0, blue:227/255.0, alpha: 1.0).cgColor
The following is the imageView setting in XCode:
Finally the issue itself. About 50% of the time I move back and forth between views or scroll, placeholder images appear in place of the actual image. In fact. The place holders often appear on first load.
===EDIT#2===
I'm trying to follow Ashley Mills second suggestion. I check the Image URL in the completion handler of AlamofireImage and the results are odd. I just loaded the table view. All images loaded fine as shown below:
I print out the the image URL when the Completion Handler fires and it looks fine:
I'm in completion
Optional(http://www.smarttapp.com/Portals/0/Users/018/18/18/sballmer.jpg)
I'm in completion
Optional(http://www.smarttapp.com/Portals/0/Users/018/18/18/sballmer.jpg)
I'm in completion
Optional(http://www.smarttapp.com/Portals/0/Users/011/11/11/Elon%20Musk.jpeg)
I'm in completion
Optional(http://www.smarttapp.com/Portals/_default/Users/001/01/1/Martin%20Profile.jpg)
Using the Navigation Bar, I back out, then reload and the images are all place holder images as shown:
The print out is as follows:
I'm in completion nil
I'm in completion nil
I'm in completion nil
Finally this is the code where the image is loaded and the completion handler:
let myURL = Comment.profileImageURL
if Comment.profileImageURL != nil {
// profileImage.af_setImage(withURL: myURL!, placeholderImage: #imageLiteral(resourceName: "Profile Image"))
profileImage.af_setImage(
withURL: myURL!,
placeholderImage: #imageLiteral(resourceName: "Profile Image"),
filter: nil,
completion:{ image in
print("I'm in completion")
print(image.response?.url)
self.setNeedsLayout()
self.layoutIfNeeded()
})
} else {
profileImage.image = UIImage(named: "Profile Image")
}
I've tried everything at this point. Please help.
UITableViewController reuses cells. In order to prevent old images reappearing in cells that are not fully loaded yet, set the cell's image to nil before loading.
cell.profileImage.image = nil
let myURL = comment.profileImageURL
if comment.profileImageURL != nil {
[...]
} else {
cell.profileImage.image = UIImage(named: "Profile Image")
}
Each cell you display in image in can be reused if is scrolled off the screen. Using af_setImage will fetch the image, and display it when the response is received, but by that time the cell might be reused, and be trying to display an image at a different URL.
You can handle this in a couple of ways, but first thing to do is to add a Comment property to your cell, and handle the downloading there rather than in the view controller (you might find making all your IBOutlet properties fileprivate helps with this - it makes sure you're can't update the cell's UI from the view controller)
1) Cancel the download request when the cell is reused…
override func prepareForResuse() {
profileImage.af_cancelImageRequest
}
This should prevent the response for a previous URL being returned, but will mean the image isn't downloaded.
2) Check the URL that that the response is for is the one you're expecting.
var requestedURL: URL?
var comment: Comment? {
didSet {
if let myURL = Comment.profileImageURL {
requestedURL = myURL
// Fetch image from URL and when response is received,
// check self.myURL == response.url
} else {
profileImage.image = UIImage(named: "Profile Image")
}
}
}
This way the image will still be downloaded even if it's not displayed.
I am using this guy's Gif class to create a Gif UIImage. https://github.com/mayoff/uiimage-from-animated-gif
Load it with this :
let fileUrl = NSURL(string: "some url" as! String)
let Gif = UIImage.animatedImageWithAnimatedGIFURL(fileUrl!)
imageview.image=Gif
The thing is that it takes time, a lot of time, like 7 seconds till you see the image.
I would like to preload it in some way, or at least get a delegate when finish loading.
What are my options here ?
You can use Grand Central Dispatch (or GCD) to perform this task in the background.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// This will happen in the background
let fileUrl = NSURL(string: "some url" as! String)
let gif = UIImage.animatedImageWithAnimatedGIFURL(fileUrl!)
dispatch_async(dispatch_get_main_queue()) {
// Always update UI on the main thread
imageview.image = gif
}
}
This will still take however long it takes to load the gif but it won't lock up the UI so the user can do things while the gif loads.