Search code examples
uitableviewdelegatessegueswift5swift-protocols

how to pass data in perform segue sender through protocol & delegate from a UICollectionViewCell inside UITableViewCell?


  1. I have VC(a) with TableView
  2. Inside the UITableViewCell.xib I installed a UICollectionView
  3. UICollectionViewCell registered inside the UITableViewCell.xib of course.
  4. I want to perform segue from VC(a) to VC(b) when UICollectionViewCell didSelected
  5. And since the UICollectionViewCell is registered inside the UITableViewCell.xib so I can't use performSegue(withIdentifier: String, sender: Any) inside the .xib file because it is not inherited from UIViewController

I did some searches in StackOverFlow and I found methods to perform segue by creating a custom delegator protocol I applied the protocol and everything seems OK except the sender!!

I know how to use it inside VC without protocol but I don't know how to use it after applying the protocol

I'm new to Swift Language and so please help me to complete my code

Here below is the UITableViewCell.xib code

protocol MyCustomCellDelegator {
    func cellWasPressed()
}


class AdminPList_TableViewCell: UITableViewCell {
    
    var delegate: MyCustomCellDelegator?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        setupCell()
        getFProducts()
    }
    
    @IBOutlet weak var CollectionView: UICollectionView!
    
    func getFProducts() {
        productAPI.GetAllproducts { (appendThisProduct) in
            self.FP_Array.append(appendThisProduct)
            self.CollectionView.reloadData()
        }
    }
    
    var FP_Array : [productObject] = []    
}

extension AdminPList_TableViewCell : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
    func setupCell() {
        CollectionView.delegate = self ; CollectionView.dataSource = self

        CollectionView.register(UINib(nibName: "FproductsCell", bundle: nil), forCellWithReuseIdentifier: "FPcell")

//      CollectionView.register(UINib(nibName: "TproductsCell", bundle: nil), forCellWithReuseIdentifier: "TPcell")
        
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        return CGSize(width: self.CollectionView.frame.size.width-10, height: self.CollectionView.frame.size.height)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {  // make spacing between each cell
        return 10
    }

    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        return FP_Array.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let FPcell = CollectionView.dequeueReusableCell(withReuseIdentifier: "FPcell", for: indexPath) as! FproductsCell
        
        FPcell.UpdateFP_cell_Content(RetrivedProducts: FP_Array[indexPath.row])
        
        return FPcell
    }
    
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        let selectedProduct = FP_Array[indexPath.row] // I want to pass it to sender in perform segue un ProductsLibrary Class

//      print (selectedProduct.productName)
        self.delegate?.cellWasPressed()
        
    }
}

Help me to pass selectedProduct to perform segue in below protocol

And here below is the VC(a) where the UITableView is installed:

class ProductsLibrary : UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupCell()
    }
    
    @IBOutlet weak var TableView: UITableView!

    
}



extension ProductsLibrary : UITableViewDelegate, UITableViewDataSource, MyCustomCellDelegator {
    
    func cellWasPressed(withData:  productObject) {
        performSegue(withIdentifier: "EditProduct", sender: self)
        
    }
    
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let next = segue.destination as? ProductManagement{
            print ("Editing Product is Active")

              let product = sender as? productObject
                print(product) //it shows nil !!!
                next.EditingProduct = product
        }
    }
    
    func setupCell() {
        TableView.delegate = self ; TableView.dataSource = self
        
        TableView.register(UINib(nibName: "AdminPList_TableViewCell", bundle: nil), forCellReuseIdentifier: "PLcell")
    }
    
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                    
        return 3
    }
    
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                        
        let cell = tableView.dequeueReusableCell(withIdentifier: "PLcell", for: indexPath) as! AdminPList_TableViewCell
            
        cell.delegate = self
        
        
            return cell
    }
    
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        return self.TableView.frame.size.height/3
    }
    

    
    
}

Solution

  • When I started learning the delegation pattern I always made some mistakes. So I make a rule for myself. When you have to implement a delegate always remind 6 steps.

    The First 3 steps are for the class that will pass the data or write protocol, here your AdminPList_TableViewCell is that class. The 3 steps are

    1. Write a protocol (a bunch of method declaration) // you did it.
    2. Declare a protocol variable inside the class that will pass the data. // you did that too (var delegate: MyCustomCellDelegator?)
    3. Call the methods you declared. // you did that (self.delegate?.cellWasPressed())

    The Last 3 steps are for the class that will conform that protocol, here ProductsLibrary is that class.

    1. Conform that protocol in that class which will implement those methods. // here you did that (extension ProductsLibrary: ..., MyCustomCellDelegator)

    2. Assign delegate to self, but what is self here? well, self is the ProductsLibrary which has been delegated! // you missed that

    3. Implement the protocol methods. // you did that :)

    How to solve this?

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                        
        ...
        cell.delegate = self // you missed this line
        ...
        return cell
    }
    

    Now for passing selectedProduct just change the protocol method definition everywhere.

    protocol MyCustomCellDelegator {
        func cellWasPressed(withData: productObject)
    }
    

    Then call from the didSelectItemAt method

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
        let selectedProduct = FP_Array[indexPath.row] // I want to pass it to sender in perform segue un ProductsLibrary Class
        self.delegate?.cellWasPressed(withData: selectedProduct)
            
    }
    

    Now use it inside the method body.

    func cellWasPressed(withData data: productObject) {
        //now use the data and write your necessary code.
        performSegue(withIdentifier: "EditProduct", sender: self)
    
    }
    

    Hope it will help you :).