Недавно мне понадобилось сделать адаптивный лэйаут для UICollectionView для поддержки экранов всех iPhone и iPad, а так же новой функции SplitView. Старый метод, который я использовал требовал слишком много кода, в котором можно было легко запутаться.
Как я делал раньше
Класс контроллера я назначал делегатом UICollectionViewDelegateFlowLayout и использовал эти методы:
collectionView(_:layout:sizeForItemAtIndexPath:) collectionView(_:layout:insetForSectionAtIndex:) collectionView(_:layout:minimumLineSpacingForSectionAtIndex:) collectionView(_:layout:minimumInteritemSpacingForSectionAtIndex:)
Пример. Мне нужно красиво уместить ячейки 100х100 pt на экране с расстояниями в 4 pt между ними, я делал так:
// настраиваем размер и расстояния. insetForSectionAtIndex задан в сториборде, так как менять его в коде нет необходимости let cellWidth:CGFloat = 100, spacing:CGFloat = 4 // задаем размер ячейки в зависимости от ширины collection view func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { let columns:CGFloat = ceil(collectionView.bounds.size.width / cellWidth) let size = (collectionView.bounds.size.width - ((columns-1)*spacing)) / columns return CGSizeMake(size, size) } // возвращаем расстояния func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat { return spacing } // здесь я обновлял высоту collection view при показе страницы и при изменении ориентации устройства (viewDidLoad: и viewWillTransitionToSize: func collectionViewUpdateLayout() { self.collectionView.performBatchUpdates(nil) { (_) -> Void in self.collectionViewHeight.constant = self.collectionView.contentSize.height } }
Пример 2. Если ширина UICollectionView больше 450 pt, используем две колонки квадратных ячеек. Расстояния между ними и границами UICollectionView – 4 pt. Если ширина меньше, то колонка одна, а расстояния – 0.
// Задаем минимальную ширину для двух колонок. если ширина collection view станет меньше, у нас будет отображаться одна колонка ячеек. Так же задаем отступы между ячейками и границами collection view. let minimalCollectionViewWidthForDoubleColumnMode:CGFloat = 450, edgeInsets:CGFloat = 4, spacing:CGFloat = 4, lineSpacingForOneColumnMode:CGFloat = 0 // считаем размер ячеек func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { let size = collectionView.bounds.size.width if size > minimalCollectionViewWidthForDoubleColumnMode { return CGSizeMake((size-edgeInsets*2-spacing)/2, (size-edgeInsets*2-spacing)/2) } else{ return CGSizeMake(size, size) } } // возвращаем расстояния func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat { return spacing } func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat { let size = collectionView.bounds.size.width if size > minimalCollectionViewWidthForDoubleColumnMode { return spacing } else { return lineSpacingForOneColumnMode } } func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { if collectionView.bounds.size.width > minimalCollectionViewWidthForDoubleColumnMode { return UIEdgeInsetsMake(edgeInsets, edgeInsets, edgeInsets, edgeInsets) } else{ return UIEdgeInsetsZero } }
(в коде выше могут быть серьёзные недочёты, но он служил мне верой и правдой в реальном приложении)
Мой новый способ
Я написал расширение для UICollectionViewLayout, которое позволяет задавать все эти значения в одну строчку кода, а если нужно — создавать сложные правила для разной ширины UICollectionView.
Улучшаем Пример 1.
// Та самая функция, которую мы вызываем во viewDidLoad: и viewWillTransitionToSize: withTransitionCoordinator: func collectionViewUpdateLayout() { // Задаем правило collectionView.collectionViewLayout.makeAdaptive(withMinimumCellWidth: 100, cellAspectRatio: 1, spacing: CGSizeMake(4, 4), andEdgeInsets:UIEdgeInsetsMake(4, 4, 4, 4)) // Обновляем высоту collection view self.collectionView.performBatchUpdates(nil) { (_) -> Void in self.collectionViewHeight.constant = self.collectionView.contentSize.height } }
Улучшаем Пример 2.
// Та самая функция, которую мы вызываем во viewDidLoad: и viewWillTransitionToSize: withTransitionCoordinator: func collectionViewUpdateLayout() { // Создаём два правила для разных размеров экрана let rule1 = AdaptiveLayoutRule(columns: 1, width: 0, spacing: CGSizeZero, andInsets:UIEdgeInsetsZero) let rule2 = AdaptiveLayoutRule(columns: 2, width: 450, spacing: CGSizeMake(4, 4), andInsets:UIEdgeInsetsMake(4, 4, 4, 4)) // Применяем правило self.collectionViewLayout.makeAdaptiveWithRules([rule1, rule2], aspectRatio: 1) // Обновляем высоту collection view self.collectionView.performBatchUpdates(nil) { (_) -> Void in self.collectionViewHeight.constant = self.collectionView.contentSize.height } }
Вот так
Если в вашем проекте есть необходимость использовать UICollectionView с простым и адаптивным лэйаутом, надеюсь, моё расширение вам пригодится.