CollectionView Custom cell Label Disappears on Scroll - swift

I have a custom cell for my collectionview and one of the labels in the cell disappears when I scroll down and back up to the cell again.
Here is my cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == productCollectionView {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifiers.ProductCell, for: indexPath) as? ProductCell {
if cartProducts.contains(products[indexPath.item]) {
//this is the product
var product = products[indexPath.item]
//get the index from cartProducts
let index = cartProducts.firstIndex(of: product)
//Get the cartcount and update the product
let cartCount: Int = cartProducts[index!].cartCount
product.cartCount = cartCount
products[indexPath.item] = product
//now set the updated product for cell.product
cell.product = products[indexPath.item]
cell.delegate = self
return cell
} else {
cell.product = products[indexPath.item]
cell.delegate = self
return cell
}
} else
if collectionView == categoryCollectionView{
//...this is another collection and not the collectionView that I have problem with
return cell
}
return UICollectionViewCell()
}
}
And here is what I have in y custom cell for the label that is disappearing when I scroll down and back up to the cell:
class ProductCell: UICollectionViewCell {
var product: Product!
{
didSet {
commonUIUpdate()
}
}
func commonUIUpdate() {
//set the price per amount and its unit
if product.pricePerAmountExist == true {
pricePerAmountLbl.isHidden = false
if let PricePerAmount = formatter.string(from: Double(product.pricePerAmount)/100 as NSNumber) {
let PricePerAmountVoume = Float(String(Double(product.pricePerAmountVolume)/100))?.clean ?? ""
pricePerAmountLbl.text = "\(PricePerAmount)/\(String(describing: PricePerAmountVoume))\(product.pricePerAmountUnit)"
}
} else
if product.pricePerAmountExist == false {
pricePerAmountLbl.isHidden = true
}
}
}
I have the label pricePerAmountLbl.isHidden set to true and false when needed but still I dont understand why its disappearing. When I dont set the hidden status for the label in the custom cell the content of the label gets repeated in all cell which is not right either.
Edit:
I added the following to the cell but still no success the problem is still there
else if product.pricePerAmountExist == false {
pricePerAmountLbl.isHidden = true
} else {
pricePerAmountLbl.isHidden = false
}

You are not handling all cases. Your label stays hidden because it was set hidden in the previous cell. You might want to alter you code to cover for the missing case. Hope it helps:
else if product.pricePerAmountExist == false {
pricePerAmountLbl.isHidden = true
} else {
pricePerAmountLbl.isHidden = false
pricePerAmountLbl.text = "This case was not handled before"
}

There was nothing wrong with my code! The error was happening in storyboard. My label was stored in a stackview and that stackview was stored in another stackview; when I was scrolling and the label would go hidden the stackview height would have gone to zero and it stayed at zero even though I unhide the label.

Related

Swift - How to deselect all selected cells [duplicate]

I have a FollowVC and FollowCell Setup with collection View. I can display all the datas correctly into my uIcollection view cell using the following code with no problem.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("FollowCell", forIndexPath: indexPath) as? FollowCell {
let post = posts[indexPath.row]
cell.configureCell(post, img: img)
if cell.selected == true {
cell.checkImg.hidden = false
} else {
cell.checkImg.hidden = true
}
return cell
}
}
Note that I could also select and deselect multiple images using the following code
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if deletePressed == true {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! FollowCell
cell.checkImg.hidden = false
} else {
let post = posts[indexPath.row]
performSegueWithIdentifier(SEGUE_FOLLOW_TO_COMMENTVC, sender: post)
}
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! FollowCell
cell.checkImg.hidden = true
}
When In "Select" mode, I can perform the selction of each cell and a check mark will be displayed on the cell. However, what I want to do is to have a cancel buttom to disable all the selected cell and removing the checkImg.
I have tried
func clearSelection() {
print("ClearSelection posts.count = \(posts.count)")
for item in 0...posts.count - 1 {
let indexP = NSIndexPath(forItem: item, inSection: 0)
followCollectionView.deselectItemAtIndexPath(indexP, animated: true)
let cell = followCollectionView.cellForItemAtIndexPath(indexP) as! FollowCell
cell.checkImg.hidden = true
}
}
The program crashes here giving me a fatal error: Unexpectedly found nil while unwrapping an optional error at
let cell = followCollectionView.cellForItemAtIndexPath(indexP) as! FollowCell
I dont know why it is having trouble unwrapping the cell to be my FollowCell which contains an instance of the checkImg. I already used it before in a similar situation in didSelectItemAtIndexPath and it seems to work?
Thanks,
Not all of the selected cells may be on screen at the point when you are clearing the selection status, so collectionView.cellForItemAtIndexPath(indexPath) may return nil. Since you have a force downcast you will get an exception in this case.
You need to modify your code to handle the potential nil condition but you can also make your code more efficient by using the indexPathsForSelectedItems property of UICollectionView
let selectedItems = followCollectionView.indexPathsForSelectedItems
for (indexPath in selectedItems) {
followCollectionView.deselectItemAtIndexPath(indexPath, animated:true)
if let cell = followCollectionView.cellForItemAtIndexPath(indexPath) as? FollowCell {
cell.checkImg.hidden = true
}
}
Using Extension in Swift 4
extension UICollectionView {
func deselectAllItems(animated: Bool) {
guard let selectedItems = indexPathsForSelectedItems else { return }
for indexPath in selectedItems { deselectItem(at: indexPath, animated: animated) }
}
}
To simplify further, you could just do
followCollectionView.allowsSelection = false
followCollectionView.allowsSelection = true
This will in fact correctly clear your followCollectionView.indexPathsForSelectedItems even though it feels very wrong.
collectionView.indexPathsForSelectedItems?
.forEach { collectionView.deselectItem(at: $0, animated: false) }
This answer may be useful in swift 4.2
let selectedItems = followCollectionView.indexPathsForSelectedItems
for (value in selectedItems) {
followCollectionView.deselectItemAtIndexPath(value, animated:true)
if let cell = followCollectionView.cellForItemAtIndexPath(value) as? FollowCell {
cell.checkImg.hidden = true
}
}
I got it solved easier by doing this:
tableView.selectRow(at: nil, animated: true, scrollPosition: UITableView.ScrollPosition.top)

didDeselectItemAt not working after scrolling swift

I am designing a menu tab bar with collectionview, and I want to change the color of the label when it is selcted.
Everything works fine but when the selected item is not in the screen anymore(due to scroll out of the screen), then the func inside didDeselectItemAt is not working anymore.
Is there anyway to solve this problem? Below is the code:
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if collectionView == self.productMenuCollectionView {
guard let cell = self.productMenuCollectionView.cellForItem(at: indexPath) as? ProductMenuCollectionViewCell else {
return
}
cell.label.textColor = UIColor.black
} else {
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.productMenuCollectionView {
let cell = self.productMenuCollectionView.cellForItem(at: indexPath) as! ProductMenuCollectionViewCell
cell.label.textColor = CustomColor.primary
} else {
}
}
You are observing this behaviour because the cells are reused, so one cell can be used for one index path, but when that index path scrolls out of view, and new index paths scrolls into view, the same cell object could be used for one of the new cells. Whenever you dequeue a cell, keep in mind that you might be reconfiguring old cells!
So what happens is, one of the old selected cells moves out of view, and gets reconfigured for use at a new index path. Your code presumably removes the selected color from that cell at that time, so when you scroll back up, the color is gone.
What you should do is, in ProductMenuCollectionViewCell, override isSelected:
override var isSelected: Bool {
didSet {
if isSelected {
self.label.textColor = CustomColor.primary
} else {
self.label.textColor = UIColor.black
}
}
}
And in cellForItemAtIndexPath:
if collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false {
cell.isSelected = true
} else {
cell.isSelected = false
}

Why my checkbox in custom cell shows different behaviour while selecting and scrolling in swift?

I have a xib view in which I took a tableView with a customcell xib. In this custom cell I have a checkbox button which behaves like check and uncheck using custom cell. But when ever I click the first cell checkbox as tick the multiple of 9th cell like 9th row cell, 18th row cell, .....also became ticked. and while scrolling the checkbox tick option is changing between cells. I am not able to know why this is happening..??
I have registered cell xib view as:
override func viewDidLoad() {
super.viewDidLoad()
//Register custom cell
let nib = UINib(nibName: "CustomOneCell", bundle: nil)
AddOnTableView.registerNib(nib, forCellReuseIdentifier: "addoncell")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ADDONITEMS.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomOneCell = AddOnTableView.dequeueReusableCellWithIdentifier("addoncell") as! CustomOneCell
let item: AddOnItems = ADDONITEMS[indexPath.row]
cell.addOnName.text = item.name
cell.addOnPrice.text = "£\(item.price!)"
return cell
}
For checkbox I have added a custom class as below:
var isCheckedAddOnGlobal = Bool()
class AddOnCheckBox: UIButton {
let checkedImage = UIImage(named: "checkboxredtick.png")! as UIImage
let unCheckedImage = UIImage(named:"checkbox untick.png")!as UIImage
//bool property
var ischecked:Bool = false{
didSet{
//print(ischecked)
if ischecked == true{
self.setImage(checkedImage, forState: .Normal)
}else{
self.setImage(unCheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action:#selector(CheckBox.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.ischecked = false
}
func buttonClicked(sender: UIButton) {
if (sender == self) {
if ischecked == true{
ischecked = false
isCheckedAddOnGlobal = false
}else{
ischecked = true
isCheckedAddOnGlobal = true
}
}
}
}
This is happening because you are reusing the TableViewCell, To solve your problem you can try something like this, first create an array of Int that give you selected row and use that array inside cellForRowAtIndexPath method.
var selectedItems = [Int]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomOneCell = AddO nTableView.dequeueReusableCellWithIdentifier("addoncell") as! CustomOneCell
let item: AddOnItems = ADDONITEMS[indexPath.row]
cell.addOnName.text = item.name
cell.addOnPrice.text = "£\(item.price!)"
cell.checkBoxBtn.tag = indexPath.row
if (selectedItems.contains(indexPath.row)) {
cell.checkBoxBtn.setImage(UIImage(named:"checkbox untick.png"), forState: .Normal)
}
else {
cell.checkBoxBtn.setImage(UIImage(named: "checkboxredtick.png"), forState: .Normal)
}
cell.checkBoxBtn.addTarget(self, action:#selector(self.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func buttonClicked(sender: UIButton) {
if (self.selectedItems.contains(sender.tag)) {
let index = self.selectedItems.indexOf(sender.tag)
self.selectedItems.removeAtIndex(index)
}
else {
self.selectedItems.append(sender.tag)
}
self.tableView.reloadData()
}
Best way is on selecting cell call
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomOneCell
cell.buttonClicked()
}
and change buttonClicked method to
func buttonClicked() {
...
}
I would make an object, which contains the product information and a boolean, to check if the product has been selected or not.
If you make it this way, the checkmarks will appear correct. When you are scrolling on a tableview, then it loads the data everytime it shows new cells.
Right now, it only knows that the index etc. 9 is selected, and when you scroll down and load new cells, then the index 9 will be selected automatic again.
Try something like this:
Example
class Product {
var productName = "Test"
var isSelected: Bool = false
}
Under your cellForRowAtIndexPath
if product.isSelected == true {
cell.checkBoxBtn.setImage(UIImage(named:"checkbox untick.png"), forState: .Normal)
} else {
cell.checkBoxBtn.setImage(UIImage(named: "checkboxredtick.png"), forState: .Normal)
}

UITableview with custom cells

Hi I am having a problem in my app
I am using UITableView with the custom cell. In each cell, I have a
checkbox, when I check it, it is getting the cell’s element into a
set. Everything is working correctly except a small problem.
The Problem:
2
As in the image when I click on the first row, the 10th row is seeming
like checked. The image changed to checked but in practice, it did
not occur in my set.
Here is the related part of the code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.labelInCell.text = tableData[indexPath.row]//data.myFunc().myset[indexPath.row]
cell.checkBoxInCell.tag = indexPath.row
cell.checkBoxInCell.addTarget(self, action: Selector("yourCheckBoxClicked:"), forControlEvents: .TouchUpInside)
// cell.images.image = UIImage(named: tableData[indexPath.row])
// if images name is same as in tableData put it in front of label
return cell
}
func yourCheckBoxClicked(cbx:UIButton){
let picked = self.tableData[cbx.tag]
if choosenSet.contains(picked) {
choosenSet.remove(picked) // uncheck
} else {
choosenSet.insert(picked) // check
}
print(choosenSet)
}
Number of rows in section ->
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
Checkboxes class ->
class checkBox: UIButton {
//images
let checkedImage = UIImage(named: "button_checkboxFilled")
let uncheckedImage = UIImage(named: "button_checkboxEmpty")
// Bool Property
var isChecked : Bool = false {
didSet{
if isChecked == true{
self.setImage(checkedImage, forState: .Normal)
}else {
self.setImage(uncheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: "buttonClicked:" , forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
func buttonClicked (sender:UIButton) {
if (sender == self) {
if isChecked == true {
isChecked = false
}else {
isChecked = true
}
}
}
}
This is because of cells reusage. You should just add this line to your func tableView(tableView:cellForRowAtIndexPath:) method:
cell.checkBoxInCell.isChecked = choosenSet.contains(tableData[indexPath.row])
Your cells are reused, quick solution which will work fine as your table view doesn't have a lot of cells:
let cell: TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell" + String(indexPath.row)) as! TblCell
If your tableView has many rows than you should have "cell" identifier and update the selection style on each cellForRowAtIndexPath call

Remove the subView inside Cell

In my ViewController, I have a variable:
var shareButtonTapped = false
I want when I click the shareButton on the navigationBar, it will show the shareButton all of the cell, except the cell which indexPath.row == 0.
Here is the action of shareButton:
#IBAction func shareButtonTapped(sender: AnyObject) {
shareButtonTapped = !shareButtonTapped
collectionView.reloadData()
}
And here is the CollectionViewDataSource method:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! GiftCollectionViewCell
if shareButtonTapped {
cell.gift = gifts[indexPath.row]
let shareButton = FBSDKShareButton(frame: CGRect(x: 0, y: 0, width: 80, height: 30))
shareButton.tag = 1
let photo = FBSDKSharePhoto()
photo.image = gifts[indexPath.row].image
photo.userGenerated = true
let content = FBSDKSharePhotoContent()
content.photos = [photo]
content.contentURL = NSURL(string: "http://www.fantageek.com")
shareButton.shareContent = content
cell.contentView.addSubview(shareButton)
return cell
} else {
cell.gift = gifts[indexPath.row]
return cell
}
}
It worked:
But I want to click this shareButton on NavigationBar again, all of shareButton inside each cell will disappear. How to do this?
Any helps would be appreciated. Thanks.
Use removeFromSuperview() method inside your else block:
else {
cell.gift = gifts[indexPath.row]
for subview in cell.contentView.subviews {
if subview is FBSDKShareButton {
subview.removeFromSuperview()
}
}
return cell
}
Better approach will be to keep the share button always be part of cell. And show and hide the button based on status of shareButtonTapped of bar button.
Let's say your button name is shareImage then in cellForRow
cell.shareButton.hidden = false
if(shareButtonTapped){
cell.shareButton.hidden = true
}