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)
}
}
}
Еще есть случай, из-за которого большой заголовок не отображался после прокрутки.
Если пользователь:
- Откройте приложение, повернув устройство в ландшафтный режим.
- Прокрутите представление.
- Поверните устройство в книжной ориентации.
В этот момент у метода 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
:
- Получите текущее смещение, чтобы иметь возможность применить его снова (важно для достижения анимации)
- Прокрутите без анимации до рассчитанного в данный момент плюс еще несколько, потому что большой заголовок не учитывался при расчете
- Теперь система учитывает большой заголовок, поэтому получите текущий, который будет включать его размер, поэтому сохраните его в константе, которая будет использоваться на последнем шаге.
- Вернитесь к исходному смещению без анимации , поэтому визуальные изменения не будут замечены.
- Прокрутите до ранее рассчитанной на этот раз анимации , чтобы добиться желаемой анимированной прокрутки.
Когда!animated
:
- Прокрутите без анимации до плюса еще немного. На этом этапе система будет учитывать большой заголовок, поэтому...
- Прокрутите до текущего
adjustedContentInset
как это было рассчитано с большим названием в нем
Вроде взлома, но работает.