How to load image with SDWebImage correctly in UICollectionView in swift - swift

I'm facing the following problem, well I have a custom collection view, where I upload an image from the URL I get from a service, everything works fine at the moment but sometimes when scrolling or when I leave the screen and return, my collection view customized it is altered, it does not load correctly, I have seen several posts where they present the same problem in different cases, but trying to conclude that my problem is at the moment of using the SDWebImage library. I have seen that they use methods like the prepareForReuse setting the image to nil but still my problem persists.
So you see the first time
and then it looks like this the second time
Here this my code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ItemMenuFilterCollectionViewCell", for: indexPath) as! ItemMenuFilterCollectionViewCell
cell.mImgProduct.image = nil
cell.mImgProduct.sd_setImage(with: URL(string: mDataListProducts[indexPath.row].photo!), placeholderImage: UIImage(named: "placeholder.png"))
cell.mTxtProductName.text = mDataListProducts[indexPath.row].shortName
cell.mTxtBrand.text = mDataListProducts[indexPath.row].marca
cell.mTxtStore.text = mDataListProducts[indexPath.row].tienda
cell.mTxtPrice.text = mDataListProducts[indexPath.row].price
cell.mRatingStar.rating = mDataListProducts[indexPath.row].valor?.toDouble() ?? 0
return cell
}
As I mentioned, I think that my problem is in charge of the image with the libreta since I comment on that line of code cell.mImgProduct.sd_setImage(with: URL(string: mDataListProducts[indexPath.row].photo!), placeholderImage: UIImage(named: "placeholder.png"))
All function ok, I would like to know a better way to do it or maybe correct something that is happening to me.
I also have this in the prepareforReuse of my customviewcell but still nothing.
class ItemMenuFilterCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var mRatingStar: CosmosView!
#IBOutlet weak var mImgProduct: UIImageView!
#IBOutlet weak var mTxtProductName: UILabel!
#IBOutlet weak var mTxtBrand: UILabel!
#IBOutlet weak var mTxtStore: UILabel!
#IBOutlet weak var mTxtPrice: UILabel!
override func prepareForReuse() {
self.mImgProduct.sd_cancelCurrentImageLoad()
self.mImgProduct.image = nil
}
}

Related

Custom UITableViewCell changes width randomly (swift)

I'm making a chess app for swift with a chat function. I upload my messages to firebase firestore and then, download them into a [Message] array and display them in a UITableView.
I've been looking around for this on stackoverflow and found that several people are having trouble with sizes of a custom UITableViewCell. I've run into the same problem, but in my case the width of the cell seems to randomly change every time I call .reloadData(). To illustrate :
The first 2 times or so when I type something in my chat it should look like this (the cells in the circle)
But as you can see after a few times this happens :
My code :
Custom cell class
class MessageCustomCell: UITableViewCell {
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var bgView: UIView!
#IBOutlet weak var leftImage: UIImageView!
#IBOutlet weak var messageBubble: UIView!
#IBOutlet weak var messageLabel: UILabel!
#IBOutlet weak var rightImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
messageLabel.numberOfLines = 0
messageBubble.layer.cornerRadius = 5
}
}
TableView delegate methods (TableView = messageView)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allMessages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = messageView.dequeueReusableCell(withIdentifier: "MessageCustomCell") as! MessageCustomCell
//cell.width = tableView.frame.width
let msg = allMessages[indexPath.row]
//cell.frame.size.width = messageView.frame.width
//cell.contentView.frame.size.width = messageView.frame.width
let date = msg.time
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "d/M/yy, h:mm"
let realDate = dateFormatter.string(from: date)
print(msg.fromUserName!)
if msg.fromUserName! == currentUserName {
cell.messageBubble.backgroundColor = UIColor(named: "lightSquare")
cell.messageLabel.textColor = .black
cell.rightImage.isHidden = true
} else {
cell.messageBubble.backgroundColor = UIColor(named: "darkSquare")
cell.messageLabel.textColor = .white
cell.leftImage.isHidden = true
}
cell.dateLabel.text = realDate
cell.messageLabel.text = msg.text!
return cell
}
In my MessageCustomcell.xib file I have a few constraints :
I've put the leftImage, messageBubble (UIView), rightImage in a horizontal stackview. The images have only width and height constraints. The label in the messageBubble has constraints to its superview (top,bottom,trailing,leading)
The stackView has constraints to the bgView (UIView), also top bottom trailing and leading.
Same for the bgView to the safe area.
I've tried setting the frame for the custom cell, but that didn't seem to work, so I've commented that out.
Really hope someone can help me out. If you guys need more info then please do let me know :)
Thanks all.
EDIT :
Here are my constraints for the custom cell xib file.
Alright thanks to #SPatel, who pointed out to make 2 separate .xib files for the receiver chat bubble and the sender chat bubble. This actually worked for my situation and was the right way to go in the first place, instead of manipulating one custom cell for both sender and receiver.
I'm thinking the constraint issue had to do with me trying to hide either the right image or the left image, to change the custom cell appearance for both sides. That must have messed up the whole constraints situation for the custom cell.
Anyway, happy man. Moving on!

Delegate used to control a button inside a cell not working

I have a custom UIView with 3 tableviews, the tableviews are self-sizing depending on the content they have, and they a show a different nib when they are empty than when they have content. All of this is embedded in a scroll view and it's working already, my view scrolls and the tableviews display the content appropriately and they autosize properly, the scroll view's size is adjusting without problems either.
In two of the tableviews when the tableviews have items there are some buttons in the cell nib that I want to access along with the index of the cell clicked inside the View controller where I have defined the Table view.
Here in the pic I'm showing the tableViews each with one item, the Canastas tableView has just a delete item button and the Productos tableView has a delete item button and a stepper to increase or decrease the amount.
I found a solution involving delegates in StackOverflow
Get button click inside UI table view cell, which I implemented. But for some reason it isn't working and the code in the delegate method isn't being executed.
Here is my code:
CanastasViewCell
import UIKit
protocol CanastasViewCellDelegate: class {
func closeButtonTapped(at index: IndexPath)
}
class CanastasViewCell: UITableViewCell {
#IBOutlet weak var imagenProducto: UIImageView!
#IBOutlet weak var nombreProducto: UILabel!
#IBOutlet weak var descProducto: UILabel!
#IBOutlet weak var precioProducto: UILabel!
#IBOutlet weak var closeButton: UIButton!
weak var delegate: CanastasViewCellDelegate?
var indexPath: IndexPath!
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func eliminarProducto(_ sender: AnyObject) {
self.delegate?.closeButtonTapped(at: indexPath)
}
}
CarritoViewController
import UIKit
class CarritoViewController: UIViewController, UITableViewDelegate,
UITableViewDataSource, CanastasViewCellDelegate {
func closeButtonTapped(at index: IndexPath) {
print("Button tapped at index:\(index)")
}
It looks like you're not setting the delegate inside CarritoViewController. You'll want to make sure that you set cell.delegate = self inside func tableView(UITableView, cellForRowAt: IndexPath).

How do I use UIStepper to change a property in a Realm Model Object?

I currently have a ViewController with a TableView inside it called SelectedListItemsViewController. This ViewController's TableView is populated by a custom cell class called SelectedListItemsTableViewCell.
I have an array of Realm Model Objects called selectedListItems, each of which has several properties. The SelectedListItemsTableViewCell populates the TableView with the listItem property of that indexPath.row's object, and each row has a UIStepper with a UILabel next to it that (as of now) shows UIStepper.value for each row. Ideally, the label will reflect the listItemWeight property of each row, and change it when incrementing or decrementing that row.
This is my custom cell:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
}
}
And in my ViewController's cellForRowAtIndexPath, I've configured the cell like so:
// Configure the cell...
cell.selectedListItemLabel.text = selectedListItems[indexPath.row].listItem
cell.listItemWeightLabel.text = "\(selectedListItems[indexPath.row].listItemWeight)"
Which perfectly loads the listItem property, and the listItemWeight property shows up correctly, but as soon as I increment or decrement on the UIStepper it gets messed up.
How do I properly link my UILabel and UIStepper to the [indexPath.row].listItemWeight?
In the same method that gets called when your stepper updates, update your listItem. However, since this item is stored in your Realm database, you will have to get an instance of your Realm database and write the change to the database.
You can do this by having your TableViewCell hold on to an instance of the listItem. Your new TableViewCell class will look something like this:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
var listItem: Item?
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
if let listItem = listItem {
let realm = try! Realm
try! realm.write {
listItem.listItemWeight = Int(sender.value) ?? 0
}
}
}
}
The above answer was helpful in leading me to the actual solution of my issue, but if anyone in the future is curious - I ended up using a closure.
In my custom cell class, I did
#IBAction func stepperValueChanged(sender: UIStepper) {
selectedListItemLabel.text = Int(sender.value).description
tapped?(self)
}
And in my view controller, I did
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! SelectedListItemsTableViewCell
cell.stepperControl.value = selectedListItems[indexPath.row].listItemWeight
// Configure the cell...
cell.tapped = { (selectedCell) -> Void in
selectedListItems[indexPath.row].listItemWeight = cell.stepperControl.value
}
Which allowed me to access each cell's UIStepper in the view controller file.
It helped to read flashadvanced's Option 2 answer in this thread.

How to dynamically add a cell to UICollectionView

I want to add a cell in UICollectionCell on run time. What is the best way that not affected performances?
And It's my sample cell class:
class PersonViewCell: UICollectionViewCell {
#IBOutlet weak var coverImg: UIImageView!
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var titleLbl: UILabel!
var p: Person!
func configureCell(p: Person) {
self.titleLbl = p.title
nameLbl.text = p.name
coverImg.image = UIImage(named: "image-not-available")
}
}
Make a new instance variable in your class to store an array and save your JSON results to it. Then use that to return the number of cells in numberOfItemsInSection, and draw the cells in cellForItemAtIndexPath. If you want to update after that, just call collection.reloadData()
Also, assuming you're making your network request on a background thread, make sure you call collection.reloadData() on the main.

iOS8/Swift: UICollectionViewCell height calculation

deriving from other posts about this topic i tried the following approach - but somehow "foo" as well as "bar" seem to be nil
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlowLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var foo = collectionView.cellForItemAtIndexPath(indexPath) as? messageCell
var bar = foo?.messageText.frame.height
return CGSizeMake(self.view.frame.width-20, bar!+20)
}
the class for the custom cell:
class messageCell: UICollectionViewCell,UIPopoverPresentationControllerDelegate,UITextViewDelegate{
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var timestampLabel: UILabel!
#IBOutlet weak var messageText: UITextView!
func setCell(authorLabelText:String, messageText:String,timestampLabelText:String) {
self.authorLabel?.text = authorLabelText
self.timestampLabel?.text = timestampLabelText
self.messageText?.text = messageText
self.messageText.delegate = self
self.messageText.sizeToFit() //works, i can see different heights when giving a custom color background
println(self.messageText.frame.height)
}
}
Am I supposed to set the height in my custom cell class? That would make no sense as it isnt rendered yet, wouldn't it?
€dit:
A Screenshot from stopping at "foo":
bar is nil because foo is nil - that's what the optional chaining "?" is doing in that expression. If collectionView.cellForItemAtIndexPath(indexPath) as? MessageCell† is nil, it would imply either:
There is no cell at that index path, or
The cell at indexPath is not an instance of MessageCell
I would suggest it's more likely to be the latter, as those delegate messages shouldn't really call with an invalid index path. Where you register your cell class (either in code, or in the storyboard), are you sure you're registering them as MessageCell cells?
† Types should begin with an uppercase character