reload tableview without image in cell reloads - swift

I have a tableview and cells within that it has imageview and it gets image from server and whenever user scroll down to end of tableview,tableview's data gets reloaded but my problem is cells image is going to download image from server again whenever tableview gets reloaded, this is my code:
let cell = tableView.dequeueReusableCell(withIdentifier: "pizzacell", for: indexPath) as! CustomPizzaCell
let imgurl = URL(string: "\(BaseUrl)//assests/products/imgs/\(MainMenu.cellarray[indexPath.row].img)")
cell.pizzaimg.kf.setImage(with: imgurl, options: [.downloadPriority(Float(indexPath.row)),.transition(.flipFromTop(1.0)),.forceRefresh,.processor(processor)])
if indexPath.row == MainMenu.cellarray.count-1 {
if !isendoftable {
MainMenu.start += 5
MainMenu.end += 5
var parameters: Parameters = ["START": "\(MainMenu.start)"]
parameters["END"] = "\(MainMenu.end)"
parameters["TYPE"] = "\(MainMenu.type)"
print(parameters)
ApiManager.request(Address: "\(BaseUrl)/admin/Android/getLastProducts", method: .post, parameters: parameters) { json in
self.stopAnimating()
if json == nil {
let popupDialog = PopupDialog(title: "خطای دسترسی به سرور", message: "")
let btn = DefaultButton(title: "تلاش مجدد"){}
btn.titleFont = UIFont(name: "IRANSans(FaNum)", size: 15)
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
btn.titleFont = UIFont(name: "IRANSans(FaNum)", size: 17)
}
popupDialog.addButton(btn)
self.present(popupDialog, animated: true, completion: nil)
}
else {
if (json?.array?.isEmpty)! {
MainMenu.start = 0
MainMenu.end = 5
self.isendoftable = true
}
for item in (json?.array)! {
let piiiza = MainMenu.cell(id: item["id"].description, title: item["title"].description, img: item["img"].description)
MainMenu.cellarray.append(piiiza)
}
tableView.reloadData()
}
}
}
}
return cell

You are using Kingfisher which already handle image caching. Loading a new image when tableView get reloaded should only be reading cache.
You need to remove the .forceRefresh in this line:
cell.pizzaimg.kf.setImage(with: imgurl, options: [.downloadPriority(Float(indexPath.row)),.transition(.flipFromTop(1.0)),.forceRefresh,.processor(processor)])
According to the docs, forceRefreshignore the cache and downloaded from the URL again. You can see this here.
forceRefresh
If set, Kingfisher will ignore the cache and try to fire a download task for the resource.

Related

UIImage and UILabel disappearing after doing UIRefreshControl

UIImages and UILabel are disappearing in some collectionViews after refreshing inside my app.
here is my code. You can see what is wrong in the picture, the left image is before refreshing and there is a discount price label and after refreshing the simulator, it is gone. getProductLatestData(), getBestSellingData() are the Alamofire get request for API.
//For Refresh Controller
//----------------------
var player : AVAudioPlayer
var refreshController : UIRefreshControl = {
let refreshController = UIRefreshControl()
refreshController.addTarget(self, action: #selector(updateData), for: .valueChanged)
return refreshController
}()
//----------------------
//For Update Data when scroll to top
//----------------------------------
#objc func updateData(){
//To play sound when scroll top
//-----------------------------
let pathToSound = Bundle.main.path(forResource: "Flick", ofType: "wav")
let url = URL(fileURLWithPath: pathToSound!)
do {
player = try AVAudioPlayer(contentsOf: url)
player?.play()
} catch let error {
print(error.localizedDescription)
}
//-----------------------------
//Fetch Latest Products Data
getProductLatestData(pageNumber: 0, pageSize: 0)
//Fetch BestSelling Products Data
getBestSellingData(pageNumber: 0, pageSize: 0)
//Fetch Promotion Products Data
getPromotionProductsData(pageNumber: 0, pageSize: 0)
//Fetch Products By Category
getProductsByCat(pageNumber: 0, pageSize: 0)
//Fetch Reward Products
getRewardProducts()
self.refreshController.endRefreshing()
}
if collectionView == latestItemCV {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "latestItemCell", for: indexPath) as! PromotionItemsCollectionViewCell
if let percent = latestItemData[indexPath.row].promotePercent {
if percent != 0 {
cell.percentOff.isHidden = false
cell.percentOff.text = " \(percent) % Off "
cell.percentOffBackground.isHidden = false
}
else{
cell.percentOff.isHidden = true
cell.percentOffBackground.isHidden = true
}
}
if let name = latestItemData[indexPath.row].name {
cell.itemsName.text = name
}
else{
cell.itemsName.text = "-"
}
if let url = latestItemData[indexPath.row].url {
cell.itemsImageView.sd_setImage(with: URL(string: url), placeholderImage: UIImage(named: "placeholder"))
}
//----------------------------------
Thanks to #larme I recheck the code here is how I fixed it, I added those disappeared labels isHidden = false also as I did isHidden = true for them.
if let promotionPrice = latestItemData[indexPath.row].promotePrice {
if promotionPrice != 0 {
cell.originalItemsPrice.text = "\(setCommaForPrice(price: Int(promotionPrice))) Ks"
cell.promotionPercent.isHidden = false
cell.promotionLine.isHidden = false
}
else{
if let orPrice = latestItemData[indexPath.row].originalPrice {
cell.originalItemsPrice.text = "\(setCommaForPrice(price: Int(orPrice))) Ks"
}
cell.promotionPercent.isHidden = true
cell.promotionLine.isHidden = true
}
}

Modify cell contents in tableview after clicking in Swift

I have a chat app with 2 custom cells, sender and receiver cell. Once the cells are populated, only the ones with "show image" text can be clicked. I am trying to click on these cells so that
i can hide the label
I can show the image from the url
The tap function works but i am not able to do the above steps. Kindly help because i do not know how to go about this
Here is my code
#objc
func tapFunctionCell(sender:UITapGestureRecognizer) {
print("tap again sen")
let tapLocation = sender.location(in: tblViewChat)
let indexPath = self.tblViewChat.indexPathForRow(at: tapLocation)
let position = indexPath?.row ?? 0
print(position)
let cell = tblViewChat.dequeueReusableCell(withIdentifier: "senderCell") as! SenderTblCell
cell.lblMessage.isHidden = true
let url = URL(string:objChatVM.getMessage(index:position))
print(url)
cell.ImageB.kf.setImage(with:url)
cell.ImageB.isHidden = false
}
#objc
func tapFunction(sender:UITapGestureRecognizer) {
print("tap again receive")
let cell2 = tblViewChat.dequeueReusableCell(withIdentifier: "receiverCell") as! ReceiverTblCell
cell2.lblMessage.isHidden = false
let tapLocation = sender.location(in: tblViewChat)
let indexPath = self.tblViewChat.indexPathForRow(at: tapLocation)
let position = indexPath?.row ?? 0
print(position)
let url = URL(string:objChatVM.getMessage(index:position))
print(url)
cell2.imageButton.kf.setImage(with:url)
cell2.imageButton.isHidden = false
}
Issues in your tap function code. When you want to get a cell from the index path use the cellForRow method of table view. so instead of this
let cell = tblViewChat.dequeueReusableCell(withIdentifier: "senderCell") as! SenderTblCell
use this for both tap action.
let cell = tblViewChat.cellForRow(at: indexPath) as! SenderTblCell

Swift and JSON url Image, edited

So this is the code that I am using to create my table view from scratch. My question is how can I parse an image if the image is of string (url) format?
class ArticleCell : UITableViewCell {
var article: Article? {
didSet {
articleTitle.text = article?.title
//articleImage.image = article?.urlToImage
descriptionTitle.text = article?.description
}
}
private let articleTitle : UILabel = {
let lbl = UILabel()
lbl.textColor = .black
lbl.font = UIFont.boldSystemFont(ofSize: 20)
lbl.textAlignment = .left
return lbl
}()
private let descriptionTitle : UILabel = {
let desclbl = UILabel()
desclbl.textColor = .black
desclbl.font = UIFont.boldSystemFont(ofSize: 10)
desclbl.textAlignment = .left
return desclbl
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(articleTitle)
addSubview(descriptionTitle)
Because then what I would like to do is:
addsubview(articleImage)
I get an error as I am declaring an image but it is in a string format. Now, using storyboard is easy, but programmatically I have this issue.
Is it more understandable now? I am so sorry if I made confusion.
To load an image from a URL (or string) we first need to do the following:
guard let urlToImage = article?.urlToImage, let urlContent = URL(string: urlToImage) else { return }
if let data = try? Data(contentsOf: urlContent) {
if let image = UIImage(data: data) {
articleImage.image = image
}
}
}
However this creates a terrible UI experience for the user as image loading from a URL synchronously will hold up our main thread and everyone else in the queue will be stuck waiting for image loading to finish.
For the user's sake, let's set title and description so long but send image loading on some background thread. It should look something like this:
var article: Article? {
didSet {
articleTitle.text = article?.title
descriptionTitle.text = article?.description
guard let urlToImage = article?.urlToImage, let urlContent = URL(string: urlToImage) else { return }
DispatchQueue.global().async {
if let data = try? Data(contentsOf: urlContent) {
if let image = UIImage(data: data) {
DispatchQueue.main.async { [weak self] in
self?.articleImage.image = image
self?.setNeedsLayout()
}
}
}
}
}
}
When we have our image we set it on main thread again and call setNeedsLayout() to tell the cell that it needs to adjust the layout of it's subviews.
NOTE: The above solution works if you are displaying very little cells. If you are displaying MANY cells on your tableview and you want to scroll while they load, you will encounter the age old problem of loading cell content (in this case the image) at the incorrect index path. I suggest reading up on how to asynchronously load images into table and collection views. Have fun!

How to randomize cards data from model array without repetition

I am displaying cards data from my model array, how do i randomize card data everytime i start the app and should not be repetitive. I am using ZLSwipeable view library for creating cards fro swipe feature. this is the function in which i have write all my code for swipe cards, and in the code wordsdata is the model array that i have declaredlike this var wordsdata = ModelRsult.
CardView is the view i am displaying my cards into
screenshot of my card display screen
in the image it is shown banana as the first data, now when i run the app again it should show me another data first and so on
my source code:
func nextCardView() -> UIView?
{
let arrayData = wordsData
if (self.colorIndex >= arrayData.count)
{
return nil
}
let cardView = CardView(frame: swipeableView.bounds)
if loadCardsFromXib
{
let contentView = Bundle.main.loadNibNamed("CardContentView", owner: self, options: nil)?.first! as! CardView
contentView.superVC = self
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.backgroundColor = cardView.backgroundColor
cardView.addSubview(contentView)
let data = arrayData[(self.colorIndex)] as ModelResult
contentView.wordModel = data
let savedValue = UserDefaults.standard.bool(forKey: "isLangChanged")
tempLangChanged = savedValue
if tempLangChanged == false
{
if let spanishName = data.cardWordEnglish, spanishName.count != 0
{
contentView.wordSpanish.text = spanishName
print(spanishName)
}
}
else
{
if let dict = data.cardWordImage, dict.count != 0
{
let url = WS_wordIconUrl + dict
contentView.wordImage.kf.indicatorType = .activity
contentView.wordImage.kf.setImage(with: URL(string: url))
}
if let spanishName = data.cardWordSpanish, spanishName.count != 0
{
contentView.wordSpanish.text = spanishName
print(spanishName)
}
}
contentView.soundBtn.addTarget(self, action:#selector(self.soundBtn(sender:)), for: .touchUpInside)
contentView.hideCard.addTarget(self, action:#selector(self.hideCard(sender:)), for: .touchUpInside)
contentView.soundBtn.tag = self.colorIndex
contentView.hideCard.tag = self.colorIndex
cardView.tag = self.colorIndex
constrain(contentView, cardView) { view1, view2 in
view1.left == view2.left
view1.top == view2.top
view1.width == cardView.bounds.width
view1.height == cardView.bounds.height
}
}
colorIndex += 1
return cardView
}
The best way for this problem is to shuffle the array where your cards are and then use it. In your case, I would shuffle it at the app start.
array.shuffle()

Cell repopulating over each other in TableView

I'm currently working with a tableview that contains three sections. Each section loads it's data from an array that is loaded from a backend and is stored in a local array. The data downloads and displays without issue, but once I scroll, the cells reload themselves over their previous data. The reason I caught it, outside of the memory warning, was that the cell contains an image with a drop shadow and the shadow grows darker with each scroll. I have a feeling the issue is related to how I'm calling the cell at indexPath for each section but can't seem to find a solution. I've searched around and haven't been able to find a resolution yet.
The cells all share the same custom class and reload their respective data through the same outlets.
Not sure if this is relevant to the issue but the view is called from information in the AppDelegate.
Here's the code that I'm working with staring with number of rows in section.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return likesName.count
} else if section == 1 {
return commentsName.count
} else if section == 2 {
return bookmarksName.count
}
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("toggleCell", forIndexPath:indexPath) as! RightToggleCell
cell.userAvatar.layer.cornerRadius = cell.userAvatar.frame.size.width/2
cell.userAvatar.clipsToBounds = true
cell.artworkImage.contentMode = UIViewContentMode.ScaleAspectFill
cell.artworkImage.clipsToBounds = true
cell.artistName.text = likesName[indexPath.row]
cell.information.text = "liked your artwork"
likesImage[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.artworkImage.image = downloadedImage
}
}
likesAvatar[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.userAvatar.image = downloadedImage
}
}
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier("toggleCell", forIndexPath:indexPath) as! RightToggleCell
cell.userAvatar.layer.cornerRadius = cell.userAvatar.frame.size.width/2
cell.userAvatar.clipsToBounds = true
cell.artworkImage.contentMode = UIViewContentMode.ScaleAspectFill
cell.artworkImage.clipsToBounds = true
cell.artistName.text = commentsName[indexPath.row]
cell.information.text = commentsInformation[indexPath.row]
commentsImage[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.artworkImage.image = downloadedImage
}
}
commentsAvatar[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.userAvatar.image = downloadedImage
}
}
} else if indexPath.section == 2 {
let cell = tableView.dequeueReusableCellWithIdentifier("toggleCell", forIndexPath:indexPath) as! RightToggleCell
cell.userAvatar.layer.cornerRadius = cell.userAvatar.frame.size.width/2
cell.userAvatar.clipsToBounds = true
cell.artworkImage.contentMode = UIViewContentMode.ScaleAspectFill
cell.artworkImage.clipsToBounds = true
cell.artistName.text = bookmarksName[indexPath.row]
cell.information.text = "bookmarked your artwork"
bookmarksImage[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.artworkImage.image = downloadedImage
}
}
bookmarksAvatar[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.userAvatar.image = downloadedImage
}
}
}
return cell
}
It's a bit hard to line up your code, but it looks like you have:
let cell = ...
if ... {
let cell = ...
} else if ... {
let cell = ...
}
return cell
If this is what you have, the let cell inside each if is only scoped to exist within that if. Therefore, the return cell at the end will return the initial, empty cell. If this is the case, you should change it to:
if ... {
let cell = ...
return cell
} else if ... {
let cell = ...
return cell
} else {
let cell = ...
return cell
}
Note that the last condition must just be else and not else if or the compiler will tell you not all code paths return a value (when the last "if" fails). However, you're saying it's basically working, so something doesn't make sense.
Either way, cells get reused for new rows once a row moves off screen. Therefore, by the time your getDataInBackgroundWithBlock returns, the cell might belong to a different row. Given the drop-shadow is getting darker, it sounds like you have code in RightToggleCell that is rerunning when a cell gets reused.
When you load data in the background, you should store it outside the cell, and reload that row when the load finishes. Something (very roughly) along the lines of:
likesImage[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
artworkImages[indexPath.row] = downloadedImage
dispatch_async(dispatch_get_main_queue(), {
tableView.reloadRowsAtIndexPaths([indexPath.row], withRowAnimation: true)
})
}
}
The cellForRowAtIndexPath should display the relevant artworkImages if it's available, or use the above code if not. Note that any updating of the UI must be done on the main thread, which is why dispatch_async is being called above.
Instead of doing everything on the TableViewController i suggest you to put this code into your "RightToggleCell".
override func awakeFromNib() {
self.userAvatar.layer.cornerRadius = self.userAvatar.frame.size.width/2
self.userAvatar.clipsToBounds = true
self.artworkImage.contentMode = UIViewContentMode.ScaleAspectFill
self.artworkImage.clipsToBounds = true
}
By doing this iOS doesn't modify cornerRadius and clipsToBounds everytime you reload your TableView
With the guidance from Michael, I moved return cell inside of each if let statement and returned UITableViewCell() outside of the statements and that resolved the issue. Thanks again Michael, I up voted your response but I don't have a high enough reputation for it to post.