Поведение UICollectionViewFlowLayout не определено, поскольку ширина ячейки больше ширины collectionView
2015-08-18 16:07:51.523 Пример [16070:269647] поведение UICollectionViewFlowLayout не определено, потому что: 2015-08-18 16:07:51.523
Пример [16070:269647] ширина элемента должна быть меньше ширины UICollectionView минус значения вставок раздела слева и справа, минус значения вставок содержимого слева и справа.
2015-08-18 16: 07: 51.524 Пример [16070:269647] Соответствующий экземпляр UICollectionViewFlowLayout есть и к нему прикреплен; анимации = {позиция =; bounds.origin=; bounds.size=; }; слой =; contentOffset: {0, 0}; contentSize: {1024, 770}> макет представления коллекции: . 2015-08-18 16:07:51.524 Пример [16070:269647] Создайте символическую точку останова в UICollectionViewFlowLayoutBreakForInvalidSizes, чтобы перехватить это в отладчике.
Это то, что я получаю, то, что я делаю,
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(self.collectionView!.frame.size.width - 20, 66)
}
когда я поворачиваюсь из пейзажа в портрет, консоль показывает это сообщение об ошибке только в iOS 9, кто-нибудь знает, что это происходит, и есть ли исправление для этого?
24 ответа
Это происходит, когда размер вашей коллекции изменяется на что-то менее широкое (например, перейдите из альбомного в портретный режим), и ячейка становится слишком большой для размещения.
Почему ячейка становится слишком большой, поскольку должна вызываться схема потока представления коллекции и возвращаться подходящий размер?
collectionView (collectionView: UICollectionView, макет collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath)
Обновление для включения Swift 4
@objc переопределяет func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { ... }
Это потому, что эта функция не вызывается или, по крайней мере, не сразу.
Что происходит, так это то, что ваш подкласс макета потока представления коллекции не переопределяет shouldInvalidateLayoutForBoundsChange
функция, которая returns false
по умолчанию.
Когда этот метод возвращает значение false, представление коллекции сначала пытается перейти к текущему размеру ячейки, обнаруживает проблему (которая регистрирует предупреждение), а затем вызывает макет потока для изменения размера ячейки.
Это означает 2 вещи:
1 - предупреждение само по себе не вредно
2 - вы можете избавиться от него, просто переопределив функцию shouldIn validateLayoutForBoundsChange для возврата true. В этом случае макет потока всегда будет вызываться при изменении границ представления коллекции.
Выбрать просмотр коллекции в раскадровке перейти к инспектору размеров изменить размер оценки на нет
XCode 11
Для меня причина была в том, что для параметра CollectionView Estimate было установлено значение Automatic. Кажется, это поведение по умолчанию в XCode 11.
Следовательно, для меня, когда я загружал изображения в ячейки, из-за размера изображений и автоматической опции (которая хочет адаптировать размер ячейки к размеру изображения) ширина ячейки стала больше, чем ширина CollectionView.
Если для этого параметра установлено значение Нет, проблема решена.
Я также видел, как это происходит, когда мы устанавливаем для параметра valuesItemSize значение AutomaticSize, а вычисленный размер ячеек составляет менее 50x50 (Примечание. Этот ответ был действителен во времена iOS 12. Возможно, в более поздних версиях его исправили).
т. е. если вы заявляете о поддержке ячеек с самоопределением размера, установив предполагаемый размер элемента в automaticSize
постоянная:
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
и ваши ячейки на самом деле меньше, чем 50x50 точек, тогда UIKit распечатывает эти предупреждения. К сожалению, ячейки в конечном итоге имеют соответствующий размер, и предупреждения кажутся безвредными.
Один из обходных путей исправления заключается в замене константы размером элемента 1x1:
flowLayout.estimatedItemSize = CGSize(width: 1, height: 1)
Это не влияет на поддержку самоконтроля, поскольку в качестве документации для estimatedItemSize
отмечает:
Установка его в любое другое значение заставляет представление коллекции запрашивать каждую ячейку для ее фактического размера, используя метод предпочитаемый ячейкой selectedLayoutAttributesFitting(_:).
Тем не менее, это может / не может иметь последствия для производительности.
Я решил эту проблему с помощью safeAreaLayoutGuide
,
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.safeAreaLayoutGuide.layoutFrame.width), height: 80);
}
и вам также нужно переопределить эту функцию для правильной поддержки портретного и ландшафтного режимов.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout();
}
Я использую подкласс UICollectionViewFlowLayout
и используя его itemSize
свойство для указания размера ячейки (вместо collectionView:sizeForItemAtIndexPath:
метод делегата). Каждый раз, когда я поворачиваю экран на более короткую ширину (например, пейзаж -> портрет), я получаю это огромное предупреждение.
Я смог это исправить, выполнив 2 шага.
Шаг 1: В UICollectionViewFlowLayout
подкласса prepareLayout()
функционировать, двигаться super.prepareLayout()
после где self.itemSize
установлено. Я думаю, что это делает супер класс, чтобы использовать правильный itemSize
значение.
import UIKit
extension UICollectionViewFlowLayout {
var collectionViewWidthWithoutInsets: CGFloat {
get {
guard let collectionView = self.collectionView else { return 0 }
let collectionViewSize = collectionView.bounds.size
let widthWithoutInsets = collectionViewSize.width
- self.sectionInset.left - self.sectionInset.right
- collectionView.contentInset.left - collectionView.contentInset.right
return widthWithoutInsets
}
}
}
class StickyHeaderCollectionViewLayout: UICollectionViewFlowLayout {
// MARK: - Variables
let cellAspectRatio: CGFloat = 3/1
// MARK: - Layout
override func prepareLayout() {
self.scrollDirection = .Vertical
self.minimumInteritemSpacing = 1
self.minimumLineSpacing = 1
self.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: self.minimumLineSpacing, right: 0)
let collectionViewWidth = self.collectionView?.bounds.size.width ?? 0
self.headerReferenceSize = CGSize(width: collectionViewWidth, height: 40)
// cell size
let itemWidth = collectionViewWidthWithoutInsets
self.itemSize = CGSize(width: itemWidth, height: itemWidth/cellAspectRatio)
// Note: call super last if we set itemSize
super.prepareLayout()
}
// ...
}
Обратите внимание, что вышеуказанное изменение каким-то образом изменит размер макета, когда вращение экрана перестанет работать. Это где шаг 2 входит.
Шаг 2: Поместите это в контроллер представления, который содержит представление коллекции.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
collectionView.collectionViewLayout.invalidateLayout()
}
Теперь предупреждение прошло:)
Некоторые заметки:
Убедитесь, что вы добавляете ограничения в collectionView и не используете
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Убедитесь, что вы не звоните
invalidateLayout
вviewWillTransitionToSize()
потому что ширина ячейки от края до края в альбомной ориентации больше ширины кадра представления коллекции в портретной ориентации. Смотрите ниже ссылки.
Рекомендации
Для Xcode 11 ни один из вышеперечисленных методов у меня не работал.
Оказалось, что нужно установить для параметра Estimate Size значение None на панели размеров представления коллекции.
Измените значение "Нет" оценочного размера представления вашей коллекции из автоматического.
Вы можете сделать это с помощью раскадровки или программно.
Но вам нужно рассчитать его вручную с помощью пользовательского класса макета или API протокола UICollectionViewDelegateFlowLayout.
Это происходит потому, что ширина ячейки представления коллекции после поворота больше ширины представления коллекции. Предположим, что у вас есть экран 1024x768, и представление вашей коллекции заполняет экран. Когда ваше устройство имеет альбомную ориентацию, ширина вашей ячейки будет равна self.collectionView.frame.size..width - 20 =1004 и больше, чем ширина представления вашей коллекции в портретном = 768. Отладчик говорит, что "ширина элемента должна быть меньше ширины UICollectionView минус вставки разделов влево и вправо, минус вставки содержимого влево и правильные ценности ".
Это работает для меня:
Недействительно макет при вращении:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
Есть отдельная функция для расчета доступной ширины:
Примечание: принимая во внимание как вставку раздела, так и вставку содержимого.
// MARK: - Helpers
func calculateCellWidth(for collectionView: UICollectionView, section: Int) -> CGFloat {
var width = collectionView.frame.width
let contentInset = collectionView.contentInset
width = width - contentInset.left - contentInset.right
// Uncomment the following two lines if you're adjusting section insets
// let sectionInset = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, insetForSectionAt: section)
// width = width - sectionInset.left - sectionInset.right
// Uncomment if you are using the sectionInset property on flow layouts
// let sectionInset = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset ?? UIEdgeInsets.zero
// width = width - sectionInset.left - sectionInset.right
return width
}
И, наконец, наконец, возвращаем размер предмета:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: calculateCellWidth(for: collectionView, section: indexPath.section), height: 60)
}
Я думаю, важно отметить, что это работает, потому что аннулирование макета не приведет к немедленному пересчету размера ячейки, но во время следующего цикла обновления макета. Это означает, что как только обратный вызов размера элемента в конечном счете вызывается, представление коллекции имеет правильный фрейм, таким образом позволяя точное определение размера.
Вуаля!
После некоторого экспериментирования это, похоже, также связано с тем, как вы создадите макет collectionView.
Tl;dr: использовать AutoLayout, а не autoresizingMask.
Таким образом, для решения этой проблемы наилучшие решения, которые я нашел для обработки изменения ориентации, используют следующий код, который имеет смысл:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
self.collectionView.collectionViewLayout.invalidateLayout()
}, completion: nil)
}
Однако все еще существуют ситуации, когда вы можете получить предупреждение о размере элемента. Для меня это если я нахожусь в альбомной ориентации на одной вкладке, переключаюсь на другую вкладку, поворачиваюсь в портретную, затем возвращаюсь к предыдущей вкладке. Я попытался сделать недействительным макет в willAppear, willLayout, всех обычных подозреваемых, но не повезло. На самом деле, даже если вы звоните invalidateLayout
до super.willAppear()
Вы все еще получаете предупреждение.
И, в конечном счете, эта проблема связана с размером обновления границ collectionView перед тем, как делегат запрашивает itemSize.
Поэтому, имея в виду, я попытался использовать AutoLayout вместо collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
и с этим проблема решена! (Я использую SnapKit, чтобы сделать AutoLayout, чтобы я не вырывал свои волосы постоянно). Вам также просто нужно invalidateLayout
в viewWillTransition
(без coordinator.animate
так же, как у вас в вашем примере), а также invalidateLayout
на дне viewWillAppear(:)
, Это, кажется, охватывает все ситуации.
Я не знаю, используете ли вы автоматическое изменение размера или нет - было бы интересно узнать, работает ли моя теория / решение для всех.
Вы можете проверить в отладчике, если collectionView.contentOffset
меняется на отрицательный, в моем случае он меняется от (0,0)
в (0,-40)
, Вы можете решить эту проблему, используя этот метод
if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
Это решение определенно работает.. Попробуйте этот код
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
collectionView.collectionViewLayout.invalidateLayout()
super.viewWillTransition(to: size, with: coordinator)
}
Я мог бы решить это, поставив
super.prepare()
в конце
override func prepare() { }
если вы используете динамические типы для шрифтов, просто перезагрузитеcollectionView
при изменении размера шрифта:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
collectionView?.reloadData()
}
У меня была похожая проблема.
В моем случае у меня был вид коллекции, а когда вы нажали на одну из ячеек, всплывающее окно с UITextField
открыт, чтобы редактировать элемент. После того, как этот поповер исчез, self.collectionView.contentInset.bottom
был установлен на 55 (изначально 0).
Чтобы исправить мою проблему, после исчезновения всплывающего окна я вручную устанавливаю для contentInset значение UIEdgeInsetsZero
,
Первоначальная проблема, кажется, связана с контекстной панелью прогнозирования, которая отображается в верхней части клавиатуры. Когда клавиатура скрыта, панель исчезает, но contentInset.bottom
значение не восстанавливается до исходного значения.
Поскольку ваша проблема связана с шириной, а не с высотой ячейки, проверьте, не contentInset
или же layout.sectionInset
значения совпадают с установленными вами.
Это мое решение:
Удалите все методы UICollectionViewFlowDelegate из вашего контроллера представления.
Создайте свой собственный класс flowLayout как подкласс UICollectionViewFlowLayout.
класс ImageCollectionViewFlowLayout: UICollectionViewFlowLayout {}
Настройте все размеры и другие параметры в методе prepare (). См. Мой пример ниже.
private let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0) override func prepare() { super.prepare() guard let collectionView = collectionView else { return } self.sectionInset = sectionInsets self.minimumLineSpacing = sectionInsets.bottom self.minimumInteritemSpacing = sectionInsets.bottom let width = collectionView.bounds .inset(by: collectionView.layoutMargins) .inset(by: self.sectionInset) .width self.estimatedItemSize = CGSize(width: width, height: width + 16) //self.itemSize - I do not set itemsize because my cell has dynamical height }
Это все. Держите свой код в чистоте.
Я тоже несколько часов борюсь с этой проблемой.
Мой UICollectionViewController
встроен в представление контейнера, при повороте iPad из альбомной ориентации в вертикальную отображалась ошибка. Однако размеры ячеек изменились.
Я решил ошибку, вызвав в UICollectionViewController
:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
self.collectionView.collectionViewLayout.invalidateLayout()
super.viewWillTransition(to: size, with: coordinator)
}
"Супер" должен быть вызван после аннулирования макета.
Кроме того, мне нужно было вызвать следующее в представлении, в которое встроено представление контейнера:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
DispatchQueue.main.async {
self.collectionViewController.collectionView.reloadData()
}
}
Таким образом, ячейки обновляются после выполнения поворота экрана.
Я была такая же проблема. Я исправил это, очистив свой сборник от его ограничений и сбросив их в раскадровке.
В моем случае для вертикального UICollectionView у меня было:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: padding(scale: 37))
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: padding(scale: 10), left: padding(scale: 4), bottom: padding(scale: 3), right: padding(scale: 4))
}
Я исправил это, подогнав горизонтальные вставки к размеру:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionView.bounds.width - (2 * padding(scale: 4))), height: padding(scale: 37))
}
Любые изменения в UIView должны выполняться из основного потока. Поместите свой код collectionView в дополнение к иерархии представлений в:
DispatchQueue.main.async {
...
}
Я обнаружил, что это работает очень хорошо для UICollectionViewController
и правильно анимирован:
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Ensure the layout is within the allowed size before animating below, or
// `UICollectionViewFlowLayoutBreakForInvalidSizes` breakpoint will trigger
// and the logging will occur.
if ([self.collectionView.collectionViewLayout isKindOfClass:[YourLayoutSubclass class]]) {
[(YourLayoutSubclass *)self.collectionView.collectionViewLayout updateForWidth:size.width];
}
// Then, animate alongside the transition, as you would:
[coordinator animateAlongsideTransition:^
(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self.collectionView.collectionViewLayout invalidateLayout];
}
completion:nil];
[super viewWillTransitionToSize:size
withTransitionCoordinator:coordinator];
}
///////////////
@implementation YourLayoutSubclass
- (void)prepareLayout {
[super prepareLayout];
[self updateForWidth:self.collectionView.bounds.size.width];
}
- (void)updateForWidth:(CGFloat)width {
// Update layout as you wish...
}
@end
... См. Комментарии встроенного кода выше для объяснения.
Если вы устанавливаетеcollectionViewLayout
в коде, затем убедитесь, что размер допустим. Установите точку останова в строке кода, где вы установилиitemSize
и проверьте, соответствует ли размер инструкции предупреждения об ошибке: ширина элемента должна быть меньше, чем ширина UICollectionView минус вставки левого и правого значений раздела, минус вставки левого и правого значений содержимого.
Это то, что вам нужно:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//Get frame width
let width = self.view.frame.width
//I want a width of 418 and height of 274 (Aspect ratio 209:137) with a margin of 24 on each side of the cell (See insetForSectionAt 24 + 24 = 48). So, check if a have that much screen real estate.
if width > (418 + 48) {
//If I do return the size I want
return CGSize(width: 418, height: 274)
}else{
//Get new width. Frame width minus the margins I want to maintain
let newWidth = (width - 48)
//If not calculate the new height that maintains the aspect ratio I want. NewHeight = (originalHeight / originalWidth) * newWidth
let height = (274 / 418) * newWidth
//Return the new size that is Aspect ratio 209:137
return CGSize(width: newWidth, height: height)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 33
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 33
}