Поведение UICollectionViewFlowLayout не определено, поскольку ширина ячейки больше ширины коллекцииView
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 =; }; layer =; contentOffset: {0, 0}; contentSize: {1024, 770} > макет коллекции:. 2015-08-18 16: 07: 51.524 Пример [16070: 269647] Сделать символический точка останова в UICollectionViewFlowLayoutBreakForInvalidSizes для catch это в отладчике.
Это то, что я получаю, что я делаю, это
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(self.collectionView!.frame.size.width - 20, 66)
}
когда я вращаюсь от пейзажа к портрету, консоль показывает это сообщение об ошибке только в iOS 9, кто-нибудь знает, что это происходит, и если есть исправление для этого?
![Снимок экрана]()
Ответы
Ответ 1
Это происходит, когда просмотр коллекции изменяется на более низкий (например, переход от пейзажного к портретному режиму), и ячейка становится слишком большой, чтобы соответствовать.
Почему ячейка становится слишком большой, так как нужно отобразить макет потока представления коллекции и вернуть подходящий размер?
collectionView (collectionView: UICollectionView, макет collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath)
Обновить, чтобы включить Swift 4
@objc переопределить func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) → UICollectionViewCell {...}
Это потому, что эта функция называется not или, по крайней мере, не сразу.
Что происходит, так это то, что подкласс класса представления представления коллекции не отменяет функцию shouldInvalidateLayoutForBoundsChange
, которая по умолчанию returns false
.
Когда этот метод возвращает false, представление коллекции сначала пытается перейти с текущим размером ячейки, обнаруживает проблему (которая регистрирует предупреждение), а затем вызывает раскладку потока для изменения размера ячейки.
Это означает 2 вещи:
1 - предупреждение само по себе не вредно
2 - вы можете избавиться от него, просто переопределив функцию shouldInvalidateLayoutForBoundsChange, чтобы вернуть true.
В этом случае макет потока всегда будет вызываться, когда изменяются границы просмотра коллекции.
Ответ 2
Это происходит потому, что ширина ячейки представления коллекции больше, чем ширина просмотра коллекции после вращения. Предположим, что у вас есть экран 1024x768, и ваш вид коллекции заполняет экран. Когда ваше устройство является ландшафтом, ваша ширина ячейки будет self.collectionView. frame.size.width - 20 = 1004, а его ширина больше, чем ширина коллекции в портрете = 768. Отладчик говорит, что "ширина элемента должна быть меньше ширины UICollectionView за вычетом значений вставки слева и справа, минус вставки содержимого слева и справа".
Ответ 3
Я использую подкласс 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()
потому что ширина ячейки от края до края в альбомной viewWillTransitionToSize()
больше ширины кадра коллекций представлений в портретной ориентации. Смотрите ниже ссылки.
Рекомендации
Ответ 4
Я решил эту проблему, используя 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();
}
Ответ 5
Вы можете проверить отладчик, если collectionView.contentOffset
изменен на отрицательный, в моем случае он изменяется от (0,0)
до (0,-40)
. Вы можете решить эту проблему, используя этот метод
if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
Ответ 6
После некоторых экспериментов это, похоже, также связано с тем, как вы компонуете свой 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()
, вы все равно получите предупреждение.
И в конечном итоге эта проблема привязана к размеру обновления границ коллекции, прежде чем делегат попросит элемент itemize.
Итак, имея в виду, я попытался использовать AutoLayout вместо collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
, и с этим проблема решена! (Я использую SnapKit, чтобы делать AutoLayout, чтобы я не вытаскивал свои волосы постоянно). Вам также просто нужно invalidateLayout
в viewWillTransition
(без coordinator.animate
, так же, как в вашем примере), а также invalidateLayout
внизу viewWillAppear(:)
. Это похоже на все ситуации.
Я не знаю, пользуетесь ли вы авторезистированием или нет - было бы интересно узнать, работает ли моя теория/решение для всех.
Ответ 7
Это работает для меня:
Недействительно макет при вращении:
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)
}
Я думаю, важно отметить, что это работает, потому что аннулирование макета не приведет к немедленному пересчету размера ячейки, но во время следующего цикла обновления макета. Это означает, что как только обратный вызов размера элемента в конечном счете вызывается, представление коллекции имеет правильный фрейм, таким образом позволяя точное определение размера.
Вуаля!
Ответ 8
Я также видел, как это происходит, когда мы устанавливаем для параметра valuesItemSize значение AutomaticSize, а вычисленный размер ячеек составляет менее 50x50 (Примечание. Этот ответ был действителен во времена iOS 12. Возможно, в более поздних версиях его исправили).
то есть, если вы объявляете поддержку ячеек с саморазмером, устанавливая предполагаемый размер элемента в константу automaticSize
:
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
и ваши ячейки на самом деле меньше, чем 50x50 точек, тогда UIKit распечатывает эти предупреждения. К сожалению, клетки в конечном итоге имеют соответствующий размер, и предупреждения кажутся безвредными.
Один из обходных путей исправления заключается в замене константы размером элемента 1x1:
flowLayout.estimatedItemSize = CGSize(width: 1, height: 1)
Это не влияет на самое-проклейках поддержки, потому что, как документация estimatedItemSize
упоминает:
Установка его в любое другое значение заставляет представление коллекции запрашивать каждую ячейку для ее фактического размера, используя ячейку предпочитаемый элементayLayoutAttributesFitting (_ :).
Тем не менее, это может/не может иметь последствия для производительности.
Ответ 9
У меня была аналогичная проблема.
В моем случае у меня появилось представление коллекции, и когда вы постучали по одной из ячеек, для редактирования элемента открылось popover с UITextField
. После этого popover исчезло, self.collectionView.contentInset.bottom
было установлено в 55 (изначально 0).
Чтобы исправить мою проблему, после исчезновения представления popover, я вручную настраиваю содержимоеInset на UIEdgeInsetsZero
.
![contextual prediction bar]()
Оригинальная проблема, похоже, связана с панелью контекстного прогнозирования, которая отображается вверху клавиатуры. Когда клавиатура скрыта, панель исчезает, но значение contentInset.bottom
не возвращается к исходному значению.
Поскольку ваша проблема, похоже, связана с шириной, а не с высотой ячейки, проверьте, не соответствуют ли какие-либо из значений contentInset
или layout.sectionInset
тем, которые были установлены вами.
Ответ 10
У меня была такая же проблема. Я исправил это, очистив мой просмотр коллекции его ограничений и сбросив их в Storyboard.
Ответ 11
Я обнаружил, что это хорошо работает для 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
... См. Комментарии встроенного кода выше для объяснения. 👍🏻
Ответ 12
Это то, что вам нужно:
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
}