UICollectionView, ячейки полной ширины, разрешить автоматическое размещение динамической высоты?

120

В вертикальном UICollectionView,

Можно ли иметь ячейки полной ширины , но разрешить управление динамической высотой с помощью автоматического размещения ?

Это кажется мне, пожалуй, «самым важным вопросом в iOS, на который нет действительно хорошего ответа».


Важный:

Обратите внимание, что в 99% случаев для достижения полной ширины ячеек + динамической высоты автоматического раскладки просто используйте табличное представление. Это так просто.


Итак, каков пример того, где вам нужно представление коллекции?

Представления коллекций невероятно мощнее табличных представлений.

Один простой пример, в котором вам действительно нужно использовать представление коллекции для достижения ячеек полной ширины + динамической высоты автоматического раскладки:

Если вы выполняете анимацию между двумя макетами в представлении коллекции. Например, между макетом с 1 и 2 столбцами, когда устройство вращается.

Это обычная и нормальная идиома в iOS. К сожалению, этого можно достичь только путем решения проблемы, поставленной в этом QA. : - /

Fattie
источник
да возможно с классом UICollectionViewDelegateFlowLayout
Сунил Праджапати
Разве это не означает, что вы действительно можете использовать tableView? какие дополнительные функции вам понадобятся, чтобы усложнить это?
Catalina T.
1
Привет @CatalinaT. , представления коллекций имеют много, много, много дополнительных функций по сравнению с представлениями таблиц. Простой пример - добавление физики. (Если вы не понимаете, что я имею в виду, это то, как вы делаете «прыгучие» списки в iOS.)
Fattie 01
вы также можете каким-то образом применить физику (например, SpringDampingи initialSpringVelocity) в таблице Cell ..... посмотрите этот Bouncy List с помощью tableView ,,,, pastebin.com/pFRQhkrX вы можете анимировать TableViewCellв таблице в willDisplayCell:(UITableViewCell *)cellметоде delgate @Fattie, который вы можете принять ответ, который решил вашу проблему, чтобы помочь другим, кто ищет ту же проблему
Диру
ох, вы пробовали список Bouncy с помощью списка? @Fattie
Dhiru

Ответы:

125

1. Решение для iOS 13+

В Swift 5.1 и iOS 13 вы можете использовать объекты Compositional Layout для решения вашей проблемы.

В следующем полном примере кода показано, как отображать многострочность во UILabelвсю ширину UICollectionViewCell:

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        let size = NSCollectionLayoutSize(
            widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
            heightDimension: NSCollectionLayoutDimension.estimated(44)
        )
        let item = NSCollectionLayoutItem(layoutSize: size)
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
        section.interGroupSpacing = 10

        let headerFooterSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .absolute(40)
        )
        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: headerFooterSize,
            elementKind: "SectionHeaderElementKind",
            alignment: .top
        )
        section.boundarySupplementaryItems = [sectionHeader]

        let layout = UICollectionViewCompositionalLayout(section: section)
        collectionView.collectionViewLayout = layout
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

    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)
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Ожидаемый показ:

введите описание изображения здесь


2. Решение для iOS 11+

В Swift 5.1 и iOS 11 вы можете UICollectionViewFlowLayoutсоздать подкласс и установить для его estimatedItemSizeсвойства значение UICollectionViewFlowLayout.automaticSize(это сообщает системе, что вы хотите иметь дело с автоматическим изменением размеров UICollectionViewCell). Затем вам придется переопределить layoutAttributesForElements(in:)и layoutAttributesForItem(at:)установить ширину ячеек. Наконец, вам придется переопределить preferredLayoutAttributesFitting(_:)метод своей ячейки и вычислить ее высоту.

В следующем полном коде показано, как отобразить многострочность UILabelвнутри на всю ширину UIcollectionViewCell(ограничена UICollectionViewбезопасной областью и UICollectionViewFlowLayoutвставками):

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]
    let customFlowLayout = CustomFlowLayout()

    override func viewDidLoad() {
        super.viewDidLoad()

        customFlowLayout.sectionInsetReference = .fromContentInset // .fromContentInset is default
        customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        customFlowLayout.minimumInteritemSpacing = 10
        customFlowLayout.minimumLineSpacing = 10
        customFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        customFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)

        collectionView.collectionViewLayout = customFlowLayout
        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

}

CustomFlowLayout.swift

import UIKit

final class CustomFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
        layoutAttributesObjects?.forEach({ layoutAttributes in
            if layoutAttributes.representedElementCategory == .cell {
                if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        })
        return layoutAttributesObjects
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let collectionView = collectionView else {
            fatalError()
        }
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }

        layoutAttributes.frame.origin.x = sectionInset.left
        layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
        return layoutAttributes
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
        layoutIfNeeded()
        layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
        return layoutAttributes
    }

}

Вот несколько альтернативных реализаций для preferredLayoutAttributesFitting(_:):

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    return layoutAttributes
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    label.preferredMaxLayoutWidth = layoutAttributes.frame.width
    layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
    return layoutAttributes
}

Ожидаемый показ:

введите описание изображения здесь

Иману Пети
источник
У меня это работало хорошо, пока я не добавил заголовки разделов. Заголовки разделов неуместны и закрывают другие ячейки в iOS 11. Но отлично работает в iOS 12. Сталкивались ли вы с какой-либо проблемой при добавлении заголовков разделов?
Накул
Огромное спасибо. Ты спас мне неделю. 🙇‍♂️
Gaetan
1
CollectionView прокручивается вверх при изменении размера
Саджит
1
отличные новости, @ImanouPetit! Желаю, чтобы награда была больше! Фантастическая информация об объектах Compositional Layout, спасибо.
Fattie
1
Привет, я пробую это. Несмотря на то, что я устанавливаю макет в viewdidload, как вы это делали выше, я получаю сбой, в котором говорится, что сборный вид должен быть инициализирован с параметром макета, отличным от nil. Что-то мне не хватает? Это Xcode 11 для iOS 13.
geistmate
29

проблема

Вам нужна автоматическая высота, а также полная ширина, невозможно использовать оба варианта UICollectionViewFlowLayoutAutomaticSize.

Вы хотите использовать, UICollectionViewпоэтому решение для вас ниже.

Решение

Шаг I. Рассчитайте ожидаемую высоту ячейки.

1. Если у вас есть только UILabel в CollectionViewCellчем установить numberOfLines=0и вычислили ожидаемую высоту UIlable, пройти все три paramters

func heightForLable(text:String, font:UIFont, width:CGFloat) -> CGFloat {
    // pass string, font, LableWidth  
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
     label.numberOfLines = 0
     label.lineBreakMode = NSLineBreakMode.byWordWrapping
     label.font = font
     label.text = text
     label.sizeToFit()

     return label.frame.height
}

2. Если ваш CollectionViewCellсодержит только UIImageViewи если он должен быть динамическим по высоте, вам нужно получить высоту UIImage (у вас UIImageViewдолжны быть AspectRatioограничения)

// this will give you the height of your Image
let heightInPoints = image.size.height
let heightInPixels = heightInPoints * image.scale

3. Если он содержит оба, вычислите их высоту и сложите их вместе.

ШАГ II . Верните размерCollectionViewCell

1. Добавьте UICollectionViewDelegateFlowLayoutделегата в свой viewController

2. Реализуйте метод делегата.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    // This is just for example, for the scenario Step-I -> 1 
    let yourWidthOfLable=self.view.size.width
    let font = UIFont(name: "Helvetica", size: 20.0)

    var expectedHeight = heightForLable(array[indePath.row], font: font, width:yourWidthOfLable)


    return CGSize(width: view.frame.width, height: expectedHeight)
}

Надеюсь, это вам поможет.

Дхиру
источник
1
Возможно ли иметь горизонтальную прокрутку UICollectionView с вертикальным увеличением высоты ячеек с автоматическим макетом?
nr5 07
Я хочу добиться того же, но с половинной шириной и динамической высотой
Михир Мехта
return CGSize(width: view.frame.width/2, height: expectedHeight)также сохраните в учетной записи Padding, чем верните ширину, иначе две ячейки не поместятся в одну строку.
Dhiru
@ nr5 Вы получили решение? Мое требование такое же, как и ваше. Спасибо.
Маулик Бхуптани
@MaulikBhuptani Я не смог полностью сделать это с помощью Auto Layout. Пришлось создать собственный класс макета и переопределить некоторые методы класса.
nr5
18

Есть несколько способов решить эту проблему.

Один из способов - дать макету потока представления коллекции примерный размер и вычислить размер ячейки.

Примечание. Как указано в комментариях ниже, начиная с iOS 10 вам больше не нужно указывать и предполагаемый размер для запуска вызова ячеек func preferredLayoutAttributesFitting(_ layoutAttributes:). Раньше (iOS 9) требовалось предоставить приблизительный размер, если вы хотели запросить ячейки prefferedLayoutAttributes.

(при условии, что вы используете раскадровки и представление коллекции подключено через IB)

override func viewDidLoad() {
    super.viewDidLoad()
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.estimatedItemSize = CGSize(width: 375, height: 200) // your average cell size
}

Для простых ячеек этого обычно бывает достаточно. Если размер по-прежнему неверен, в ячейке представления коллекции вы можете переопределить func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes, что даст вам более точный контроль над размером ячейки. Примечание. Вам все равно нужно будет указать примерный размер схемы потока .

Затем переопределите, func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributesчтобы вернуть правильный размер.

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
    let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize)
    autoLayoutAttributes.frame = autoLayoutFrame
    return autoLayoutAttributes
}

В качестве альтернативы вместо этого вы можете использовать размерную ячейку для вычисления размера ячейки в UICollectionViewDelegateFlowLayout.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.width
    let size = CGSize(width: width, height: 0)
    // assuming your collection view cell is a nib
    // you may also instantiate a instance of our cell from a storyboard
    let sizingCell = UINib(nibName: "yourNibName", bundle: nil).instantiate(withOwner: nil, options: nil).first as! YourCollectionViewCell
    sizingCell.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    sizingCell.frame.size = size
    sizingCell.configure(with: object[indexPath.row]) // what ever method configures your cell
    return sizingCell.contentView.systemLayoutSizeFitting(size, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
}

Хотя это не идеальные готовые примеры, они должны помочь вам начать работу в правильном направлении. Я не могу сказать, что это лучшая практика, но у меня это работает даже с довольно сложными ячейками, содержащими несколько меток, которые могут или не могут переноситься на несколько строк.

Эрик Мерфи
источник
@Fattie Непонятно, как esitmatedItemSize не имеет отношения к ячейке представления коллекции динамического размера, поэтому я хотел бы услышать ваши мысли. (не сарказм)
Эрик Мерфи
Кроме того, если в этом ответе отсутствует что-то конкретное, чтобы быть полезным и / или то, что вы считаете каноническим, дайте мне знать. Я не буду спорить с наградой, я просто хотел бы быть полезным для всех, кто задумывается над этим вопросом.
Эрик Мерфи
1
(включая или не включая EstimatedItemSize не имеет значения в текущей iOS, и, в любом случае, это не помогает в проблеме «полная ширина + динамическая высота».) Код в конце, перья редко используются в наши дни, и он имеет незначительное отношение к динамической высоте (например, из нескольких текстовых представлений с динамической высотой). хотя спасибо
Fattie 05
Я тоже пришел к этому решению, но в моем случае, iOS 11, Xcode 9.2, содержимое ячейки казалось отключенным от самой ячейки - я не мог заставить изображение расширяться до полной высоты или ширины фиксированного размера. Я также заметил, что в CV были ограничения настроек preferredLayoutAttributesFitting. Затем я добавил собственное ограничение в указанную функцию, привязку к фиксированному измерению из EstimatedItemSize, FWIW, см. Мой ответ ниже.
Крис Коновер,
16

Я нашел довольно простое решение этой проблемы: внутри моего CollectionViewCell я получил UIView (), который на самом деле является просто фоном. Чтобы получить полную ширину, я просто установил следующие привязки

bgView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width - 30).isActive = true // 30 is my added up left and right Inset
bgView.topAnchor.constraint(equalTo: topAnchor).isActive = true
bgView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
bgView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
bgView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

«Магия» происходит в первой строке. Я динамически устанавливаю widthAnchor на ширину экрана. Также важно вычесть вставки вашего CollectionView. В противном случае ячейка не появится. Если вы не хотите иметь такой вид фона, просто сделайте его невидимым.

FlowLayout использует следующие настройки

layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

Результатом является ячейка полной ширины с динамической высотой.

введите описание изображения здесь

inf1783
источник
1
святая корова - это потрясающе! так очевидно ! молодец!
Fattie
1
просто TBC @ inf1783, вы делаете это в UICollectionView? (не табличный вид) - правильно?
Fattie
1
@Fattie Да, это UICollectionView;)
inf1783
фантастический @ inf1783, не могу дождаться, чтобы попробовать это. Кстати, еще один хороший способ сделать это - создать подкласс UIView и просто добавить внутреннюю ширину (я предполагаю, что это будет иметь тот же эффект, и, вероятно, заставит его работать и во время раскадровки)
Fattie
1
Каждый раз, когда я пытаюсь это сделать, я захожу в бесконечный цикл с ошибкой Поведение UICollectionViewFlowLayout не определено: /
Skodik.o
7

РАБОТАЕТ!!! Проверено на IOS: 12.1 Swift 4.1

У меня есть очень простое решение, которое работает без нарушения ограничений.

введите описание изображения здесь

Мой ViewControllerClass

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    let cellId = "CustomCell"

    var source = ["nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,"]

    override func viewDidLoad() {
        super.viewDidLoad()

        self.collectionView.delegate = self
        self.collectionView.dataSource = self
        self.collectionView.register(UINib.init(nibName: cellId, bundle: nil), forCellWithReuseIdentifier: cellId)

        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        }

    }

}


extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.source.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? CustomCell else { return UICollectionViewCell() }
        cell.setData(data: source[indexPath.item])
        return cell
    }


}

CustomCell класс:

class CustomCell: UICollectionViewCell {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var widthConstraint: NSLayoutConstraint!

    override func awakeFromNib() {
        super.awakeFromNib()
        self.widthConstraint.constant = UIScreen.main.bounds.width
    }

    func setData(data: String) {
        self.label.text = data
    }

    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        return contentView.systemLayoutSizeFitting(CGSize(width: self.bounds.size.width, height: 1))
    }

}

Основной ингредиент является systemLayoutSizeFitting функция Customcell. А также мы должны установить ширину представления внутри Cell с ограничениями.

Абхишек Бисвас
источник
6

Вам нужно добавить ограничение ширины в CollectionViewCell

class SelfSizingCell: UICollectionViewCell {

  override func awakeFromNib() {
      super.awakeFromNib()
      contentView.translatesAutoresizingMaskIntoConstraints = false
      contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
  }
}
Mecid
источник
наконец, кто-то сделал его идеальным в двух строках
Омар Н Шамали
Это ограничит ячейку фиксированной шириной без возможности самостоятельного изменения размера. Если вы повернули устройство или отрегулируете размер экрана на iPad, он останется неизменным.
Томас Вербеек
4
  1. Набор estimatedItemSizeмакета вашего потока:

    collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
  2. Определите ограничение ширины в ячейке и установите его равным ширине супервизора:

    class CollectionViewCell: UICollectionViewCell {
        private var widthConstraint: NSLayoutConstraint?
    
        ...
    
        override init(frame: CGRect) {
            ...
            // Create width constraint to set it later.
            widthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0)
        }
    
        override func updateConstraints() {
            // Set width constraint to superview's width.
            widthConstraint?.constant = superview?.bounds.width ?? 0
            widthConstraint?.isActive = true
            super.updateConstraints()
        }
    
        ...
    }

Полный пример

Проверено на iOS 11.

Римский
источник
3

Лично я нашел лучший способ иметь UICollectionView, где AutoLayout определяет размер, в то время как каждая ячейка может иметь другой размер, - это реализовать функцию UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath при использовании фактической ячейки для измерения размера.

Я говорил об этом в одном из своих сообщений в блоге

Надеюсь, это поможет вам достичь того, чего вы хотите. Я не уверен на 100%, но я считаю, что в отличие от UITableView, где вы действительно можете иметь полностью автоматическую высоту ячеек, используя AutoLayout в сочетании с

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionView не имеет такого способа, позволяющего AutoLayout определять размер, потому что UICollectionViewCell не обязательно заполняет всю ширину экрана.

Но вот вопрос к вам : если вам нужны ячейки с полной шириной экрана, зачем вам вообще использовать UICollectionView вместо старого доброго UITableView, который поставляется с ячейками автоматического изменения размера?

xxtesaxx
источник
держите его, Aprit ниже говорит, что UICollectionViewFlowLayoutAutomaticSize теперь доступен в макете потока в iOS10 ...
Fattie
Я тоже только что это видел. По-видимому, теперь это актуально в iOS 10. Я только что смотрел соответствующий доклад WWDC по этому поводу: developer.apple.com/videos/play/wwdc2016/219 Однако это полностью автоматически изменит размер ячейки в соответствии с ее содержимым. Я не уверен, как вы могли указать ячейке (с помощью AutoLayout) заполнить ширину экрана, поскольку вы не можете установить ограничение между UICollectionViewCell и его родительским элементом (по крайней мере, не в StoryBoards)
xxtesaxx
право. Кажется, мы не приблизились к реальному решению этой невероятно очевидной проблемы. : /
Fattie 05
Согласно докладу, вы все равно можете перезаписать sizeThatFits () или preferredLayoutAttributesFitting () и рассчитать размер самостоятельно, но я думаю, что это не то, что просил OP. В любом случае, меня все равно будет интересовать вариант использования, когда ему / ей нужен полноразмерный UICollectionViewCell, и почему было бы так важно не использовать UITableView в этом конкретном случае (конечно, могут быть определенные случаи, но тогда я думаю вам просто нужно самому заниматься некоторыми вычислениями)
xxtesaxx 05
подумав об этом, совершенно невероятно, что вы не можете просто установить «columns == 1» (а затем, возможно, columns == 2, когда устройство находится сбоку) с представлениями коллекций.
Fattie 05
3

Согласно моему комментарию к ответу Эрика, мое решение очень похоже на его, но мне пришлось добавить ограничение в предпочтительныйSizeFor ..., чтобы ограничиться фиксированным размером.

    override func systemLayoutSizeFitting(
        _ targetSize: CGSize, withHorizontalFittingPriority
        horizontalFittingPriority: UILayoutPriority,
        verticalFittingPriority: UILayoutPriority) -> CGSize {

        width.constant = targetSize.width

        let size = contentView.systemLayoutSizeFitting(
            CGSize(width: targetSize.width, height: 1),
            withHorizontalFittingPriority: .required,
            verticalFittingPriority: verticalFittingPriority)

        print("\(#function) \(#line) \(targetSize) -> \(size)")
        return size
    }

В этом вопросе есть несколько дубликатов, я подробно ответил на него здесь и предоставил рабочий образец приложения здесь.

Крис Коновер
источник
2

Не уверен, что это можно назвать «действительно хорошим ответом», но я использую именно его для этого. Мой макет потока горизонтальный, и я пытаюсь настроить ширину с помощью автоматического макета, так что он похож на вашу ситуацию.

extension PhotoAlbumVC: UICollectionViewDelegateFlowLayout {
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // My height is static, but it could use the screen size if you wanted
    return CGSize(width: collectionView.frame.width - sectionInsets.left - sectionInsets.right, height: 60) 
  }
}

Затем в контроллере представления, где изменяется ограничение автоматического размещения, я запускаю NSNotification.

NotificationCenter.default.post(name: NSNotification.Name("constraintMoved"), object: self, userInfo: nil)

В моем подклассе UICollectionView я прослушиваю это уведомление:

// viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(handleConstraintNotification(notification:)), name: NSNotification.Name("constraintMoved"), object: nil)

и аннулируем макет:

func handleConstraintNotification(notification: Notification) {
    self.collectionView?.collectionViewLayout.invalidateLayout()
}

Это вызывает sizeForItemAtповторный вызов с использованием нового размера представления коллекции. В вашем случае он должен иметь возможность обновляться с учетом новых ограничений, доступных в макете.

Марк Суман
источник
просто TBC Марк значит , ты на самом деле изменения размера ячейки? (то есть появляется ячейка размером X, затем что-то происходит в вашем приложении, и оно становится размером Y ..?)
THX
Да. Когда пользователь перетаскивает элемент на экран, возникает событие, обновляющее размер ячейки.
Марк Суман
1

На вашем viewDidLayoutSubviewsустановите estimatedItemSizeполную ширину (макет относится к объекту UICollectionViewFlowLayout):

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.bounds.size.width, height: 120)
}

В ячейке убедитесь, что ваши ограничения касаются как верхней, так и нижней части ячейки (в следующем коде используется картография для упрощения установки ограничений, но вы можете сделать это с помощью NSLayoutConstraint или IB, если хотите):

constrain(self, nameLabel, valueLabel) { view, name, value in
        name.top == view.top + 10
        name.left == view.left
        name.bottom == view.bottom - 10
        value.right == view.right
        value.centerY == view.centerY
    }

Вуаля, клетки теперь автоматически увеличиваются в высоту!

Рафаэль
источник
0

Ни одно из решений не помогло мне, так как мне нужна динамическая ширина, чтобы адаптироваться к ширине iPhone.

    class CustomLayoutFlow: UICollectionViewFlowLayout {
        override init() {
            super.init()
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        override var itemSize: CGSize {
            set { }
            get {
                let width = (self.collectionView?.frame.width)!
                let height = (self.collectionView?.frame.height)!
                return CGSize(width: width, height: height)
            }
        }
    }

    class TextCollectionViewCell: UICollectionViewCell {
        @IBOutlet weak var textView: UITextView!

        override func prepareForReuse() {
            super.prepareForReuse()
        }
    }




    class IntroViewController: UIViewController, UITextViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UINavigationControllerDelegate {
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionView: UICollectionView!
        var collectionViewLayout: CustomLayoutFlow!

        override func viewDidLoad() {
            super.viewDidLoad()

            self.collectionViewLayout = CustomLayoutFlow()
            self.collectionView.collectionViewLayout = self.collectionViewLayout
        }

        override func viewWillLayoutSubviews() {
            self.collectionViewTopDistanceConstraint.constant = UIScreen.main.bounds.height > 736 ? 94 : 70

            self.view.layoutIfNeeded()
        }
    }
iOS Flow
источник
0

Начиная с iOS 10, для этого у нас появился новый API-интерфейс потокового макета.

Все, что вам нужно сделать, это установить flowLayout.estimatedItemSizeновую константу UICollectionViewFlowLayoutAutomaticSize.

Источник

Арпит Донгре
источник
Хм, вы уверены, что у него * полная ширина ? Итак, по сути, один столбец?
Fattie
Возможно, если вы явно установите ширину UICollectionViewCell на полную ширину.
Arpit Dongre
Нет, он не фиксирует ширину до полной высоты и игнорирует предполагаемое значение размера, если вы не следуете ответу Эрика выше или моему ниже / позже.
Крис Коновер,
Хорошо, к сожалению, это не имеет никакого отношения к проблеме! хе! : O
Fattie 04
0

AutoLayout можно использовать для автоматического изменения размера ячеек в CollectionView за 2 простых шага:

  1. Включение динамического изменения размера ячеек

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Создайте представление контейнера и установите для containerView.widthAnchor.constraint значение от, collectionView(:cellForItemAt:)чтобы ограничить ширину contentView шириной collectionView.
class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            containerViewWidthAnchor.constant = maxWidth
            containerViewWidthAnchor.isActive = true
        }
    }

    ....
}

Вот и вы получите желаемый результат. Полный код см. В следующих статьях:

Ссылки / Кредиты:

Скриншот: введите описание изображения здесь

JolasJoe
источник
-6

Вы должны унаследовать класс UICollectionViewDelegateFlowLayout в своей коллекцииViewController. Затем добавьте функцию:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width, height: 100)
}

Используя это, вы получаете размер ширины экрана.

И теперь у вас есть collectionViewController со строками в виде tableViewController.

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

образование
источник
7
Это точно не ответ на вопрос :) Это дало бы фиксированную высоту 100, то есть так программировали до того, как автопрокладывание существовало.
Fattie 05