Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Popover attached to cell in UICollectionView moving when is called the ReloadData of this collection view

I have a UICollectionView that the user can clicks and it shows a popover attached to the cell clicked. In iOS 12, the popover stayed in the same origin (x,y or cell) if the reload data was called or not, but in iOS 13, the popover moves between the cells when the reload data of the collection view is called.

Follow a video of the behavior in iOS 13: https://vimeo.com/385021234

The presentation is done using the following code:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let cell = collectionView.cellForItem(at: indexPath) else {
        return
    }

    presentPopover(from: self, cell: cell)
}

func presentPopover(from view: UIViewController, cell: UIView) {
    let popoverView = PopoverViewController(nibName: "PopoverViewController", bundle: nil)
    let popover: UIPopoverPresentationController = popoverView.popoverPresentationController!

    popover.sourceRect = cell.bounds
    popover.sourceView = cell

    view.present(popoverView, animated: true, completion: nil)
}

And the PopoverViewController is using the modalPresentationStyle = .popover

Anyone had this issue before in iOS 13? Our application was working fine in iOS 11 and 12.

I have an example of this behavior in the following git repository: https://github.com/diegodossantos95/UICollectionViewPopoverBug

Steps to reproduce in the repository:

  1. Click on a collection cell

  2. Popover opens

  3. Wait for the reload data

Thanks,

Diego.

like image 208
Diego Dos Santos Avatar asked Nov 25 '25 05:11

Diego Dos Santos


1 Answers

Instead of attaching the popover to a cell who you can guarantee won't be dequeued, you could use a temporary UIView created from the cell's frame and then attach the popover to that. Here's the relevant code to accomplish this.

class ViewController: UIViewController {
    var timer = Timer()

    var popOverTempView = UIView()

    @IBOutlet weak var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(reloadData), userInfo: nil, repeats: true)
        self.collectionView.addSubview(popOverTempView)
        popOverTempView.isHidden = true
        popOverTempView.backgroundColor = .clear
    }

}

extension ViewController: UICollectionViewDelegate {

    func presentPopover(from view: UIViewController, cell: UIView) {
        let popoverView = PopoverViewController(nibName: "PopoverViewController", bundle: nil)
        let popover: UIPopoverPresentationController = popoverView.popoverPresentationController!

        popoverView.delegate = self

        popOverTempView.frame = cell.frame
        popOverTempView.isHidden = false
        popover.sourceRect = popOverTempView.bounds
        popover.sourceView = popOverTempView

        view.present(popoverView, animated: true, completion: nil)

    }

}

extension ViewController: PopoverViewControllerDelegate {
    func willDismissPopover() {
        popOverTempView.isHidden = true
    }
}

protocol PopoverViewControllerDelegate {
    func willDismissPopover()
}

class PopoverViewController: UIViewController {

    var delegate: PopoverViewControllerDelegate?

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        delegate?.willDismissPopover()
    }
}

Update 5/20/20:

I think I found a cleaner more direct fix. Instead of using a cell's bounds directly, convert it's contentView's frame to the collectionView's coordinate space, then use that as the sourceRect and present from the current view controller's view.

func presentPopover(from view: UIViewController, cell: UICollectionViewCell, indexPath: IndexPath) {
    let popoverView = PopoverViewController(nibName: "PopoverViewController", bundle: nil)
    let popover: UIPopoverPresentationController = popoverView.popoverPresentationController!

    var rect = cell.convert(cell.contentView.frame, to: collectionView)
    rect = collectionView.convert(rect, to: self.view)

    popover.sourceRect = rect
    popover.sourceView = self.view

    view.present(popoverView, animated: true, completion: nil)
}
like image 164
clawesome Avatar answered Nov 28 '25 01:11

clawesome



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!