Search code examples
swiftuitableviewtagsprotocolsdelegation

use delegate to import photo to specific tableview cell


i want my swift code to use delegate to import a photo from photo gallery to a specific table view cell. What is going on right now is the photo is being imported to always to go the first cell. So if the user clicks the import button from the second cell the photo should go to the second cell. The tag is not working. I want to use delegation if possible.

import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,CustomTvCellDelegateadd, UIImagePickerControllerDelegate & UINavigationControllerDelegate {
    @objc func addCeller(_ customTV: CustomTv, location: CGPoint) {
        selectedIndexPath = IndexPath(row: 0, section: 0)
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = self
        imagePicker.sourceType = .photoLibrary
        present(imagePicker, animated: true, completion: nil)
        
      
    }
    
   
    var cellCount = 5
    var tableview = UITableView()
    var selectedIndexPath = IndexPath(row: 0, section: 0)


    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let selectedImage = info[.originalImage] as? UIImage else {
            fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
        }
        guard let cell = tableview.cellForRow(at: selectedIndexPath) as? CustomTv else { return }
        cell.pic.image = selectedImage
        tableview.reloadData()
        
        dismiss(animated: true, completion: nil)
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cellCount // use a variable here so you can change it as you delete cells, else it will crash
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 118
    }
   
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTv
        cell.addd.tag = indexPath.row
        cell.delegatef = self
     
    
      cell.addd.addTarget(self, action: #selector(addCeller), for: .touchDown)
        return cell
    }
    

 
    
    override func viewDidLoad() {
        super.viewDidLoad()

        tableview.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height * 0.8)
        
        
        view.addSubview(tableview)

 
        tableview.register(CustomTv.self, forCellReuseIdentifier: "cell")
        tableview.delegate = self
        tableview.dataSource = self
      
    }
    
}

// you need to declare a cell delegate that will let your tableview know when the button was tapped on the cell


protocol CustomTvCellDelegateadd: AnyObject {
    func addCeller(_ customTV: CustomTv, location: CGPoint)
}
class CustomTv: UITableViewCell { // it's good form to Pascal case your class name ;)
    
    
    weak var delegatef: CustomTvCellDelegateadd?
    lazy var backView : UIView = {
        let view = UIView(frame: CGRect(x: 10, y: 6, width: self.frame.width  , height: 110))
        view.backgroundColor = .green
        
        return view
    }()


    
    lazy var addd : UIButton = {
        let btn = UIButton(frame: CGRect(x: 250-25, y: 0, width: 100 , height: 55))
        btn.backgroundColor = .brown
        btn.setTitle("Add", for: .normal)
        btn.titleLabel?.textAlignment = .center
        return btn
        
    }()


        lazy var pic : UIImageView = {
            let btn = UIImageView(frame: CGRect(x: 50-25, y: 6, width: 100 , height: 110))
            btn.backgroundColor = .systemPink
    
            return btn
    
        }()
 
   
    override func layoutSubviews() {
        backView.clipsToBounds = true
        backView.frame =  CGRect(x: 0, y: 6, width: bounds.maxX  , height: 110)
        
        // moved these calls here instead of on setSelected(_selected:animated:)
        addSubview(backView)

        backView.addSubview(addd)
   
        backView.addSubview(pic)
        
        

        
 
      
    }
    

}

Solution

  • Issue:

    selectedIndexPath = IndexPath(row: 0, section: 0)
    

    Hardcoded indexPath value in addCeller method

    Solution:

    You need a bit of restructuring of code.

    1. capture the button selector in the cell itself, so you can return / pass self as an argument to protocol method. This isnt a very ideal solution (as you are only interested in index path and not the cell itself, but I am not very clear on your idea behind returning CustomTv as arguement in your protocol method, so am just keeping changes in sync with that decision)

    class CustomTv: UITableViewCell { // it's good form to Pascal case your class name ;)
        weak var delegatef: CustomTvCellDelegateadd?
        lazy var backView : UIView = {
            let view = UIView(frame: CGRect(x: 10, y: 6, width: self.frame.width  , height: 110))
            view.backgroundColor = .green
    
            return view
        }()
    
    
    
        lazy var addd : UIButton = {
            let btn = UIButton(frame: CGRect(x: 250-25, y: 0, width: 100 , height: 55))
            btn.backgroundColor = .brown
            btn.setTitle("Add", for: .normal)
            btn.titleLabel?.textAlignment = .center
            btn.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
            return btn
        }()
    
    
        lazy var pic : UIImageView = {
            let btn = UIImageView(frame: CGRect(x: 50-25, y: 6, width: 100 , height: 110))
            btn.backgroundColor = .systemPink
    
            return btn
    
        }()
    
    
        override func layoutSubviews() {
            backView.clipsToBounds = true
            backView.frame =  CGRect(x: 0, y: 6, width: bounds.maxX  , height: 110)
    
            // moved these calls here instead of on setSelected(_selected:animated:)
            addSubview(backView)
    
            backView.addSubview(addd)
    
            backView.addSubview(pic)
        }
    
        @objc func addButtonTapped() {
            self.delegatef?.addCeller(self)
        }
    }
    

    2. Now that you have CustomTv instance as a part of your method invocation, get indexpath directly by asking tableView

    func addCeller(_ customTV: CustomTv) {
            if let indexPath = self.tableview.indexPath(for: customTV) {
                selectedIndexPath = indexPath
            }
            let imagePicker = UIImagePickerController()
            imagePicker.delegate = self
            imagePicker.sourceType = .photoLibrary
            present(imagePicker, animated: true, completion: nil)
    }
    

    3. Now that you have already set button target, remove the statement from cellForRowAtIndexPath

    cell.addd.addTarget(self, action: #selector(addCeller), for: .touchDown)
    

    EDIT 1:

    As OP mentioned in comments below, these modification is resulting in compilation error, as I had modified the protocol method declaration and did not update it in the answer, hence editing to reflect the same.

    protocol CustomTvCellDelegateadd: AnyObject {
        func addCeller(_ customTV: CustomTv)
    }
    

    As I already explained in comments below, I wasn't sure the purpose of location argument in method (also if it was intended only to communicate the index of cell selected that is no longer needed with this solution) hence I have omitted the argument from declaration which lead to compilation error :)