uibutton in collectionview cell action duplicating - ios

So basically my problem is that when I click on a button which is present in collection view cell it should change the colour of button background colour. but the problem is it is changing the colour of another button. eg if I click on button 1 it changes the colour of button 6 automatically.
class hello: UICollectionViewCell {
#IBOutlet weak var btn: UIButton!
#IBAction func click(_ sender: Any) {
if btn.isSelected == true
{
btn.backgroundColor = UIColor.red
btn.isSelected = false
}
else{ btn.backgroundColor = UIColor.purple
btn.isSelected = true
}
}
override func prepareForReuse() {
super.prepareForReuse()
}
}
view controller file
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "happy", for: indexPath) as! hello
if cell.btn.isSelected
{
cell.btn.backgroundColor = UIColor.red
}
else{ cell.btn.backgroundColor = UIColor.purple
}
cell.btn.tag = indexPath.item
print(cell.btn.isSelected ,indexPath.row)
return cell
}

The problem is that the UICollectionView re-uses cell for optimized scroll performance. Hence it re-uses the cell at index 1 when displaying cell at index 6 for e.g. Therefore you need to set the state of the cell when ever it is updated/reused.
The following function is called everytime. So you need to set cell.btn. backgroundColor over here.
func collectionView(_ collectionView: UICollectionView, cellForItemAt
indexPath: IndexPath) -> UICollectionViewCell {
...
...
if dataSource[indexPath.row].selected {
btn.backgroundColor = UIColor.red
}else {
btn.backgroundColor = UIColor.purple
}
...
return cell
}
Now, it is upto your individual implementation, how you want to update the model when selection is changed. One option is you can define a protocol and implement it in your ViewController to update the values.

Related

iOS swift: UICollectionview horizontal scroll single cell not reloading

In my application UICollection scroll horizontally. collection-view cell two button and one UIView was designed.
Each cell loaded two button. when user click one button the particular cell should be reload and the UIView will be displayed in that cell only other cell not displayed.
Here i attached the collectionview image:
Here my code:
#IBOutlet weak var dataCollectionView: UICollectionView!
var addIndexArray:[Int] = []
var arraySelectedFilterIndex = [IndexPath]()
self.listArr = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"]
override func viewDidLoad() {
super.viewDidLoad()
self.dataCollectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (lvsnListArr.count/2)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = dataCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier_CollectionView, for: indexPath as IndexPath) as! DataCell
if self.arraySelectedFilterIndex.contains(indexPath) {
cell.buttonView.isHidden = false
}else{
cell.buttonView.isHidden = true
}
cell.btn1.setTitle(lvsnListArr[2 * indexPath.row], for: .normal)
cell.btn1.tag = indexPath.row
cell.btn1.addTarget(self, action: #selector(btnPressed(sender:)), for: .touchUpInside)
cell.btn2.setTitle(lvsnListArr[2 * indexPath.row + 1], for: .normal)
cell.btn2.tag = indexPath.row
cell.btn2.addTarget(self, action: #selector(btn2Pressed(sender:)), for: .touchUpInside)
return cell
}
#objc func btnPressed(sender: UIButton) {
self.addIndexArray.append(sender.tag)
let hitPoint = sender.convert(CGPoint.zero, to: dataCollectionView)
if let indexPath = dataCollectionView.indexPathForItem(at: hitPoint) {
print("indexPath--->",indexPath)
self.arraySelectedFilterIndex.append(getIndexPath)
self.dataCollectionView.reloadItems(at: [getIndexPath])
}
}
#objc func btn2Pressed(sender: UIButton) {
self.addIndexArray.append(sender.tag)
let hitPoint = sender.convert(CGPoint.zero, to: dataCollectionView)
if let indexPath = dataCollectionView.indexPathForItem(at: hitPoint) {
print("indexPath--->",indexPath)
self.arraySelectedFilterIndex.append(getIndexPath)
self.dataCollectionView.reloadItems(at: [getIndexPath])
}
}
My error:
I clicked cell btn1 one action btnPressed single cell over load and display the next cell image both images are overlap in collectionview.
Here i attached my issue cell image.
Here i got cell indexpath and indexpath.row, how can i validate and fix this issue in cell for row. struggling this point.
Kinldy help to fix this issues. Thanks advance.
You can reload single cell like you are doing in button action
self.dataCollectionView.reloadItems(at: [getIndexPath])
OR
You can get cell in button action like as below
if let cell = self.dataCollectionView.cellForItem(at: indexPath) as? DataCell
{
//Here you can write your view hide show code
if self.arraySelectedFilterIndex.contains(indexPath)
{
cell.buttonView.isHidden = false
}
else
{
cell.buttonView.isHidden = true
}
}

UIButton interaction is not smooth when used in UICollectionViewCell

I Have a UICollectionViewCell in which I have added UIButton. Normally button action gets called but some times it does not. When same button I add in a viewcontroller the interaction is very smooth. Even a gentle tap trigger the action.
Below is the code for button :
func makeTapButton(for superView: UIView) -> UIButton {
let offSetValue = 15
let button = UIButton()
button.backgroundColor = UIColor.yellow
superView.addSubview(button)
button.snp.makeConstraints { (make) in
make.leading.equalToSuperview().offset(-offSetValue)
make.trailing.equalToSuperview().offset(offSetValue)
make.top.equalToSuperview().offset(-offSetValue)
make.bottom.equalToSuperview().offset(offSetValue)
}
return button
}
func setupCustomView() {
self.addSubview(containerStackView)
containerStackView.snp.makeConstraints { (make) -> Void in
make.top.equalTo(self)
make.leading.equalTo(self)
make.trailing.equalTo(self)
make.bottom.equalTo(self)
}
containerStackView.addArrangedSubview(commentStack)
containerStackView.addArrangedSubview(retweetStack)
containerStackView.addArrangedSubview(likeStack)
commentStack.addArrangedSubview(commentImageView)
commentStack.addArrangedSubview(commentsCountLabel)
retweetStack.addArrangedSubview(retweetImageView)
retweetStack.addArrangedSubview(retweetCountLabel)
likeStack.addArrangedSubview(likeImageView)
likeStack.addArrangedSubview(likesCountLabel)
likeButton = makeTapButton(for: likeStack)
commentButton = makeTapButton(for: commentStack)
retweetButton = makeTapButton(for: retweetStack)
}
try below mentioned code when using UIbutton placed in collectionview
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath as IndexPath) as! UICollectionViewCell
cell.btnName.addTarget(self, action: #selector(btnSelClk), for: .touchUpInside)
cell.binSel.tag = collectionView.tag
cell.binSel.accessibilityValue = String(indexPath.row)
return cell
}
#objc func btnSelClk(sender:UIButton) {
selectAry[sender.tag] = sender.accessibilityValue!
// your button action
}
Defining your buttons in UICollectionViewCell class and your functions in UIViewController class being less laggy because they are reused;
import UIKit
class YourCell: UITableViewCell {
#IBOutlet weak var yourBtn: UIButton!
var yourButtonAction: (() -> ())?
#IBAction func buttonPressed(_ sender: UISlider) {
yourButtonAction()
}
}
then in your ViewController where you call your cell;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCell", for: indexPath) as! YourCell
cell.yourBtn = {[unowned self] in
// call your functions here, I hope this will be less laggy
print("button pressed")
}
}

How to allow multiple UICollectionViewCells to be selected and deselected?

I have a UICollectionView (with .allowsMultipleSelection = true), and I want it so that whenever I tap on a cell, it changes color, and when I tap it again, it reverts to its old color. I understand that UICollectionViewCells have a isSelected property, so I've tried manipulating that to change the colors.
override var isSelected: Bool {
didSet {
if isSelected == false {
contentView.backgroundColor = .lightGray
roleLabel.textColor = .gray
} else {
contentView.backgroundColor = .gray
roleLabel.textColor = .black
print("selected")
}
}
}
My the view controller class that contains the collection view in question has defined didSelectItemAt. However, whenever I tap on the cell, "selected" is printed each time - meaning regardless of how many times I've tapped on the cell, it still sets isSelected to be true each time - and the color doesn't change. Why is this happening?
you have to Store your selected cell in array.for example just store indexpath of selected Cell in Array than Check if indexpath is Already present in Array that means it is Already Selected.
var slectedCell = [IndexPath]()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if slectedCell.contains(indexPath) {
cell.backgroundColor = UIColor.gray
slectedCell = slectedCell.filter { $0 != indexPath }
}
else {
slectedCell.append(indexPath)
cell.backgroundColor = UIColor.lightGray
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if slectedCell.contains(indexPath) {
cell.backgroundColor = UIColor.gray
}
else {
cell.backgroundColor = UIColor.lightGray
}
}

How to change Color of cell in UiCollectionView if just two can be selected

I am used button inside UICollectionView cell, and I want to button can change color just two indexes and other can't change color if click other buttons uicolor.clear
and I want to like this, so how to use sender.backgroundColor
func collectionView(_: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellB", for: indexPath) as! BandingCollectionViewCell
cell.bBandingCell.addTarget(self, action: #selector(masterAction3(_:)), for: .touchUpInside)
return cell
}
{
#objc func masterAction3(_ sender: UIButton) {
var indexPath = collectionView.indexPath(for: ((sender.superview?.superview) as! BandingCollectionViewCell))
if sender.isSelected {
sender.isSelected = false
switch indexPath?.row {
case 0:
print("0")
sender.backgroundColor = UIColor.clear
case 1:
print("1")
sender.backgroundColor = UIColor.clear
default:
print("default")
sender.backgroundColor = UIColor.blue
}
} else {
sender.isSelected = true
switch indexPath?.row {
case 0:
print("0")
sender.backgroundColor = UIColor.blue
case 1:
print("1")
sender.backgroundColor = UIColor.blue
default:
print("default")
sender.backgroundColor = UIColor.clear
}
}
}
I'd do it this way:
Use custom UICollectionViewCell subclass (without button because collection view cell handles selection itself)
in this cell class override isSelected property like this:
override var isSelected: Bool {
didSet {
// set color according to state
self.backgroundColor = self.isSelected ? .blue : .clear
}
}
In class which controls your collectionView perform collectionView.allowsMultipleSelection = true
In your UICollectionViewDelegate implement method (which will prevent selection of more than two cells at a time):
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return (collectionView.indexPathsForSelectedItems?.count ?? 0) < 2
}
This way you don't need a button inside the cell.
When you select a cell, isSelected will be set. And you can customise your cell like this.
class YourCollectionViewCell: UICollectionViewCell {
override var isSelected: Bool {
didSet {
self.contentView.backgroundColor = isSelected ?.blue : .clear
}
}
}
NB: No need to add actions manually. Remove your selector method

How can i stop my tableView cell from reusing the button inside it?

I have a function inside a protocol that takes a TableViewcell as an argument.
protocol GoingButtonDelegate {
func goingButtonPressed(cell: TableViewCell)
}
class TableViewCell: UITableViewCell {
// Outlets
#IBOutlet weak var goingButton: UIButton!
var delegate: GoingButtonDelegate?
#IBAction func goingButtonTapped(_ sender: Any) {
delegate?.goingButtonPressed(cell: self)
}
I then go over to my ViewController and implement the delegate and it's function, which is to change the image of a button when tapped. The "goingSelected" is a green image and the "goingDeselected" is a red image.
This all works out fine, when tapped the button of a cell goes from red to green and vice versa. However, when the cell gets reused, the button state of the cell persists and gets reused for the new row that enters view. Is there any way to stop this from happening?
extension ViewController: GoingButtonDelegate {
func goingButtonPressed(cell: TableViewCell) {
cell.goingButton.isSelected = !cell.goingButton.isSelected
if cell.goingButton.isSelected == true {
cell.goingButton.setImage(UIImage(named: "goingSelected"), for: UIControlState.selected)
} else if cell.goingButton.isSelected == false {
cell.goingButton.setImage(UIImage(named: "goingDeselected"), for: UIControlState.normal)
}
}
}
It's possible
just replace
let cell = tableView.dequeueReusableCell(withIdentifier: identifier,
for: indexPath)
with:
let cell= Bundle.main.loadNibNamed(identifier, owner: self, options: nil)?[0]
but I think you need to change your app logic.
Set Images inside of your cell class
class TableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
self.goingButton.setImage(UIImage(named: "goingDeselected"), for:.normal)
self.goingButton.setImage(UIImage(named: "goingSelected"), for:.selected)
}
}
and in method goingButtonPressed(cell: TableViewCell) change cell to your object
and just set Bool type true or false
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.goingButton.isSelected = object.isSelected
...
}
You need to store the selected rows in an array of index paths, before that I think you should make few enhancements ... or a lot!!
the cell itself should handle it's button, the controller should just keep track of all cells status.
Add these two properties to your cell
class TableViewCell: UITableViewCell {
var indexPath:IndexPath?
var isSelected : Bool = false {
didSet{
if isSelected {
cell.goingButton.setImage(UIImage(named: "goingSelected"), for: UIControlState.normal)
} else {
cell.goingButton.setImage(UIImage(named: "goingDeselected"), for: UIControlState.normal)
}
}
}
// Outlets
#IBOutlet weak var goingButton: UIButton!
var delegate: GoingButtonDelegate?
#IBAction func goingButtonTapped(_ sender: Any) {
self.isSelected = !self.isSelected
delegate?.goingButtonPressed(cell: self)
}
..
...
}
And store the selected cells in your view controller to keep track of each cell status.
extension ViewController: GoingButtonDelegate {
var selectedCells = NSMutableArray()
func goingButtonPressed(cell: TableViewCell) {
if cell.isSelected {
selectedCells.add(cell.indexPath)
} else {
selectedCells.remove(cell.indexPath)
}
}
}
and in your "cell for row" method just add a small change
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellIdentifier") as! TableViewCell
cell.indexPath = indexPath
cell.isSelected = selectedCells.contains(indexPath)
..
...
return cell
}

Resources