UIScrollView + LargeTitle (iOS 11) - прокрутите вверх и откройте большой заголовок

Я использую следующий код для прокрутки к верхней части UICollectionView:

scrollView.scrollRectToVisible(CGRect(origin: .zero, size: CGSize(width: 1, height: 1)), animated: true)

Однако в iOS 11 и 12 scrollView прокручивается только до самого верха, не раскрывая большого заголовка UINavigationBar (когда prefersLargeTitle Бен настроен на true.)

Вот как это выглядит:

Результат, которого я хочу достичь:

4 ответа

Решение

Работает так, как задумано, вы прокручиваете до положения y = 0назначьте controller быть UIScrollView делегировать и распечатать смещение прокрутки:

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
    print(scrollView.contentOffset)
}

Вы увидите, когда отображается Большой заголовок, и переместите представление прокрутки a, но оно вернется к Большому заголовку, которое не будет напечатано. (0.0, 0.0) но (0.0, -64.0) или же (0.0, -116.0) - это то же значение, что и scrollView.adjustedContentInset, поэтому, если вы хотите прокрутить вверх и отобразить большой заголовок, вы должны сделать:

scrollView.scrollRectToVisible(CGRect(x: 0, y: -64, width: 1, height: 1), animated: true)

Вы не хотите использовать какие-либо «магические значения» (как -64 в принятом на данный момент ответе). Они могут измениться (к тому же -64 все равно неверно).

Лучшее решение - наблюдать за изменениями SafeAreaInsets и сохранять самую большую верхнюю вставку. Затем используйте это значение в методе setContentOffset. Так:

      class UICollectioView {
    var biggestTopSafeAreaInset: CGFloat = 0
            
    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        self.biggestTopSafeAreaInset = max(ui.safeAreaInsets.top, biggestTopSafeAreaInset)
    }
    
    func scrollToTop(animated: Bool) {
        ui.scrollView.setContentOffset(CGPoint(x: 0, y: -biggestTopSafeAreaInset), animated: animated)
    }
}

Кажется, что использование отрицательного смещения контента — это правильный путь.

Мне очень нравится идея Demosthese отслеживать самую большую верхнюю вставку. Однако с этим подходом есть проблема. Иногда большие заголовки не могут отображаться, например, когда iPhone находится в ландшафтном режиме.

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

Усовершенствование этого метода заключается в том, чтобы рассматривать параметр bigTopSafeAreaInset только тогда, когда панель навигации может отображать большой заголовок. Теперь проблема состоит в том, чтобы понять, когда панель навигации может отображать большой заголовок. Я провел несколько тестов на разных устройствах, и кажется, что большие заголовки не отображаются, когда класс вертикального размера компактен.

Таким образом, решение Demos можно улучшить следующим образом:

      class TableViewController: UITableViewController {
    var biggestTopSafeAreaInset: CGFloat = 0
            
    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        self.biggestTopSafeAreaInset = max(view.safeAreaInsets.top, biggestTopSafeAreaInset)
    }
    
    func scrollToTop(animated: Bool) {
        if traitCollection.verticalSizeClass == .compact {
            tableView.setContentOffset(CGPoint(x: 0, y: -view.safeAreaInsets.top), animated: animated)
        } else {
            tableView.setContentOffset(CGPoint(x: 0, y: -biggestTopSafeAreaInset), animated: animated)
        }
    }
}

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

Если пользователь:

  1. Откройте приложение, повернув устройство в ландшафтный режим.
  2. Прокрутите представление.
  3. Поверните устройство в книжной ориентации.

В этот момент у метода bigTopSafeAreaInset еще не было возможности найти наибольшее значение, и если вызывается метод scrollToTop, большой заголовок отображаться не будет. К счастью, это тот случай, который не должен происходить часто.

Довольно поздно здесь, но у меня есть моя версия истории.

Начиная с iOS 11 есть вид прокрутки. Однако это отражает только текущее состояние пользовательского интерфейса, поэтому, если большой заголовок навигации не отображается, он не будет учитываться.

Итак, мое решение состоит в том, чтобы сделать пару дополнительных вызовов, чтобы система учитывала большой размер заголовка и вычисляла его:

      extension UIScrollView {
    func scrollToTop(animated: Bool = true) {
        if animated {
            // 1
            let currentOffset = contentOffset
            // 2
            setContentOffset(CGPoint(x: 0, y: -adjustedContentInset.top - 1), animated: false)
            // 3
            let newAdjustedContentInset = adjustedContentInset
            // 4
            setContentOffset(currentOffset, animated: false)
            // 5
            setContentOffset(CGPoint(x: 0, y: -newAdjustedContentInset.top), animated: true)
        } else {
            // 1
            setContentOffset(CGPoint(x: 0, y: -adjustedContentInset.top - 1), animated: false)
            // 2
            setContentOffset(CGPoint(x: 0, y: -adjustedContentInset.top), animated: false)
        }
    }
}

Вот что происходит:

Когдаanimated:

  1. Получите текущее смещение, чтобы иметь возможность применить его снова (важно для достижения анимации)
  2. Прокрутите без анимации до рассчитанного в данный момент плюс еще несколько, потому что большой заголовок не учитывался при расчете
  3. Теперь система учитывает большой заголовок, поэтому получите текущий, который будет включать его размер, поэтому сохраните его в константе, которая будет использоваться на последнем шаге.
  4. Вернитесь к исходному смещению без анимации , поэтому визуальные изменения не будут замечены.
  5. Прокрутите до ранее рассчитанной на этот раз анимации , чтобы добиться желаемой анимированной прокрутки.

Когда!animated:

  1. Прокрутите без анимации до плюса еще немного. На этом этапе система будет учитывать большой заголовок, поэтому...
  2. Прокрутите до текущегоadjustedContentInsetкак это было рассчитано с большим названием в нем

Вроде взлома, но работает.

Другие вопросы по тегам