Адаптивный UICollectionView

Недавно мне понадобилось сделать адаптивный лэйаут для 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 с простым и адаптивным лэйаутом, надеюсь, моё расширение вам пригодится.

Автор

Yaroslav Erohin

Yaroslav Erohin

мобильный разработчик, хувиан, одержим музыкой

Добавить комментарий для REMONTkl Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *