当调用此集合视图的 ReloadData 时,附加到 UICollectionView 中的单元格的 Popover 移动

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

我有一个用户可以单击的 UICollectionView,它显示附加到单击的单元格的弹出窗口。在 iOS 12 中,无论是否调用重新加载数据,弹出窗口都保持在同一原点(x、y 或单元格),但在 iOS 13 中,当重新加载数据时,弹出窗口在单元格之间移动调用集合视图。

关注 iOS 13 中行为的视频: https://vimeo.com/385021234

演示使用以下代码完成:

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)
}

并且 PopoverViewController 正在使用 modalPresentationStyle = .popover

有人在 iOS 13 之前遇到过这个问题吗? 我们的应用程序在 iOS 11 和 12 中运行良好。

我在以下 git 存储库中有此行为的示例:https://github.com/diegodossantos95/UICollectionViewPopoverBug

在存储库中复制的步骤:

  1. 单击集合单元格

  2. 弹出窗口打开

  3. 等待重载数据

谢谢,

迭戈。

当您调用 reloadData 时,这会导致集合视图丢弃任何当前可见的项目(包括占位符)并根据数据源对象的当前状态重新创建项目。

Se重载数据前后cells frame的区别:

  - some : <UICollectionViewCell: 0x7fd8abd02190; frame = (73.5 10; 50 50); clipsToBounds = YES; hidden = YES; opaque = NO; layer = <CALayer: 0x600001b50e20>>
  - some : <UICollectionViewCell: 0x7fd8abd02190; frame = (519.5 10; 50 50); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x600001b50e20>>

如果您只需要更改集合中的一个或多个项目,请尝试使用:

func reloadItems(at: [IndexPath])

您可以使用从单元格的框架创建的临时 UIView,然后将弹出窗口附加到它,而不是将弹出窗口附加到您可以保证不会出队的单元格。这是实现此目的的相关代码。

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()
    }
}

20 年 5 月 20 日更新:

我想我找到了一个更清晰、更直接的修复方法。不要直接使用单元格的边界,而是将其 contentView 的框架转换为 collectionView 的坐标 space,然后将其用作 sourceRect 并从当前视图控制器的视图中呈现。

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)
}