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:
Click on a collection cell
Popover opens
Wait for the reload data
Thanks,
Diego.
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)
}