0

I am using collectionView to display photos and delegate functions to view custom alert. On photos I have cross mark which is for delete photo. My delegate functions and display items all working fine. But I am getting Issue when I have to delete photo from server. Because I need to pass exact image ID to web service to remove it from server. If I use cell.tag thing it gives me row value which is 1 but actual imgID is 40992. How can I pass this value to my delete delegate function?

Structure:

cell items display --tap gesture call removeImage func --- custom alert -- on Delete button -- didDeleteButtonClicked called.

Main Value which I need in cellForItem:

let imgId = AppData?.imageList?[indexPath.row].projectUnitImageId

PhotoViewController:

public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellName, for: indexPath) as! PhotoCollectionViewCell

            if(indexPath.row < (AppData?.imageList?.count ?? 0)){
                cell.imageView.isHidden = false
                cell.closeIcon.isHidden = false
                cell.addIcon.isHidden = true
                let dic = AppData?.imageList?[indexPath.row].url ?? " "
                cell.imageView.image =  UIImage(url: URL(string: dic))


                let imgId = AppData?.imageList?[indexPath.row].projectUnitImageId
                print(imgId)

                cell.closeIcon.isUserInteractionEnabled = true
                cell.closeIcon.tag = imgId ?? 0
                deleteAlertView.delegate = self
                let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(removeImage(_:)))
                cell.closeIcon.addGestureRecognizer(tapGestureRecognizer)

            } else {
                cell.imageView.isHidden = true
                cell.closeIcon.isHidden = true
                cell.addIcon.isHidden = false
            }

            return cell
        }


        @objc func removeImage(_ sender: AnyObject){

            print(imgId)
            let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
            let alertView = fotoXib?.first as! FotoDeleteAlert
            alertView.delegate = self
            self.view.addSubview(alertView)

        }


          //MARK: - Delegate Function

extension PhotoCollectionViewController: handleDeleteAction {

        func didDeleteButtonClicked(_ sender: UIButton) {

            print("delegate")
            let row = sender.tag
            print(row)

            // I have to call my delete webServices here and have to pass imgId
            deleteWebservices(imgId)


        }
    }

FotoAlert Xib custom alert:

protocol handleDeleteAction {
    func didDeleteButtonClicked(_: UIButton)
}

@IBDesignable class FotoDeleteAlert: UIView {

    var delegate: handleDeleteAction?

    @IBOutlet weak var deleteBtn: UIButton!

    override func awakeFromNib() {
        super.awakeFromNib()
        layoutIfNeeded()
        deleteBtn.addTarget(self, action: #selector(didDelete(_:)), for: .touchUpInside)
    }

    @IBAction func didCancel(_ sender: Any) {
        removeFromSuperview()
    }

    @IBAction func didDelete(_ sender: UIButton) {

        self.delegate?.didDeleteButtonClicked(sender)
        removeFromSuperview()


    }
}
Newbie
  • 360
  • 3
  • 19
  • please add this line and check propar indexpath row cell.deleteBtn.tag = indexpath.row – Himanshu Patel Dec 24 '19 at 11:46
  • cells are reusable so you cannot delete with cell tag because limited number of cell created those are visible on screen and next time cell reuse so if there are only 10 cell visible so you get maximum cell tag is 9 even items are in millions. – Matloob Hasnain Dec 24 '19 at 11:49

3 Answers3

1

You need

guard let id = AppData?.imageList?[row].projectUnitImageId else { return }  
deleteWebservices(id)
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
1

Tags are a fragile way to find the particular cell that a user interacts with. Instead, I suggest using the coorindates of the buttons.

I wrote an answer to this thread: swift: how to get the indexpath.row when a button in a cell is tapped?

In that answer I wrote an extension to UITableView, indexPathForView(_:) That function takes a view (which is contained by the table view cell ) and returns the IndexPath of the cell that contains that view.

You can use that exact same approach for collection views. Table views have the function indexPathForRow(at:) and collection views have the equivalent function indexPathForItem(at:).

The extension for a UICollection view would look something like this: (not tested)

import UIKit

public extension UICollectionView {

    /**
     This method returns the indexPath of the cell that contains the specified view
     - Parameter view: The view to find.
     - Returns: The indexPath of the cell containing the view, or nil if it can't be found
     */

    func indexPathForView(_ view: UIView) -> IndexPath? {
        let center = view.center

        //The center of the view is a better point to use, but we can only use it if the view has a superview
        guard let superview = view.superview else {
            //The view we were passed does not have a valid superview.
            //Use the view's bounds.origin and convert from the view's coordinate system
            let origin = self.convert(view.bounds.origin, from: view)
            let indexPath = self.indexPathForItem(at: origin)
            return indexPath
        }
        let viewCenter = self.convert(center, from: superview)
        let indexPath = self.indexPathForItem(at: viewCenter)
        return indexPath
    }
}

Refactor your FotoDeleteAlert to have an imgID property. Have its didDeleteButtonClicked method pass back the image ID, not the tapped button:

protocol handleDeleteAction {
    func didDeleteButtonClickedForImageID(_: Integer)
}

Then you would need to rewrite your removeImage(_:) function to take the gesture recognizer and use that to find the IndexPath:

    @objc func removeImage(_ tapper: UIGetstureRecognizer) {
        //Find the view associated with the tap gesture recognizer
        guard let view = tapper.view, 
        //use the view to find the indexPath of the cell
        let indexPath = collectionView. indexPathForView(view) else { 
            return 
        }
        let imgId = AppData?.imageList[indexPath.row].projectUnitImageId
        let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
        let alertView = fotoXib?.first as! FotoDeleteAlert
        alertView.delegate = self

        //Pass the image ID to the FotoDeleteAlert
        alertView.imgID = imgID
        self.view.addSubview(alertView)
    }

And then in your delete handler for the FotoDeleteAlert, you can fetch the image ID and use that to issue the delete command to your server.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
1

TL;DR;

You set tag of cell.closeIcon but then you use tag of FotoDeleteAlert button.

To fix that you need to add


class FotoDeleteAlert: ... {
   ...

   func setButtonTag(imageId: Int) {
       button.tag = imageId
   }
}


 @objc func removeImage(_ sender: UIView){

    print(imgId)
    let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
    let alertView = fotoXib?.first as! FotoDeleteAlert
    alertView.setButtonTag(imageId: sender.tag
    alertView.delegate = self
    self.view.addSubview(alertView)
}


extension PhotoCollectionViewController: handleDeleteAction {

        func didDeleteButtonClicked(_ sender: UIButton) {

            print("delegate")
            let imgId = sender.tag


            // I have to call my delete webServices here and have to pass imgId
            deleteWebservices(imgId)


        }
    }

Now let's cleanup your spaghetti code

Most of your collectionView(_:, cellForItemAt: ) can be moved into PhotoCollectionViewCell. I wouldn't send id via tag, instead you can create own delegate

Finally I would rewrite it into something like:

class Controller {
    func displayAlert(for info: PhotoInfo) {
        // I bet this code is invalid (view isn't correctly aligned

        let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
        let alertView = fotoXib?.first as! FotoDeleteAlert
        alertView.delegate = self
        self.view.addSubview(alertView)
    }
}

extension Controller: UICollectionViewDelegate {
    func collectionView(
        _ collectionView: UICollectionView, 
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: cellName, for: indexPath
        ) as! PhotoCollectionViewCell

        let imgInfo: PhotoInfo? = AppData?.imageList?[indexPath.row]
        cell.display(info: imgInfo)
        cell.delegate = self

        return cell
    }
}

extension Controller: PhotoCollectionViewCellDelegate {
    func photoCollectionViewCell(didPressCloseBtn cell: PhotoCollectionViewCell) {
        guard let indexPath = collectionView.indexPath(for: cell) else { return }
        if let imgInfo: PhotoInfo = AppData?.imageList?[indexPath.row] {
            displayAlert(for: imgInfo)
        }
    }
}

extension Controller: FotoDeleteAlertDelegate {
    func fotoDeleteAlert(
        didPressDelete view: FotoDeleteAlert, for item: PhotoInfo?
    ) {
        guard let item: PhotoInfo = item else { return }
        deleteWebservices(item.projectUnitImageId)
    }

}

protocol PhotoCollectionViewCellDelegate: class {
    func photoCollectionViewCell(didPressCloseBtn: PhotoCollectionViewCell)
}

class PhotoCollectionViewCell: UICollectionViewCell {
    weak var delegate: PhotoCollectionViewCellDelegate?

    var closeIcon: UIButton! {
        didSet {
            button.addTarget(
                self, action: #selector(closeTap), for: .touchUpInside
            )
        }
    }


    func display(info: PhotoInfo?) {
        imageView.isHidden = info == nil
        closeIcon.isHidden = info == nil
        addIcon.isHidden = info != nil

        if let info = info {
            imageView.image =  UIImage(url: URL(string: info.url))
        }
    }

    @objc func closeTap() {
        delegate?.photoCollectionViewCell(didPressCloseBtn: self)
    }

}

protocol FotoDeleteAlertDelegate: class {
    func fotoDeleteAlert(
        didPressDelete view: FotoDeleteAlert, for item: PhotoInfo?
    )

}

class FotoDeleteAlert {
    weak var delegate: FotoDeleteAlertDelegate?

    var deleteButton: UIButton! {
        didSet {
            button.addTarget(
                self, action: #selector(deleteTap), for: .touchUpInside
            )
        }
    }

    private var item: PhotoInfo?

    func display(item: PhotoInfo) {
        self.item = item
    }

    @objc func deleteTap() {
        delegate?.fotoDeleteAlert(didPressDelete: self, for: item)
    }
}

Pikacz
  • 431
  • 1
  • 5
  • 10