Using UIImageView+AFNetworking, images doesn't appear until scrolling - ios

I'm using UIImageView+AFNetworking to load images in table cell, but
images downloaded from server aren't shown in cell until scroll down and up again. I thought that I needed to reload data in tableView, but I don't know where to put said reload code.
Here's the code:
#IBOutlet weak var refresherTool: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
let _ = dataRefresher.sharedInstance.refreshVideos(fromUrl: "https://api.vid.me/videos/featured", withLimit: "100", withOffset: "0", completion: {
featuredVideos in dataRefresher.sharedInstance.featuretVideos = featuredVideos
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
#IBAction func refreshBtn(_ sender: UIRefreshControl) {
let _ = dataRefresher.sharedInstance.refreshVideos(fromUrl: "https://api.vid.me/videos/featured", withLimit: "100", withOffset: "0", completion: {
featuredVideos in dataRefresher.sharedInstance.featuretVideos = featuredVideos
self.refresherTool.endRefreshing()
DispatchQueue.main.async{
self.tableView.reloadData()
}
})
refresherTool.endRefreshing()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if dataRefresher.sharedInstance.featuretVideos == nil {
return 0
}
return 100
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "featuredCell", for: indexPath)
// if dataRefresher.sharedInstance.featuretVideos == nil { return cell}
let newImage = UIImageView()
newImage.setImageWithUrl(URL.init(string: dataRefresher.sharedInstance.getVideosThumbnailUrls(fromVideos: dataRefresher.sharedInstance.featuretVideos)[indexPath.row])!, placeHolderImage: #imageLiteral(resourceName: "first"))
// newImage.setImageWithUrl(URL.init(string: urls[indexPath.row])!)
cell.imageView?.image = newImage.image
cell.textLabel?.text = dataRefresher.sharedInstance.getVideosTitels(fromVideos: dataRefresher.sharedInstance.featuretVideos)[indexPath.row]
print(indexPath.row)
print(newImage.image!)
return cell
}

The setImageWithUrl method is asynchronous, meaning that if the referenced image is not in the local cache yet, it will only be downloaded some time in the future.
However, your code tries to get the image out right away:
let newImage = UIImageView()
newImage.setImageWithUrl(..., placeHolderImage: ...)
cell.imageView?.image = newImage.image <-- synch call, image is nil here at first!
Fortunately, there's no need to reload the table view or do any other arcane tricks; just stop doing unnecessary things.
Remove the temporary UIImageView and load the image into the cell's UIImageView directly.
The above code becomes:
cell.imageView?.setImageWithUrl(..., placeHolderImage: ...)

Related

How to get different image from assets and assign it to image view in different table view cells

I am trying to add an image to my tableview cell by using an NFC reader session. So, my problem here is that every first reader session, I am getting the correct image in image view, but when I try the reader session the second time, I am stuck with two same last assigned image on both cells of my table view. I know it because of tableView.dequeueReusableCell method, but I am not sure which method to use to get correct image incorrect cells.
I have also attached a screenshot to make more clear of what I mean.
In the screenshot is should see an image of a water bottle from my assets, but instead, I am getting the last assigned image to every cell
Here is the code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.nfcModel = arrData[indexPath.row]
// IMG CELL
cell.img.image = UIImage(named: name)
return cell
}
Not an expert in NFC readers.
1.Create an array of products to store product data from NFC render.
2.in tableView func cellForRowAt you can render the images from
favoriteMovies using displayMovieImage function.
Sidenote:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var favoriteMovies: [Movie] = []
override func viewWillAppear(_ animated: Bool) {
mainTableView.reloadData()
super.viewWillAppear(animated)
if favoriteMovies.count == 0 {
favoriteMovies.append(Movie(id: "tt0372784", title: "Batman Begins", year: "2005", imageUrl: "https://images-na.ssl-images-amazon.com/images/M/MV5BNTM3OTc0MzM2OV5BMl5BanBnXkFtZTYwNzUwMTI3._V1_SX300.jpg"))
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let moviecell = tableView.dequeueReusableCell(withIdentifier: "customcell", for: indexPath) as! CustomTableViewCell
let idx: Int = indexPath.row
moviecell.tag = idx
//title
moviecell.movieTitle?.text = favoriteMovies[idx].title
//year
moviecell.movieYear?.text = favoriteMovies[idx].year
// image
displayMovieImage(idx, moviecell: moviecell)
return moviecell
}
func displayMovieImage(_ row: Int, moviecell: CustomTableViewCell) {
let url: String = (URL(string: favoriteMovies[row].imageUrl)?.absoluteString)!
URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async(execute: {
let image = UIImage(data: data!)
moviecell.movieImageView?.image = image
})
}).resume()
}

Remove row from table view

How can I remove row from tableview cell I cant remove the cell from table view I don't know why and when I try to delete never gone from table view please someone tell me how I can delete it one cell from multiple cell I have it. I know I should have var but I did not put it for example var = something
class addCartTblVC: UITableViewController {
//MARK: -Variables
var coreDataVariables = GetCoreDataVariables()
//MARK: -ViewMethords
override func viewDidLoad() {
super.viewDidLoad()
self.fetchCart()
let backItem = UIBarButtonItem()
backItem.title = "Back"
navigationItem.backBarButtonItem = backItem
tableView.tableFooterView = UIView()
}
//MARK: -tableView dataSource
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return coreDataVariables.result_add_Cart.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartCell = tableView.dequeueReusableCell(withIdentifier: "cartCell", for: indexPath) as! AddCartTblCell
let img = coreDataVariables.result_add_Cart[indexPath.row].product_Image!
let userPhotoString = img
let imageUrl:URL = URL(string: userPhotoString)!
let imageData:Data = try! Data(contentsOf: imageUrl)
// Add photo to a cell as a subview
if let image = UIImage(data: imageData){
cartCell.pro_Img.image = image
}
cartCell.pro_MRP.text = coreDataVariables.result_add_Cart[indexPath.row].product_Price
cartCell.pro_Details.text = coreDataVariables.result_add_Cart[indexPath.row].product_Name
cartCell.pro_Discount.text = coreDataVariables.result_add_Cart[indexPath.row].product_Discount
if cartCell.pro_Discount.text! == cartCell.pro_MRP.text!{
cartCell.pro_Discount.isHidden = true
cartCell.crossView.isHidden = true
}
else{
cartCell.pro_MRP.textColor = UIColor.darkGray
cartCell.pro_Discount.isHidden = false
cartCell.crossView.isHidden = false
}
cartCell.proceedBtn.tag = indexPath.row
cartCell.proceedBtn.addTarget(self, action: #selector(addCartTblVC.ProceedTapped(_:)), for: .touchUpInside)
cartCell.removeFromCoreDataBtn.tag = indexPath.row
cartCell.removeFromCoreDataBtn.addTarget(self, action: #selector(addCartTblVC.deleteFromCart(_:)), for: .touchUpInside)
return cartCell
}
//MARK: -fetchFunction
func fetchCart(){
let cart = Add_Cart()
coreDataVariables.result_add_Cart = cart.fetchCart()!
}
//MARK: -Proceed Action
#IBAction func back(_ sender: UIBarButtonItem) {
dismiss(animated: false, completion: nil)
}
Before loading table data try to loop on coreDataVariables.result_add_Cart and check for duplicate elements and try to make them single element by multiplying product_Price * n of duplicates and take care of product_Discount also.
your approach should be like this ((this method only for removing duplicates)) :
extension Array where Element: Equatable {
mutating func removeDuplicates() {
var result = [Element]()
for value in self {
if !result.contains(value) {
result.append(value)
}
}
self = result
}
==> Hope not getting negative vote for this answer :D

Is this the proper way to use heightForRowAt?

Im trying to implement dynamically sized row heights based on the size of downloaded images. The problem I am encountering is that the images are not downloaded when the function heightForRowAt is run. What is the proper way to implement this code. images is an array of UIImage, rowHeights is an array of type CGFloat and imageURLS is a string array of imageURLS.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Reuse", for: indexPath) as! TableViewCell
// Configure the cell...
///////////////////////
if(cell.cellImageView.image == nil){
let downloadURL = URL(string: self.imageURLS[indexPath.row])
URLSession.shared.dataTask(with: downloadURL!) { (data, _, _) in
if let data = data {
let image = UIImage(data: data)
DispatchQueue.main.async {
cell.cellImageView.image = image
cell.cellImageView.contentMode = .scaleAspectFit
self.images.insert(image!, at: 0)
let aspectRatio = Float((cell.cellImageView?.image?.size.width)!/(cell.cellImageView?.image?.size.height)!)
print("aspectRatio: \(aspectRatio)")
tableView.rowHeight = CGFloat(Float(UIScreen.main.bounds.width)/aspectRatio)
print("tableView.rowHeight: \(tableView.rowHeight)")
self.rowHeights.insert(CGFloat(Float(UIScreen.main.bounds.width)/aspectRatio), at: 0)
tableView.reloadRows(at: [indexPath], with: .top)
}
}
}.resume()
}
///////////////////////
return cell
}
//What is the proper way to implement this function
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
print("Im in height for row")
return CGFloat(0.0)
}
If your asynchronous request may change the height of the cell, you should not update the cell directly, but rather you should completely reload the cell.
So, heightForRowAt and cellForRowAt will be called once for each visible cell before the image is retrieved. Since the image hasn't been retrieved yet, heightForRowAt will have to return some fixed value appropriate for a cell with no image. And cellForRowAt should detect that the image has not been retrieved and initiate that process. But when the image retrieval is done, rather than updating the cell directly, cellForRowAt should call reloadRows(at:with:). That will start the process again for this row, including triggering heightForRowAt to be called again, too. But this time, the image should be there, so heightForRowAt can now return an appropriate height and cellForRowAt can now just update the image view with no further network request.
For example:
class ViewController: UITableViewController {
private var objects: [CustomObject]!
override func viewDidLoad() {
super.viewDidLoad()
objects = [
CustomObject(imageURL: URL(string: "https://upload.wikimedia.org/wikipedia/commons/e/e8/Second_Life_Landscape_01.jpg")!),
CustomObject(imageURL: URL(string: "https://upload.wikimedia.org/wikipedia/commons/7/78/Brorfelde_landscape_2.jpg")!)
]
}
let imageCache = ImageCache()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
let imageURL = objects[indexPath.row].imageURL
if let image = imageCache[imageURL] {
// if we got here, we found image in our cache, so we can just
// update image view and we're done
cell.customImageView.image = image
} else {
// if we got here, we have not yet downloaded the image, so let's
// request the image and then reload the cell
cell.customImageView.image = nil // make sure to reset the image view
URLSession.shared.dataTask(with: imageURL) { data, _, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
if let image = UIImage(data: data) {
self.imageCache[imageURL] = image
DispatchQueue.main.async {
// NB: This assumes that rows cannot be inserted while this asynchronous
// request is underway. If that is not a valid assumption, you will need to
// go back to your model and determine what `IndexPath` now represents
// this row in the table.
tableView.reloadRows(at: [indexPath], with: .middle)
}
}
}.resume()
}
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let imageURL = objects[indexPath.row].imageURL
if let image = imageCache[imageURL] {
let size = image.size
return view.bounds.size.width * size.height / size.width
} else {
return 0
}
}
}
Where a simple image cache (which is not relevant to your question, but I include for the sake of completeness) is as follows:
class ImageCache {
private let cache = NSCache<NSURL, UIImage>()
private var observer: NSObjectProtocol!
init () {
observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidReceiveMemoryWarning, object: nil, queue: nil) { [weak self] _ in
self?.cache.removeAllObjects()
}
}
deinit {
NotificationCenter.default.removeObserver(observer)
}
subscript(key: URL) -> UIImage? {
get {
return cache.object(forKey: key as NSURL)
}
set (newValue) {
if let image = newValue {
cache.setObject(image, forKey: key as NSURL)
} else {
cache.removeObject(forKey: key as NSURL)
}
}
}
}

What is the best approach to resize rows in a table view?

What is the best approach to resize rows in a table view as smoothly as possible so that the image contained in the cell preserves its aspect ratio while taking up the whole cell?
Note: These images are being downloaded in cellForRowAt method so their sizes are not known until this point during the application runtime.
UPDATE:
This is the code which configures the TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var cellImageView: UIImageView!
var aspectConstraint: NSLayoutConstraint?
override func prepareForReuse() {
super.prepareForReuse()
aspectConstraint = nil
}
func setCellImage(image:UIImage){
let aspect = image.size.width / image.size.height
aspectConstraint = cellImageView.heightAnchor.constraint(equalTo: cellImageView.widthAnchor, multiplier: aspect)
cellImageView.image = image
}
}
This is the code for the tableViewController
import UIKit
import Firebase
import FirebaseStorageUI
class TableViewController: UITableViewController {
var imageURLS:[String] = [String]()
var listener:ListenerRegistration?
override func viewDidLoad() {
super.viewDidLoad()
listener = Firestore.firestore().collection("Posts").addSnapshotListener{
querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
print("New data: \(diff.document.data())")
}
if (diff.type == .modified) {
print("Modified data: \(diff.document.data())")
}
if (diff.type == .removed) {
print("Removed data: \(diff.document.data())")
}
guard let newImageURL = diff.document.data()["imageDownloadURL"] as? String else{
print("Failed to get image download URL")
return
}
print("downloadURL: \(newImageURL)")
self.imageURLS.insert(newImageURL, at: 0)
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.insertRows(at: [indexPath], with: .top)
}
}
tableView.estimatedRowHeight = 100.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return imageURLS.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Reuse", for: indexPath) as! TableViewCell
let downloadURL = URL(string: self.imageURLS[indexPath.row])
if cell.cellImageView.image == nil{
URLSession.shared.dataTask(with: downloadURL!) { (data, _, _) in
if let data = data {
let image = UIImage(data: data)
DispatchQueue.main.async {
cell.setCellImage(image:image!)
tableView.reloadRows(at: [indexPath], with: .top)
}
}
}.resume()
}
return cell
}
}
You should follow these steps:
Set UITableViewAutomaticDimension for the tableview
Set auto layout constraints for the image view to the top and bottom of the cell so that the cell will automatically adjust its height to the height of the image view.
Since you are images are downloaded you should reload reload cell on the completion of the image download (I will be able to explain more on this if you can share the code you use to download the images).

Using Almofireimage to set image in a Custom Tableview Cell

I'm using Alamofireimage to set an image based on a remote url on a UIImageView in my Custom UITableViewCell however the results are (1) the images aren't set until you scroll and (2) even though I'm using StackViews for my autolayout the sizes of the images displayed in the app vary wildly. Any idea what I'm doing wrong?
import UIKit
import Alamofire
import AlamofireImage
class AppsTableViewController: UITableViewController {
let session = URLSession.shared
var rekos: [Reko]? {
didSet {
DispatchQueue.main.async {
self.tableView?.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
RekoManager().downloadAndConvertRekos(rekoType: .apps) { (result) in
print("Reko Count = \(result.count)")
self.rekos = result
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let count = rekos?.count else {return 0}
return count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AppCell", for: indexPath) as! AppsTableViewCell
if let reko = rekos?[indexPath.row] {
cell.titleLabel.text = reko.title
cell.descriptionLabel.text = reko.description
if let imageUrlString = reko.imageUrlString {
if let imageURL = URL(string: imageUrlString) {
//TODO IMPLEMENT SPINNER OVER IMAGE
cell.appImageView.af_setImage(withURL: imageURL, placeholderImage: UIImage(named: "placeholder"), filter: nil, progress: nil, progressQueue: DispatchQueue.main, imageTransition: .noTransition, runImageTransitionIfCached: false, completion: { (result) in
cell.setNeedsLayout()
cell.layoutIfNeeded()
})
}
}
}
return cell
}
}

Resources