Asynchronous loading image with request - swift

I have an api in which I get a link for request. In order to get a picture, I need to make a request in response to which I receive image data. How can I make getting asynchronous images? I find a lot of libraries and videos in youtube where this make with image link. But how i can do it in my case?
let token_type = KeychainWrapper.standard.string(forKey: "token_type")
let access_token = KeychainWrapper.standard.string(forKey: "access_token")
let headers:HTTPHeaders? = ["Authorization": "\(token_type ?? "") \(access_token ?? "")"]
Alamofire.request(cellInfo.imageLink ?? "", method: .get, headers: headers).responseImage { response in
if let image = response.result.value {
cell.firstTypeImageView.image = image
}
}

The issue with your example is that if the cell is reused by the time the image is retrieved, you’ll be updating the wrong row with this image. If updating the image view, it’s better to use the UIImageView extension. You enjoy several benefits with table view cells when you do this because it will automatically cancel the prior request for this cell. This means that:
It eliminates the problem of scrolling quickly having the image view in a reused cell flickering as images are retrieved for rows in the table.
It also means that you don’t have to worry about the current cell getting backlogged behind requests for cells that are no longer visible.
So, you might do something like:
if let imageLink = cellInfo.imageLink, let url = URL(string: imageLink) {
var request = URLRequest(url: url)
let authValue = "\(token_type ?? "") \(access_token ?? "")"
request.setValue(authValue, forHTTPHeaderField: "Authorization")
cell.firstTypeImageView.af_setImage(withURLRequest: request)
}
Or maybe you have a placeholder image, e.g. blank.png, then you’d do:
cell.firstTypeImageView.af_setImage(withURLRequest: request, placeholderImage: UIImage(named: "blank"))
Note, if your images are much larger than the image view in which you’re presenting them, you can see some stuttering in the tableview as the OS struggles to deal with such large assets. In that scenario, you can add a resizing filter:
let filter = AspectScaledToFillSizeFilter(size: cell.firstTypeImageView.frame.size) // or use whatever size appropriate
cell.firstTypeImageView.af_setImage(withURLRequest: request, placeholderImage: UIImage(named: "blank"), filter: filter)

Related

Image loading taking more time in swift

I am working on an application, where I am loading some images from the server and showing it on table view using Alamofire. But the problem is it is taking so much time to load the image. I want to show the image, like a blurry image until the time image is not loading. Please, someone, help me.
cell.profileImageView?.pin_setImage(from: URL(string: modelArray[indexPath.row]))
This one is the solution using Kingfisher pod. You can set an activity Indicator while image is loaded.
let imageUrl = modelArray[indexPath.row]
//set activity indicator until image is loaded
cell.profileImageView?.kf.indicatorType = .activity
if let url = URL(string: imageUrl) {
let resource = ImageResource(downloadURL: url)
cell.profileImageView?.kf.setImage(with: resource)
}

Re-downloading images with different sizes when using Kingfisher

i have an app that works with API JSON response. Just retrieving a simple array of items. Each item has an url with a high-res image. I'm using Kingfisher to get this images and resize to my ImageView size (that placed on my MainView)
//MainViewController
itemImageMainVC.kf.setImage(
with: URL(string: itemModel.artImage),
options: [
.processor(DownsamplingImageProcessor(size: itemImageMainVC.bounds.size) >> RoundCornerImageProcessor(cornerRadius: 16, targetSize: itemImageMainVC.bounds.size)),
.scaleFactor(UIScreen.main.scale),
.transition(.fade(0.2)),
.cacheOriginalImage
])
Everything is fine here, i've got my images on my MainView inside TableView, and they cached well. BUT! If i try to open and display the same images, but with different sizes on the SecondView, i see the same transition animation on the SecondView, so i guess that they started to download again when they shouldn't.
//SecondViewController
itemImageSecondVC.kf.setImage(
with: URL(string: itemModel.artImage),
options: [
.processor(DownsamplingImageProcessor(size: itemImageSecondVC.bounds.size) >> RoundCornerImageProcessor(cornerRadius: 16, targetSize: itemImageSecondVC.bounds.size)),
.scaleFactor(UIScreen.main.scale),
.transition(.fade(0.2)),
.cacheOriginalImage
])
itemImageMainVC and itemImageSecondVC have different sizes.
What i'm doing wrong? I just want to download/display images on the MainView and instantly show them when i open SecondView, without re-downloading
Lets try to use the cacheKey, it should be the last path component (or event the absoluteUrlString) from your image url or something that is unique.
let resource = ImageResource(downloadURL: url, cacheKey: url.lastPathComponent)
self.imageView.kf.setImage(with: resource, placeholder: UIImage(named: "placeholder-image"), options: nil, progressBlock: nil) { (result) in
switch result {
case .success(_):
// Success case
case .failure(_):
// Failed case
}
}

How can I iterate through an array of imageUrls on Firestore, to then return as images based on their index in UIPickerView?

Currently, I am fetching imageUrls from Firestore using my 'tuwos' delegate (code not included). I then use a forIn loop, where I use a print statement to make sure each individual imageUrl has been fetched successfully (they are). Also in my forIn loop, I use the Kingfisher cocoapod to set an imageView's image based on each imageUrl. Finally, I return imageView.image, but the default image, "girl_0", is being shown each time, meaning images are not being displayed from Firestore.
func pickerView(_ pickerView: AKPickerView, imageForItem item: Int) -> UIImage {
let tuwos = self.delegate?.tuwos
let imageView = UIImageView(image: #imageLiteral(resourceName: "jane2"))
if item == 0 {
for (index, element) in tuwos!.enumerated() {
if index == 0 {
let imageName = "\(element.profileImageUrl)"
print("element image url:", element.profileImageUrl)
let url = URL(string: imageName)
imageView.kf.setImage(with: url)
}
}
}
imageView.image = UIImage(named: "girl_0")!.imageWithSize(CGSize(width: 82, height: 82))
return imageView.image!
}
Other info:
(1) I'm using the AKPickerView cocoapod for a Horizontal Picker View, but the logic seems the same ('item' refers to their index).
(2) The 'tuwos' delegate is referring to where I query Firestore.
(3) I only set the code to execute for 'if item == 0' to see if it would execute at all, then was planning to figure out an abstraction to execute for each item in my pickerView. I suspect my problem is to do with my forIn loop, and that nothing is executing outside of it, hence returning the default for item 0.
This is my first post so apologies if I've missed anything, but happy to provide any info you need. I have looked around quite a lot trying to get the answer on my own (URLSession/kingfisher/sdwebimage/escaping closure/forin loop articles) but struggling on this one.

Access to Photos on iOS(Swift), have to try twice to get picture library to show up. It doesn't show up the first time, but show's the second time

I call the function. Alright
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
let index = viewControllers?.index(of: viewController)
if index == 2 {
let layout = UICollectionViewFlowLayout()
let photoSelectorController = PhotoSelectorController(collectionViewLayout: layout)
let navController = UINavigationController(rootViewController: photoSelectorController)
present(navController, animated: true, completion: nil)
return false }
return true
}
Photos not showing on first time
I have all of the right things asking for permission and everything..
I then call for the images with these functions. It works, but the second time I hit the button after canceling posting a post..
I'm not sure how to get the images from the library for the first call.
After that it works like a charm, but most users have been telling me this isn't a good experience , if they have to try twice.
I'm trying to reduce friction in the app usage.
It should show the pictures right after the user "Allows" the app access to the pictures so they can post, but I'm not sure what I'm doing wrong for it to show the pictures soon as someone grants access.
var selectedImage: UIImage?
var images = [UIImage]()
var assets = [PHAsset]()
fileprivate func assetsFetchOptions() -> PHFetchOptions {
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 100
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
return fetchOptions
}
fileprivate func fetchPhotos() {
let allPhotos = PHAsset.fetchAssets(with: .image, options: assetsFetchOptions())
DispatchQueue.global(qos: .background).async {
allPhotos.enumerateObjects { (asset, count, stop) in
print(asset)
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 200, height: 200)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: { (image, info) in
if let image = image {
self.images.append(image)
self.assets.append(asset)
if self.selectedImage == nil {
self.selectedImage = image
}
}
if count == allPhotos.count - 1 {
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
})
}
}
}
If you fetchAssets before the user grants privacy access to your app, you'll get a PHFetchResult that's empty.
However, if before making that fetch you register as a photo library observer, you'll get a photoLibraryDidChange callback as soon as the user approves privacy access for the app... from that callback you can access an updated version of your original fetch result (see changeDetails(for:)) that has all of the assets your fetch should have found. Then you can tell your UI to update and display those assets. (This is how Apple's canonical PhotoKit example code works.)
Also, once you have a populated fetch result, please don't request thumbnails for the whole thing the way you're doing.
Users commonly have photo libraries with tens of thousands of assets, many of which are in iCloud and not on the local device. If you synchronously get all thumbnails, you'll take forever, use tons of memory and CPU resources, and generate all kinds of network traffic (slowing things down even more) for resources your user may never see.
PhotoKit is designed to allow easy use in conjunction with UI elements like UICollectionView. A collection view only loads cells that are currently (or soon to be) on screen, even if you've told it you have zillions of items in your collection — similarly, you can request thumbnails only for assets that are visible in your collection view. Wherever you have your per-cell UI setup logic is where you should have your PHImageManager request. (Again, this is what the canonical PhotoKit example code does.)
You can optimize even further by "preheating" the thumbnail fetch/generation process for assets that are soon to be onscreen. And then by managing your "preheating" to cancel such work in progress when further UI updates (e.g. fast scrolling of large collection) make it unnecessary. PHCachingImageManager does this. (And yet again, it's what the canonical Apple sample does. Actually, that sample's a bit out of date, and as such does more work than it needs to on this front — it does its own calculation of what cells are just outside the scroll rect, but since iOS 10 the UICollectionViewDataSourcePrefetching protocol manages that for you.)

Media Image Loading Indefinitely

I'm currently using JSQMessagesViewController for showing the image bubble and SDImageWeb to download the images and display it. The way I'm doing this is that someone would send a person a text message containing a url and the sender and receiver would both check for that and if it matches display and download an image from the url. It would show up as an image bubble rather than text message.
Expected behavior
When sending media images or receiving I should have the images eventually load (there's a loading animation on the images). After it loads, I should easily see it next time I go on without loading since it's cached.
Actual behavior
When receiving media images, sometimes it loads right away but sometimes if it tries to load it will load indefinitely. I know it is done downloading because if I trigger a reload of collection view either through messaging something or going back to main view and coming back to the same viewcontroller it will show up. If I close my app and restart it, the pictures I have finished downloading should show up instead it will show up as loading again for indefinitely. It only happens to the ones most recent, the old ones are all okay.
Steps to reproduce
Use SD image to download images for media bubbles
Send images possibly on a slower internet where it takes more than a few milliseconds to load
Fixes I tried
The way I did this was I would receive a message containing a url and I download it and return back a JSQMessage that contains a media bubble. I tried reloading the collectionview at that index or reloading entire collectionview with a completion block in SD web but it ends up with a endless loop.
I'm not sure if this is currently a fault of SDweb or JSQMessageController. I have tried using Kingfisher as an image caching and downloading with relatively the same results.
Code
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
let message = self.messages[indexPath.item]
if (message.body!.rangeOfString ("An image https://x.com/uploads/") != nil) {
let types: NSTextCheckingType = .Link
let detector = try? NSDataDetector(types: types.rawValue)
guard let detect = detector else {
let JSQTypeMessage = JSQMessage(senderId: message.from, senderDisplayName: message.from, date: message.date, text: message.body)
return JSQTypeMessage
}
let matches = detect.matchesInString(message.body!, options: .ReportCompletion, range: NSMakeRange(0, message.body!.characters.count))
var stringUrl:NSURL?
stringUrl = matches[0].URL!
let tempImageView = UIImageView(image: nil)
tempImageView.sd_setImageWithURL(stringUrl, completed: nil)
let photoImage = JSQPhotoMediaItem(image: tempImageView.image)
// This makes it so the bubble can be incoming rather than just all outgoing.
if !(message.from == self.senderId) {
photoImage.appliesMediaViewMaskAsOutgoing = false
}
let message = JSQMessage(senderId: message.from, displayName: self.senderDisplayName, media: photoImage)
return message
}
let JSQTypeMessage = JSQMessage(senderId: message.from, senderDisplayName: message.from, date: message.date, text: message.body)
return JSQTypeMessage
}
I think what you what to do is use the completion handler that is built into sdWebImage
tempImageView.sd_setImageWithURL(stringUrl, completed: {
//Set your image in here because you know that you have an image
// Then reload the cell
Self.tableView.reloadCellAtIndexPath(indexPath)
})