具有自定义流布局的 UICollectionView - 在具有活动平移手势的情况下滚动时单元格出现故障
Posted
技术标签:
【中文标题】具有自定义流布局的 UICollectionView - 在具有活动平移手势的情况下滚动时单元格出现故障【英文标题】:UICollectionView with custom flow layout - cells glitching when scrolling while having an active pan gesture 【发布时间】:2019-05-04 05:30:30 【问题描述】:所以我正在构建自己的拖放系统。为此,我需要一种方法来在collectionViews
中的单元格之间创建“间隙”,用户悬停拖动的项目。
我现在正在尝试一些东西,并获得了一个基本演示,它使用移动单元格的自定义流布局。
我做的演示包括一个简单的collectionView
(使用IGListKit
,但这对这个问题无关紧要,我很确定)和一个UIPanGestureRecognizer
,它允许你平移collectionView来创建手指下方的空隙。
我通过在每次pan gesture reconizer
更改时使布局无效来实现这一点。它是这样工作的,但是当我在平移collectionView
时同时滚动时,单元格似乎有点小故障。看起来是这样的(看起来单元格的渲染跟不上):
我很确定问题出在包含此调用的 makeAGap
function 中:
collectionView?.performBatchUpdates(
self.invalidateLayout()
self.collectionView?.layoutIfNeeded()
, completion: nil)
如果我不对失效设置动画,像这样
self.invalidateLayout()
self.collectionView?.layoutIfNeeded()
故障根本没有出现。它与动画有关。你有什么想法?
谢谢
PS:这是代码(还有更多 IGListKit
的东西,但这并不重要):
class MyCustomLayout: UICollectionViewFlowLayout
fileprivate var cellPadding: CGFloat = 6
fileprivate var cache = [UICollectionViewLayoutAttributes]()
fileprivate var contentHeight: CGFloat = 300
fileprivate var contentWidth: CGFloat = 0
var gap: IndexPath? = nil
var gapPosition: CGPoint? = nil
override var collectionViewContentSize: CGSize
return CGSize(width: contentWidth, height: contentHeight)
override func prepare()
// cache contains the cached layout attributes
guard cache.isEmpty, let collectionView = collectionView else
return
// I'm using IGListKit, so every cell is in its own section in my case
for section in 0..<collectionView.numberOfSections
let indexPath = IndexPath(item: 0, section: section)
// If a gap has been set, just make the current offset (contentWidth) bigger to
// simulate a "missing" item, which creates a gap
if let gapPosition = self.gapPosition
if gapPosition.x >= (contentWidth - 100) && gapPosition.x < (contentWidth + 100)
contentWidth += 100
// contentWidth is used as x origin
let frame = CGRect(x: contentWidth, y: 10, width: 100, height: contentHeight)
contentWidth += frame.width + cellPadding
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = frame
cache.append(attributes)
public func makeAGap(at indexPath: IndexPath, position: CGPoint)
gap = indexPath
self.cache = []
self.contentWidth = 0
self.gapPosition = position
collectionView?.performBatchUpdates(
self.invalidateLayout()
self.collectionView?.layoutIfNeeded()
, completion: nil)
//invalidateLayout() // Using this, the glitch does NOT appear
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
// Loop through the cache and look for items in the rect
for attributes in cache
if attributes.frame.intersects(rect)
visibleLayoutAttributes.append(attributes)
return visibleLayoutAttributes
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
return cache[indexPath.item]
class ViewController: UIViewController
/// IGListKit stuff: Data for self.collectionView ("its cells", which represent the rows)
public var data: [String]
return (0...100).compactMap "\($0)"
/// This collectionView will consist of cells, that each have their own collectionView.
private lazy var collectionView: UICollectionView =
let layout = MyCustomLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor(hex: 0xeeeeee)
adapter.collectionView = collectionView
adapter.dataSource = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.frame = CGRect(x: 0, y: 50, width: 1000, height: 300)
return collectionView
()
/// IGListKit stuff. Data manager for the collectionView
private lazy var adapter: ListAdapter =
let adapter = ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
return adapter
()
override func viewDidLoad()
super.viewDidLoad()
_ = collectionView
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
pan.maximumNumberOfTouches = 1
pan.delegate = self
view.addGestureRecognizer(pan)
@objc private func handlePan(pan: UIPanGestureRecognizer)
guard let indexPath = collectionView.indexPathForItem(at: pan.location(in: collectionView)) else
return
(collectionView.collectionViewLayout as? MyCustomLayout)?.makeAGap(at: indexPath, position: pan.location(in: collectionView))
extension ViewController: ListAdapterDataSource, UIGestureRecognizerDelegate
func objects(for listAdapter: ListAdapter) -> [ListDiffable]
return data as [ListDiffable]
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController
return RowListSection()
func emptyView(for listAdapter: ListAdapter) -> UIView?
return nil
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
return true
【问题讨论】:
【参考方案1】:好的,这一直在发生。我花了好几个小时试图解决一个问题,放弃在这里提出问题,几分钟后我找到了解决方案。
所以,我无法完全解释为什么会出现这种故障,但它似乎与屏幕的绘制有关。
我添加了一个CADisplayLink
以使布局失效与屏幕的刷新率同步,现在故障已经消失(感兴趣的人可以使用下面的代码 sn-p)。
但是,我很想知道那里到底发生了什么,以及为什么同步失效会修复绘图故障。我也会调查它,但我没有那么有经验,所以如果任何人都知道这样的事情,我非常感谢这个问题的新(更详细)答案:)
override func viewDidLoad()
super.viewDidLoad()
_ = collectionView
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
pan.maximumNumberOfTouches = 1
pan.delegate = self
view.addGestureRecognizer(pan)
let displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink.add(to: .current, forMode: .common)
var indexPath: IndexPath? = nil
var position: CGPoint? = nil
@objc private func update()
if let indexPath = self.indexPath, let position = self.position
(collectionView.collectionViewLayout as? MyCustomLayout)?.makeAGap(at: indexPath, position: position)
@objc private func handlePan(pan: UIPanGestureRecognizer)
guard let indexPath = collectionView.indexPathForItem(at: pan.location(in: collectionView)) else
return
// buffer the values
self.indexPath = indexPath
position = pan.location(in: collectionView)
【讨论】:
以上是关于具有自定义流布局的 UICollectionView - 在具有活动平移手势的情况下滚动时单元格出现故障的主要内容,如果未能解决你的问题,请参考以下文章