skip to Main Content

I don’t know a great way to describe this, but basically when I click on a button in a table view made up of the same reusable cell to view products in a certain category it’s supposed to then load the page, but instead of loading the target action it loads two actions every time. So if I click Top Picks, it’ll load Recommended, then Top Picks. If I click Recommended, it loads New Releases, then Top Picks. It’s like each button is being triggered for two things at once. I’m not sure what’s going on.

The only things these buttons have (The Button see All) is an outlet that points to flashDealCell, so I’m confused.

Video of it
https://imgur.com/a/BqQD1sW

Storyboard Hierarchy
storyboard hierarchy

My view hierarchy for the reusable cell
view hierarchy
My code to register the actions

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.section == 0 {
            //for category
            let cell = tableView.dequeueReusableCell(withIdentifier: "SeeAllProductsCell", for: indexPath) as! SeeAllProductsCell
            cell.selectionStyle = .none
            return cell
            
        } else if indexPath.section == 1 {
            
            //for featured products
            
            let cell = tableView.dequeueReusableCell(withIdentifier: "flashDealCell", for: indexPath) as! flashDealCell
            collectionview_featured = cell.collectionView_flashDeal
            //    collectionview_others.tag = indexPath.section
            collectionview_featured.delegate = self
            collectionview_featured.dataSource = self
            cell.label_header.text = "Top Picks"
            cell.button_seeAll.addTarget(self, action: #selector(SeeAllFeaturedProduct), for: .touchUpInside)
            cell.selectionStyle = .none
            if featuredData.count == 0 {
                cell.label_noProduct.isHidden = false
            }else
            {
                cell.label_noProduct.isHidden = true
            }
            cell.view_separator.isHidden = true
            return cell
       
           
        } else if indexPath.section == 2 {
            //for new arrivals
            let cell = tableView.dequeueReusableCell(withIdentifier: "flashDealCell", for: indexPath) as! flashDealCell
            collectionview_newArrival = cell.collectionView_flashDeal
       //     collectionview_others.tag = indexPath.section
            collectionview_newArrival.delegate = self
            collectionview_newArrival.dataSource = self
             cell.label_header.text = "New Releases"
            cell.button_seeAll.addTarget(self, action: #selector(SeeAllNewArrivalsProduct), for: .touchUpInside)
            cell.selectionStyle = .none
            
            if newArrivalsData.count == 0 {
                cell.label_noProduct.isHidden = false
            }else
            {
                cell.label_noProduct.isHidden = true
            }
            cell.view_separator.isHidden = false
           
            
            return cell
            
        }else{
            //for flash deals
            let cell = tableView.dequeueReusableCell(withIdentifier: "flashDealCell", for: indexPath) as! flashDealCell
            
            collectionview_flashDeals = cell.collectionView_flashDeal
            // collectionview_others.tag = indexPath.section
            collectionview_flashDeals.delegate = self
            collectionview_flashDeals.dataSource = self
            cell.label_header.text = "Recommended"//"On Sale"
            cell.button_seeAll.addTarget(self, action: #selector(SeeAllFlashDealsProduct), for: .touchUpInside)
            cell.selectionStyle = .none
            
            if flashDealsData.count == 0 {
                cell.label_noProduct.isHidden = false
                
            }else
            {
                cell.label_noProduct.isHidden = true
            }
            cell.view_separator.isHidden = false
            return cell
        }
    }

My triggered actions

 @objc func SeeAllFlashDealsProduct()  {
        let vc = UIStoryboard.init(name: "Product", bundle: nil).instantiateViewController(withIdentifier: "FlashDealsVC") as! FlashDealsVC
        vc.headerText = "Recommended"//"On Sale"
        print("Clicked on Recommended")
        self.navigationController?.pushViewController(vc, animated: true)
        
    }
    @objc func SeeAllFeaturedProduct()  {
        //--
        let vc = UIStoryboard.init(name: "Product", bundle: nil).instantiateViewController(withIdentifier: "FlashDealsVC") as! FlashDealsVC
        vc.headerText = "Top Picks"
        print("Clicked on Top Picks")
        self.navigationController?.pushViewController(vc, animated: true)
    }
    @objc func SeeAllNewArrivalsProduct()  {
        let vc = UIStoryboard.init(name: "Product", bundle: nil).instantiateViewController(withIdentifier: "FlashDealsVC") as! FlashDealsVC
        vc.headerText = "New Releases"
        print("Clicked on New Releases")
        self.navigationController?.pushViewController(vc, animated: true)
        
    }

2

Answers


  1. Chosen as BEST ANSWER

    I fixed it with a friends help

    class flashDealCell : UITableViewCell{
        
        @IBOutlet weak var collectionView_flashDeal: UICollectionView!
        var index : Int!
        @IBOutlet weak var button_seeAll: UIButton!
        @IBOutlet weak var label_header: UILabel!
        @IBOutlet weak var label_noProduct: UILabel!
        @IBOutlet weak var view_separator: UIView!
        
        override func prepareForReuse() {
            self.button_seeAll.removeTarget(nil, action: nil, for: .touchUpInside)
            super.prepareForReuse()
        }
        
    }
    

    Adding the prepareForReuse helped make sure there was only one action per button, it's not the best way to do it but it works for right now


  2. This falls under the subject of "what you shouldn’t do in cellForRowAt.

    Adding a target / action to a UIButton does not remove any existing target / actions.

    Look at this simple example – we’ll create one button, and .addTarget three times for three different funcs:

    class BadTargetSetupVC: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            var cfg = UIButton.Configuration.filled()
            cfg.title = "Tap Me"
            let btn = UIButton(configuration: cfg)
            
            btn.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(btn)
            
            NSLayoutConstraint.activate([
                btn.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                btn.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            ])
            
            btn.addTarget(self, action: #selector(btnAction1(_:)), for: .touchUpInside)
            btn.addTarget(self, action: #selector(btnAction2(_:)), for: .touchUpInside)
            btn.addTarget(self, action: #selector(btnAction3(_:)), for: .touchUpInside)
        }
        
        @objc func btnAction1(_ sender: UIButton) {
            print("Action 1")
        }
        @objc func btnAction2(_ sender: UIButton) {
            print("Action 2")
        }
        @objc func btnAction3(_ sender: UIButton) {
            print("Action 3")
        }
    
    }
    

    If you run that, every time you tap the button you’ll see this in the debug console:

    Action 1
    Action 2
    Action 3
    

    Because cells in a table view are reused, and you are dequeuing the same cell class for sections 1, 2 and 3, each time a cell is reused you are adding another target / action to the button.

    This is one of the reasons you should design your cell class to handle the button tap internally, and use a closure (or protocol/delegate pattern) to allow the cell to inform the controller that the button was tapped.

    Your controller can then take the appropriate action, based on the indexPath of the cell.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search