Collection view button background image getting repeated on scroll - ios

I have a collection view.I have a button called furnitureSelectionBtn inside the collection view cell.
When i tap the button inside the collection view,it sets the button image as selected.png.When i tap it again it gets unselected and i set with unselected.png image.
When i tap the button cardselection method gets triggered and i save the index clicked in allFurnituresArray.
Now when i scroll this collection view ,few of the cells are getting selected image even if i havent selected them.This generally happens for the cells outside of the view .Cells getting reused I beleive.
For example if i have selected the 1st button and i start scrolling,I see 4th button also in selected state
So i tried to check in cellforRow method against the allFurnituresArray indexes which are selected and even thats not working.
I also tried to see if i can use prepareForReuse() but we dont get information about index I have scrolled to there.
Can someone please help
CardCollectionViewCell.swift
class CardCollectionViewCell: SwipingCarouselCollectionViewCell {
#IBOutlet weak var furnitureSelectionBtn: UIButton!
static let reuseIdentifier = "CardCollectionViewCell"
static var nib: UINib {
get {
return UINib(nibName: "CardCollectionViewCell", bundle: nil)
}
}
override func awakeFromNib() {
super.awakeFromNib()
furnitureSelectionBtn.setBackgroundImage(image, for: UIControl.State.normal)
}
CardViewController.swift
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CardCollectionViewCell.reuseIdentifier, for: indexPath) as! CardCollectionViewCell
// Configure the cell
cell.delegate = self
cell.furnitureSelectionBtn.tag = (indexPath as NSIndexPath).row
for element in allFurnituresArray {
print(element)
if (indexPath.row) == element as! Int {
let image1 = UIImage(named: "selected.png")
cell.furnitureSelectionBtn.setBackgroundImage(image1, for: UIControl.State.selected)
}
else
{
let image1 = UIImage(named: "unselected.png")
cell.furnitureSelectionBtn.setBackgroundImage(image1, for: UIControl.State.normal)
}
}
cell.furnitureSelectionBtn.addTarget(self, action: #selector(CardViewController.cardselection(_:)), for: .touchUpInside)
return cell
}
#objc func cardselection(_ sender:UIButton!)
{
if sender.isSelected == true {
sender.isSelected = false
let image1 = UIImage(named: "unselected.png")
sender.setBackgroundImage(image1, for: UIControl.State.normal)
allFurnituresArray.remove(sender.tag)
}else {
sender.isSelected = true
let image1 = UIImage(named: "selected.png")
allFurnituresArray.add(sender.tag)
sender.setBackgroundImage(image1, for: UIControl.State.selected)
}
print("Button tapped")
print(sender.tag)
}

The best way to save the selection status is to add this data to your item in the datasource array, e.g,
(1) datasource array
var items = [Item]()
(2) didSelectItem...
items[indexPath.row].isSelected = !(items[indexPath.row].isSelected)
and handle selection status in your cell.

Related

How can I access a button added in a collection view cell?

I added buttons in my collection view cells by the code below, where 'myButton' refers to the buttons I try to make access to.
When I click some button outside the collectionView, I want one of my buttons to have its background image changed to have the background of the button clicked, which I tried with 'sendToBox' function below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! ItemCollectionViewCell
cell.myButton.setTitle(self.items[indexPath.item], for: .normal)
cell.backgroundColor = UIColor.white
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 0.5
return cell
}
...
#IBAction func keyClick(_ sender: UIButton) {
...
sendToBox(object: sender)
...
}
...
func sendToBox(object sentObject: UIButton) {
let imageToSend = sentObject.backgroundImage(for: .normal)
let imageIdentity = sentObject.restorationIdentifier
let cell = self.collectionView(ItemCollectionSet, cellForItemAt: IndexPath(item: 0, section: 0))
cell.myButton ## blah blah not working!
}
So I want to make a direct access to one of the cells I created, and then change the button inside it to have a different appearance. I'm stuck in this matter for a whole day, please help me out.

Toggle image within uicollectionviewcell when clicked

I have a uicollectionview with a series of custom class cells that have a few textviews and a uibutton. With over 100 cells, I just want to toggle the uibutton image for each respective cell. The uibutton is a favorites button, and like most apps I just want to favorite and "un-favorite" different cells.
NOTE: I tried to add the gesture recognizer in the class directly, but for some reason the image changes, but it highlights multiple cells instead of the specific cell that was clicked
my code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SimpleExampleSubCell
cell.backgroundColor = UIColor.init(white: 0.10, alpha: 0.25)
cell.infoLine2TextVw.text = ""
cell.infoLine3TextVw.text = ""
if let heading_name = self.dict_dict_holder[indexPath.item]["Name"]{
cell.headerTextVw.text = heading_name
cell.infoLine1TextVw.text = self.dict_dict_holder[indexPath.item]["Phone"]
}
cell.bringSubview(toFront: cell.headerTextVw)
cell.favorite_button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AddFavorite(withSender:))))
return cell
}
#objc func AddFavorite(withSender sender:UIButton){
print("clicked")
//The line below fails each time I run it.
sender.setImage(newImage.png,.normal)
}
Replace
#objc func addFavorite(withSender sender:UIButton){
with
// not recommended use touchUpInside
#objc func addFavorite(_ sender:UITapGestureRecognizer){
let btn = sender.view! as! UIButton
}
OR better
cell.favorite_button.addTarget(self, action:#selector(addFavorite), for: .touchUpInside)
Don't Add tapgestures to buttons , as they they have their own targets like touchUpInside or touchUpOutside and many more
table cells are reused you need to nil them inside cellForRowAt or give an else
if someCondition {
cell.favorite_button.setImage(newImage1.png,.normal)
else {
cell.favorite_button.setImage(newImage2.png,.normal)
}
you have to set the default image (plus everything you want to reset) for each cell in the prepareForReuse() method so it clears up the reused content

UITableView-upon scrolling it loses properties associated with selected cells. How to save these properties? - swift

I have an application with a custom popup that holds a tableview (see below). When I tap on a check box(UIButton), i swap out the background image and toggle between checked and unchecked. When I have a checked box, when I scroll up or down, the checked box defaults back to unchecked. Please could someone advise on how to keep the checked status of each cell upon scrolling?
class CustomHashTagPopup: UIViewController, UITableViewDelegate, UITableViewDataSource{
// CREATE TABLEVIEW CELLS
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableViewPicker.dequeueReusableCell(withIdentifier: "pickerCell") as! PreviewTableViewCustomPickerCell
if(indexPath == [0,0]){
let image = UIImage.init(named: "add")
cell.checkBoxOutlet.setBackgroundImage(image!, for: .normal)
cell.pickerLabel.text = "Create New HashTag"
cell.isCreateTagCell = true
}else{
let image = UIImage.init(named: "uncheck")
cell.checkBoxOutlet.setBackgroundImage(image!, for: .normal)
cell.pickerLabel.text = arrayHashTags[indexPath.row]
cell.isCreateTagCell = false
}
return cell
}
}
class PreviewTableViewCustomPickerCell: UITableViewCell {
var isSelect: Bool = false
var isCreateTagCell: Bool = false // TO DISTINGUISH 'CEATE NEW HASHTAG OPTION'
/*** OUTLETS ***/
#IBOutlet weak var checkBoxOutlet: UIButton!
#IBOutlet weak var pickerLabel: UILabel!
// TAP ON CHECK BOX
#IBAction func checkBoxBtn(_ sender: Any) {
if(isSelect == false && isCreateTagCell == false){
isSelect = true
// SHOW GREEN SELECTED CHECK BOX
let image = UIImage.init(named: "tick")
self.checkBoxOutlet.setBackgroundImage(image!, for: .normal)
}else if(isSelect == true && isCreateTagCell == false){
isSelect = false
// SHOW UNCHECKED BOX
let image = UIImage.init(named: "uncheck")
self.checkBoxOutlet.setBackgroundImage(image!, for: .normal)
}
}
}
The thing you miss is cell-reusing , you have to store every action inside the model array and check the current value and set the appropriate settings inside cellForRowAt , you may set the delegate of the actions to the vc for the buttons inside cellForRowAt for ease access of the model array to manipulate it as the user clicks that button at a specific indexPath , so conside declaring array like this
let arr = [false,true,false,false,false] first index is dummy as it will be skipped in Create New HashTag
//
#objc func btnClicked(_ sender:UIButton) {
arr[sender.tag] = !arr[sender.tag]
// reload indexPath of row = sender.tag , section = 0
}
//
Inside cellForRowAt
if (indexPath == [0,0]) {
let image = UIImage(named: "add")
cell.checkBoxOutlet.setBackgroundImage(image!, for: .normal)
cell.pickerLabel.text = "Create New HashTag"
cell.isCreateTagCell = true
}else{
let image = UIImage(named: arr[indexPath.row] ? "tick" : "uncheck") // edited here
cell.checkBoxOutlet.setBackgroundImage(image!, for: .normal)
cell.pickerLabel.text = arrayHashTags[indexPath.row]
cell.isCreateTagCell = false
}
cell.button.addTarget(self, action: #selector(btnClicked(_:)), for: .touchUpInside)
cell.button.tag = indexPath.row

UITableViewCell images disappear when scrolling

I have a UIButton inside a UITableViewCell where the image changes once the button is tapped. Though the selected buttons get selected as intended, once the UITableView scrolls, the selected images disappear since the cells are reused.
I'm having trouble writing the logic. Please help.
My code is below, in Swift 3.
CellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
//Button_Action
addSongButtonIdentifier(cell: cell, indexPath.row)
}
This is where the cell is created:
func addSongButtonIdentifier(cell: UITableViewCell, _ index: Int) {
let addButton = cell.viewWithTag(TABLE_CELL_TAGS.addButton) as! UIButton
//accessibilityIdentifier is used to identify a particular element which takes an input parameter of a string
//assigning the indexpath button
addButton.accessibilityIdentifier = String (index)
// print("visible Index:",index)
print("Index when scrolling :",addButton.accessibilityIdentifier!)
addButton.setImage(UIImage(named: "correct"), for: UIControlState.selected)
addButton.setImage(UIImage(named: "add_btn"), for: UIControlState.normal)
addButton.isSelected = false
addButton.addTarget(self, action: #selector(AddToPlaylistViewController.tapFunction), for:.touchUpInside)
}
The tap function:
func tapFunction(sender: UIButton) {
print("IndexOfRow :",sender.accessibilityIdentifier!)
// if let seporated by a comma defines, if let inside a if let. So if the first fails it wont come to second if let
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]//toggle the state when tapped multiple times
}
sender.isSelected = !sender.isSelected //image toggle
print(" Array Data: ", self.sateOfNewSongArray)
selectedSongList.removeAll()
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element{
print("true:", index)
selectedSongList.append(songDetailsArray[index])
print("selectedSongList :",selectedSongList)
}
}
}
There is logical error in func addSongButtonIdentifier(cell: UITableViewCell, _ index: Int) function at line addButton.isSelected = false
it should be addButton.isSelected = self.sateOfNewSongArray[index]
Since, cellForRowAtIndexpath method is called every time table is scrolled, it's resetting selected state of 'addButton'
You need to have array where you store which indexes are selected like selectedSongList array that you have. Then in your cellForRow method you need to use bool proparty from this array to give selected or deselected state to your button or in your addSongButtonIdentifier method selected state need to be
addButton.isSelected = selectedSongList.contains(indexPath.row)
Create a Model class for filling UITableView and take UIImage varaivals in that model, which will hold the current image for cell. On click on button action just change the UIImage variable with current image.
Best approach will be using a model class and keeping the track of each indiviual element in cell. But let me give you a quick fix.
Create a custom class of Button any where like this.
class classname: UIButton {
var imageName: String?
}
Go in your storyboard change the class from UIButton to classname
In your tableViewCellForIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let addButton = cell.viewWithTag(TABLE_CELL_TAGS.addButton) as! classname
if let imgName = addButton.imageName {
addButton.setImage(UIImage(named: imgName), for: UIControlState.normal)
} else {
addButton.setImage(UIImage(named: "add_btn"), for:UIControlState.normal)
}
addButton.addTarget(self, action: #selector(AddToPlaylistViewController.tapFunction), for:.touchUpInside)
return cell
}
Now let's move to your tapbutton implementation
func tapFunction(sender: classname) {
print("IndexOfRow :",sender.accessibilityIdentifier!)
// if let seporated by a comma defines, if let inside a if let. So if the first fails it wont come to second if let
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]//toggle the state when tapped multiple times
}
sender.imageName = sender.imageName == "correct" ? "add_btn" : "correct" //image toggle
sender.setImage(UIImage(named: sender.imageName), for:UIControlState.normal)
print(" Array Data: ", self.sateOfNewSongArray)
selectedSongList.removeAll()
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element{
print("true:", index)
selectedSongList.append(songDetailsArray[index])
print("selectedSongList :",selectedSongList)
}
}
}

Reusing cell doesn't work well - TableView

I have a problem about my cell's button.
In my tableView each row is composed by: an image, some labels and a button.
The button has a checkmark image. When it is clicked, the button's image changes.
The problem is that also another button's image changes without reason.
This mistake happens because my cell is reused.
I have tried to use prepareForReuse method in TableViewCell but nothing happens. I've also tried with selectedRowAt but I didn't have any results. Please help me.
Image 1:
Image 2:
This is my func in my custom Cell:
override func prepareForReuse() {
if checkStr == "uncheck"{
self.checkBook.setImage(uncheck, for: .normal)
} else if checkStr == "check"{
self.checkBook.setImage(check, for: .normal)
}
}
func isPressed(){
let uncheck = UIImage(named:"uncheck")
let check = UIImage(named: "check")
if self.checkBook.currentImage == uncheck{
checkStr == "check"
} else self.checkBook.currentImage == check{
checkStr == "uncheck"
}
}
In my tableView:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell: ListPropertyUserCell = tableView.cellForRow(at: indexPath) as! ListPropertyUserCell
let uncheck = UIImage(named:"uncheck")
let check = UIImage(named: "check")
if selectedCell.checkBook.imageView?.image == uncheck{
selectedCell.checkStr = "check"
} else if selectedCell.checkBook.imageView?.image == check{
selectedCell.checkStr = "uncheck"
}
}
From the information in your post, this looks like a cell reuse issue. The problem is that the tableView reuses the cells rather than creating new ones, to maintain performance. If you haven't reset the cell's state, the reused cell will be remain configured in the old state.
For a quick fix, you can implement the prepareForReuse method on UITableViewCell.
However, you'll need to store which cell is 'checked' in your view controller if you want the checkbox to be selected after scrolling the tableView. You can store this yourself, or use the tableView's didSelectRowAtIndexPath method.
Try to do it like this:
var checkBook = UIImageView()
if self.checkBook.image == UIImage(named: "check"){
self.checkBook.image = UIImage(named: "uncheck")
}
else{
self.checkBook.image = UIImage(named: "check")
}
If you're using the click on the entire cell, you can override the setSelected func in your custom cell just like that.
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.checkBook.image = UIImage(named: "check")
} else {
self.checkBook.image = UIImage(named: "uncheck")
}
}
UITableViewCell is reusable. You can't store state of view in cell. You should setup cell in
func tableView(UITableView, cellForRowAt: IndexPath)
method of your data source
The easiest way to achieve that is to implement
func tableView(UITableView, didSelectRowAt: IndexPath)
func tableView(UITableView, didDeselectRowAt: IndexPath)
methods of UITableViewDelegate
Then you can add/remove indexPath to some array in these methods and in cellForRowAtIndexPath setup cell.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("YourTableViewCell") as! YourTableViewCell
if array.contains(indexPath) {
cell.checkBook.image = UIImage(named: "check")
} else {
cell.checkBook.image = UIImage(named: "uncheck")
}
return cell
}
Try my code . here selectindex is use for get selected cell index and selectedindex is NSMutableArray that i store all selected cell value.
var selectindex : Int?
var selectedindex : NSMutableArray = NSMutableArray()
#IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("LikeCell", forIndexPath: indexPath)
let like: UIButton = (cell.viewWithTag(2) as! UIButton)// like button
let comment: UIButton = (cell.viewWithTag(3) as! UIButton) // comment button
comment.setBackgroundImage(UIImage(named: "chat.png"), forState: UIControlState.Normal) // comment button set
like.addTarget(self, action: #selector(self.CloseMethod(_:event:)), forControlEvents: .TouchDown)
comment.addTarget(self, action: #selector(self.CloseMethod1(_:event:)), forControlEvents: .TouchDown)
return cell
}
// This is my like button action method.
#IBAction func CloseMethod(sender: UIButton, event: AnyObject) {
let touches = event.allTouches()!
let touch = touches.first!
let currentTouchPosition = touch.locationInView(self.tableview)
let indexPath = self.tableview.indexPathForRowAtPoint(currentTouchPosition)!
selectindex = indexPath.row
if selectedindex.containsObject(selectindex!) {
sender.setBackgroundImage(UIImage.init(named: "like (1).png"), forState: .Normal)
selectedindex.removeObject(selectindex!)
}else{
sender.setBackgroundImage(UIImage.init(named: "like.png"), forState: .Normal)
selectedindex.addObject(selectindex!)
}
}
I faced this problem recently, and did not find much about it. What solve, after much searching, was to use:
override func prepareForReuse() {
btnAdd.setImage(nil, for: .normal) //here I use to change to none image
super.prepareForReuse()
}
just put this method inside your custom UITableViewCell, and set which item you want to realese stats.

Resources