UICollectionView с пользовательским макетом потока - клетки прокручиваются при прокрутке при активном жесте панорамирования - PullRequest
1 голос
/ 04 мая 2019

, поэтому я создаю свою собственную систему Drag & Drop.Чтобы это работало, мне нужен способ создать «пробелы» между ячейками в collectionViews, где пользователь наводит перетаскиваемые элементы.

Я сейчас пробую материал и получил базовую демонстрацию, работающую с использованиемпользовательский макет потока, который перемещает ячейки вокруг.

Демонстрация, которую я сделал, состоит из простого collectionView (с использованием IGListKit, но это не имеет значения для этой проблемы, я уверен, я уверен) и UIPanGestureRecognizer, который позволяет вам перемещаться по collectionView, чтобы создать промежуток под вашим пальцем.

Я добиваюсь этого путем отмены макета при каждом изменении pan gesture reconizer.Это работает таким образом, но когда я одновременно прокручиваю по панораме collectionView, ячейки, кажется, немного сбиваются.Это выглядит так (похоже, что рендеринг ячеек не может идти в ногу):

enter image description here

Я почти уверен, что проблема в том,в функции makeAGap, которая содержит этот вызов:

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 Ответ

0 голосов
/ 04 мая 2019

Хорошо, это продолжается. Я часами пытаюсь решить проблему, отказываюсь задавать вопрос здесь, а через несколько минут я нахожу / a решение.

Итак, я не могу полностью объяснить, почему происходит этот сбой, но похоже, что это связано с рисованием экрана. Я добавил CADisplayLink для синхронизации аннулирования макета с частотой обновления экрана, и теперь ошибка исчезла (фрагмент кода ниже для всех, кто заинтересован).

Тем не менее, я хотел бы знать, что именно там происходит и почему синхронизация аннулирования устраняет сбой при рисовании. Я тоже собираюсь разобраться в этом, но я не настолько опытен, если кто-нибудь знает о таких вещах, как Буду очень признателен за новый (более подробный) ответ на этот вопрос:)

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