Где я могу установить для contentOffset последний элемент в UICollectionView до появления представления? - PullRequest
0 голосов
/ 28 декабря 2018

Я прокручиваю несколько горизонтальных коллекций в последний (крайний правый) элемент, используя:

 timelineCollectionView.scrollToMaxContentOffset(animated: false)
   postsCollectionView.scrollToMaxContentOffset(animated: false)

, это прекрасно работает, за исключением того, что я не могу понять, где их разместить.

Различные места, которые я пробовал:

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

viewDidAppear - он отлично прокручивается.Потрясающие!За исключением того, что теперь вы можете видеть его прокрутку, даже если вы положили анимацию: ложьCollectionView загружается в крайнее левое положение в течение доли секунды перед обновлением в крайнее правое положение

viewDidLayoutSubviews - это работает отлично - время и все!Однако он вызывается много раз.Если бы я мог определить, какое подпредставление было только что выложено, возможно, я мог бы прокрутить его там, но я не уверен, как.

Есть ли лучший вариант?Как я могу это сделать?

Также вот функции, которые я использую для установки смещения содержимого:

extension UIScrollView {

    var minContentOffset: CGPoint {
        return CGPoint(
            x: -contentInset.left,
            y: -contentInset.top)
    }

    var maxContentOffset: CGPoint {
        return CGPoint(
            x: contentSize.width - bounds.width + contentInset.right,
            y: contentSize.height - bounds.height + contentInset.bottom)
    }

    func scrollToMinContentOffset(animated: Bool) {
        setContentOffset(minContentOffset, animated: animated)
    }

    func scrollToMaxContentOffset(animated: Bool) {
        setContentOffset(maxContentOffset, animated: animated)
    }
}

Ответы [ 4 ]

0 голосов
/ 11 января 2019

Кажется, лучшее место для этого - viewWillAppear, и теперь он работает.Обратите внимание, что мне пришлось добавить несколько вызовов:

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let postCount = posts.fetchedObjects?.count
        let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0)
        postsCollectionView.setNeedsLayout()
        postsCollectionView.layoutIfNeeded()
        postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) // you can also do setContentOffset here
}

Причина, по которой мой viewWillAppear раньше не работал, заключается в том, что он не знал contentSize моего collectionView и думал, что contentSize равен 0, поэтому scrollToItem иsetContentOffset никуда не перемещал.layoutIfNeeded () проверяет contentOffset и теперь, когда он ненулевой, он перемещает его на соответствующую величину.

Редактировать: Фактически, вы можете даже поместить это в ViewDidLoad, если хотите, чтобы он не запускался повторно, например, при добавлении и отклонении дополнительного представления.Ключ действительно в том, что команда layoutIfNeeded обращается к contentSize.

0 голосов
/ 01 января 2019

вы можете использовать executeBatchUpdate в viewdidLoad.

timelineCollectionView.reloadData()
timelineCollectionView.performBatchUpdates(nil, completion: {
    (result) in
     timelineCollectionView.scrollToMaxContentOffset(animated: false)
})

Второй вариант - вы можете поместить наблюдателя для представления коллекции ContentSize.

override func viewDidLoad() {
    super.viewDidLoad()
    timelineCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    timelineCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let observedObject = object as? UICollectionView, observedObject == timelineCollectionView {
        print("set ContentOffset here")
        timelineCollectionView.scrollToMaxContentOffset(animated: false)
    }
}
0 голосов
/ 02 января 2019

Поскольку вам нужен размер контента и границы вашего scrollView для вычисления целевого смещения контента, вам нужно выложить scrollView перед его настройкой.

viewDidLayoutSubviews тоже мне подходит.Он вызывается каждый раз, когда представление вашего контроллера представления только что изложило свои подпредставления (только его подпредставления, а не подпредставления его подпредставлений).Просто добавьте флаг, чтобы убедиться, что изменение сделано только один раз.Хак является довольно распространенным явлением, когда вы обрабатываете ротации или изменения в наборе признаков.

var firstTime = true

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    guard firstTime else { return }
    firstTime = false
    collectionView.scrollToMaxContentOffset(animated: false)
}

Есть ли лучший вариант?

  • viewWillAppear вызывается слишком рано.В настоящее время границы и размер содержимого неверны.

  • viewDidAppear вызывается слишком поздно.Жаль, макет сделан, хотя.Он вызывается после завершения анимации представления контроллера представления, поэтому смещение содержимого вашего представления прокрутки будет равно нулю в течение всей продолжительности анимации.

  • Взлом блоков завершения UIView.animation или executeBatchUpdates?Ну, это работает, но это совершенно нелогично.Я думаю, что это неправильное решение.

Кроме того, как указано в документации :

завершение : [...] Если продолжительность анимации равна 0, этот блок выполняется в начале следующего цикла цикла выполнения.

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

0 голосов
/ 31 декабря 2018

Есть ли лучший вариант?

Да, есть, и это будет UICollectionView.scrollToItem(at:at:animated:)

Далее нужно дождаться, пока ячейки не будут сняты, чтобы мы могли вызватьUICollectionView.scrollToItem(at:at:animated:), и вы можете сделать это несколькими способами.

Вы можете позвонить UICollectionView.reloadData в animateWithDuration:animations:completion, он работает как в viewDidLoad, так и viewWillAppear, прокрутка тоже не выглядит, например, так:

func scrollToItem(at index: Int) {
    UIView.animate(withDuration: 0.0, animations: { [weak self] in
        self?.collectionView.reloadData()
    }, completion: { [weak self] (finished) in
        if let count = self?.dataSource?.count, index < count {
            let indexPath = IndexPath(item: index, section: 0)
            self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
        }
    })
}

Другой способ - отправить блок кода в основную очередь после UICollectionView.reloadData, например:

func scrollToItem(at index: Int) {
    self.collectionView.reloadData()
    DispatchQueue.main.async { [weak self] in
        // this will be executed after cells dequeuing
        if let count = self?.dataSource?.count, index < count {
            let indexPath = IndexPath(item: index, section: 0)
            self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
        }
    }
}

Я также создал gist , где я использую горизонтальный UICollectionView, просто создайте новый проект xcode и замените ViewController.swift на исходный код gist.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...