Search code examples
iosswiftuitableviewuicollectionviewuicollectionviewcell

How can i write an extension for UIButton in UITableViewCell or UICollectionViewCell that only affects the cell which it is inside?


As you know when we declare a button inside a reusable cell it repeats the UI after 5 or 6 cells while scrolling. prepareToReuse() method only resets the UI but to keep changes on UI we need to declare a dictionary,

prepareToReuse() method only resets the UI, but; to keep changes on UI 1) we need to declare an array of dictionary 2) Fill that dictionary with default values as many as cell count we need 3) change the value of element at same index with our cell 4) while reusing cell make a check if we change default value or not

import UIKit

//defining a enum to track cell state

enum  CellState {
    case selected
    case unselected

    init() {
        self = .unselected
    }
}

class yourCell: UICollectionViewCell {

 @IBOutlet weak var yourBtn: UIButton!

// creating our func
 var buttonAction: (() -> ())?

 override func awakeFromNib() {
        super.awakeFromNib()
    }



@IBAction func sliderAction(_ sender: UISlider) {

    // defining our func where we want to use it
    buttonAction()
 }

}


// inside our viewController
import UIKit

class yourViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    @IBOutlet weak var collectionView: UICollectionView!

    // you data source that you fill the cells
    var yourDataArray: [yourDataSource]()

    // the array that will hold status of our cells
    private var  cellStateArray: [CellState]?


    override func viewDidLoad() {
        super.viewDidLoad()

        //confirming our delegate and dataSource
        collectionView.dataSource = self
        collectionView.delegate = self

       //filling your cellStateArray as many as your cell count
       self.cellStateArray = Array(repeating: .unselected, count: self.yourDataArray!.count )
    }


    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if yourDataArray?.isEmpty == false {
            return yourDataArray!.count
        } else {
            print("Caution yourDataArray IS EMPTY")
            return 0
        }
    }

 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "yourCell", for: indexPath) as! yourCell

// checking your button selected or not while reuse it (while scrolling)

     if  cellStateArray![indexPath.row] == .selected  {
            cell.yourBtn.backgroundColor = .red
        } else {
            cell.yourBtn.backgroundColor = .blue
       }


    cell.buttonAction = {

  //calling button function
       button_Select(_ sender: cell.yourBtn, cellStateArray: cellStateArray, indexPath: IndexPath)

 }

}


// Defining the function what you want to do with your button in each cell

func button_Select(_ sender: UIButton, cellStateArray: [CellState], indexPath: IndexPath ) {
        sender.isSelected = !sender.isSelected
        if sender.isSelected{
            sender.backgroundColor = .red

           //setting value when your button selected
            cellStateArray[indexPath.row] = .selected
        }
        else{
            sender.backgroundColor = .blue

          //setting value when your button unselected
            cellStateArray[indexPath.row] = .unselected
        }
     collectionView.reloadData()
}


}

I mentioned my own method also if someone needs to use it but as you see it is so long way. My question is can we define an extension for UIButton to do same job or is there a shorter and better way to use it. Tracking repeating buttons in reusableCells like "like Buttons, switches, check boxes, etc." are problem don't know why apple don't do something for it. I would be very happy if someone show me a better way. Thank you.


Solution

  • check this code i have consider your datasource as model not dictionary you can make it easily.

    class yourCell: UICollectionViewCell {
    
        @IBOutlet weak var yourBtn: UIButton!
    
        // creating our func
        var reloadData: (() -> ())?
        var model = yourDataSource()
    
        override func awakeFromNib() {
            super.awakeFromNib()
        }
    
        @IBAction func sliderAction(_ sender: UISlider) {
    
            self.model.isSelected = !self.model.isSelected
            reloadData?()
        }
    
        func setCellData(model:yourDataSource){
    
            self.model = model
    
            if  model.isSelected  {
                yourBtn.backgroundColor = .red
            } else {
                yourBtn.backgroundColor = .blue
            }
    
        }
    }
    
    
    // inside our viewController
    import UIKit
    
    class yourViewController: UIViewController {
    
        @IBOutlet weak var collectionView: UICollectionView!
    
        // you data source that you fill the cells
        var yourDataArray = [yourDataSource]() // considering this as model and has isSelected property (default false)
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            //confirming our delegate and dataSource
            collectionView.dataSource = self
            collectionView.delegate = self
    
            //fill your model here or pass data as u want
    
        }
    }
    
    extension yourViewController : UICollectionViewDelegate,    UICollectionViewDataSource{
    
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            if yourDataArray?.isEmpty == false {
                return yourDataArray!.count
            } else {
                print("Caution yourDataArray IS EMPTY")
                return 0
            }
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
            let model = self.yourDataArray[indexPath.row]
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "yourCell", for: indexPath) as! yourCell
    
            cell.setCellData(model: model)
            cell.reloadData = {
    
                //reload full collection view or single item 
                self.collectionView.reloadData()
            }
    
            return cell
        }
    }