UICollectionView, клітини повної ширини, дозволяють динамічну висоту автоматичного вимикання?


120

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

Чи можливо мати осередки повної ширини , але дозволити динамічну висоту контролювати за допомогою авторозмітки ?

Це вважає мене, мабуть, "найважливішим питанням в iOS, що не має дійсно гарної відповіді".


Важливо:

Зауважте, що в 99% випадків, щоб досягти повної ширини комірок + динамічна висота автоматичного розкладу , просто використовуйте подання таблиці. Це так просто.


То який приклад, де вам потрібен перегляд колекції?

Перегляди колекції неймовірно потужніші, ніж представлення таблиць.

Один простий приклад, коли вам потрібно використовувати подання колекції, щоб досягти повної ширини комірок + динамічна висота авторозмітки:

Якщо ви анімуєте між двома макетами у вікні колекції. Наприклад, між компонуванням 1 та 2 стовпців, коли пристрій обертається.

Це звичайна і нормальна ідіома в iOS. На жаль, це може бути досягнуто лише шляхом вирішення проблеми, поставленої в цій якості. : - /


так можливо з класом UICollectionViewDelegateFlowLayout
Sunil Prajapati

хіба це не означає, що ви насправді можете використовувати tableView? яка додаткова функціональність вам потрібна, щоб це ускладнювати?
Каталіна Т.

1
Привіт @CatalinaT. , перегляди колекції мають багато, багато, багато додаткових функцій над поданнями таблиць. Простий приклад - додавання фізики. (Якщо ви не знаєте, що я маю на увазі, це те, як ви складаєте списки "бадьорі" в iOS.)
Fattie

ви також можете якось застосувати фізику (як SpringDampingі initialSpringVelocity) у комірці таблиці ..... подивіться на цей підстрибуючий список за допомогою tableView ,,,, pastebin.com/pFRQhkrX Ви можете анімувати TableViewCellтаблицю в willDisplayCell:(UITableViewCell *)cellметоді delgate @Fattie ви можете прийняти відповідь, яка вирішила вашу проблему, допомогти іншим, хто шукає цю ж проблему,
Dhiru

ок, ви спробували список підстрибуючих, використовуючи перегляд списку? @Fattie
Dhiru

Відповіді:


123

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

За допомогою Swift 5.1 та iOS 13 ви можете використовувати об'єкти компонування композиції для вирішення вашої проблеми.

Наступний повний зразок коду показує, як відобразити багаторядкові 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. Чи стикалися ви з додаванням заголовків розділів?
Nakul

Дуже дякую. Ти врятував мій тиждень. 🙇‍♂️
Гаетан

1
CollectionView прокручує доверху, коли змінюється розмір
Sajith

1
фантастичні новини, @ImanouPetit! Бажаю, щоб щедрості були більшими! Фантастична інформація про об’єкти компонування композиції, дякую.
Fattie

1
Привіт, я пробую це. Незважаючи на те, що я встановлюю макет у viewdidload, як ви робили вище, я отримую збої, вказуючи, що колекція перегляду повинна бути ініціалізована параметром макета, що не має нульового значення. Щось мені не вистачає? Це 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делегата у свій переглядController

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

Я хочу досягти такої ж, але половини ширини та динамічної висоти
Міхір Мехта

return CGSize(width: view.frame.width/2, height: expectedHeight)також врахуйте прокладку, ніж поверніть ширину, інакше дві комірки не вмістяться в один ряд.
Дхіру

@ nr5 Ви отримали рішення? Моя вимога така ж, як і ваша. Дякую.
Маулік Бхуптані

@MaulikBhuptani Я не зміг зробити це повністю з автоматичним макетом. Довелося створити спеціальний клас макета та замінити деякі методи класу.
nr5

18

Є кілька способів вирішити цю проблему.

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

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

(якщо припустимо, що ви використовуєте мовленнєві дошки, а представлення колекції підключено через 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
(включаючи або не включаючи оціненийItemSize не має жодних змін у поточному iOS, і взагалі не допомагає у питанні "повна ширина + динамічна висота"). Код в кінці, внизу рідко використовуються в ці дні, і він має мало стосується динамічної висоти (наприклад, з декількох переглядів тексту динамічної висоти). спасибі, хоча
Fattie

Я також прийшов до цього рішення, але в моєму випадку, iOS 11, Xcode 9.2, вміст комірки здавалося відключеним від самої комірки - я не міг отримати зображення, яке розшириться на повну висоту або ширину фіксованого розміру. Далі я зауважив, що резюме - це обмеження в налаштуваннях preferredLayoutAttributesFitting. Потім я додав власне обмеження у вказаній функції, прив'язуючись до фіксованого розміру з оцінковогоItemSize, 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

Результат - це клітинка повної ширини з динамічною висотою.

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


1
свята корова - це дивовижно! так очевидно! блискучий!
Fattie

1
просто TBC @ inf1783, ви це робите в UICollectionView? (не вид таблиці) - правильно?
Fattie

1
@Fattie Так, це UICollectionView;)
inf1783

фантастичний @ inf1783, не дочекаюся, щоб спробувати це. До речі, ще один хороший спосіб зробити це - підкласировать UIView і просто додати внутрішню ширину (я здогадуюсь, це матиме такий же ефект, і, мабуть, це також змусить працювати у часі розкадровки)
Fattie

1
Кожен раз, коли я намагаюся це, я опиняюся в нескінченному циклі з поведінкою UICollectionViewFlowLayout не визначається помилка: /
Skodik.o

7

РОБОТА !!! Тестовано на IOS: 12.1 Швидкий 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
  }
}

нарешті хтось зробив його ідеальним у двох рядках
Омар Н Шамалі

Це обмежить клітинку фіксованою шириною, навряд чи самостійно розміром. Якщо ви повинні повернути пристрій або відрегулювати розмір екрана на 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 визначає розмір, а кожна комірка може мати різний розмір - це реалізувати функцію розміру UICollectionViewDelegateFlowLayoutForItemAtIndexPath, використовуючи фактичну клітинку для вимірювання розміру.

Я говорив про це в одному зі своїх публікацій в блозі

Сподіваємось, ця допоможе вам досягти того, чого ви хочете. Я не на 100% впевнений, але я вважаю, на відміну від UITableView, де ви можете реально мати повністю автоматичну висоту комірок, використовуючи невід'ємну функцію AutoLayout з

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

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

Але ось для вас питання : Якщо вам потрібні клітинки на весь екран, чому ви взагалі турбуєтесь за допомогою UICollectionView над старим хорошим UITableView, який поставляється з клітинками автоматичного розміру?


тримати його, Aprit нижче говорить , що UICollectionViewFlowLayoutAutomaticSize тепер доступний в макеті потоку, в iOS10 ...
Fattie

Я теж щойно це бачив. Мабуть, це вже зараз в iOS 10. Я просто спостерігав, як відповідна WWDC розмовляє з цього приводу: developer.apple.com/videos/play/wwdc2016/219 Тим не менш, це повністю автоматизує клітинку, щоб відповідати її вмісту. Я не впевнений, як ви могли сказати клітині (за допомогою AutoLayout) заповнити ширину екрана, оскільки ви не можете встановити обмеження між UICollectionViewCell та його батьківським (хоча б не в StoryBoards)
xxtesaxx

правильно. нам здається, що ми не наближаємось до фактичного вирішення цієї неймовірно очевидної проблеми. : /
Fattie

Згідно з розмовою, ви все одно можете перезаписати розмірThatFits () або предпочитаюLayoutAttributesFitting () та обчислити розмір самостійно, але це не те, що просив ОП, я думаю. У будь-якому разі, я все ще зацікавився б випадком використання, коли йому / їй потрібен UICollectionViewCell на повну ширину, і чому було б такою великою справою не використовувати UITableView у цьому конкретному випадку (впевнений, що можуть бути певні випадки, але тоді я здогадуюсь ви просто повинні самі розібратися з деякими розрахунками)
xxtesaxx

задумавшись про це, зовсім неймовірно, що ви не можете просто встановити "колонки == 1" (а потім, можливо, стовпці == 2, коли пристрій знаходиться збоку), з видами колекції.
Fattie

3

На мій коментар до відповіді Еріка, моє рішення дуже схоже на його, але мені довелося додати обмеження у preferenceSizeFor ..., щоб обмежитися фіксованим виміром.

    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 Mark Ви маєте на увазі, що ви насправді змінюєте розмір комірки? (тобто клітина з’являється і вона розміром X, тоді у вашій програмі щось відбувається, і вона стає розміром Y ..?) thx
Fattie

Так. Коли користувач перетягує елемент на екран, запускається подія, яка оновлює розмір комірки.
Марк Суман

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

0

З iOS 10 у нас з'явився новий API на макеті потоку для цього.

Все , що вам потрібно зробити , це встановити ваші flowLayout.estimatedItemSizeдо нової постійної, UICollectionViewFlowLayoutAutomaticSize.

Джерело


Хм, ви майже впевнені, що це робить * повну ширину ? Отже, по суті одна колонка?
Fattie

Можливо, якщо явно встановити ширину UICollectionViewCell на повну ширину.
Арпіт Донгре

Ні, він не фіксує ширину на повний зріст і ігнорує оцінене значення розміру, якщо ви не дотримуєтесь відповіді Еріка вище, або мої нижче / пізніше.
Кріс Коновер

Добре, на жаль, тоді це абсолютно не пов'язано з проблемою! хе! : O
Fattie

0

Автоматичне розміщення можна використовувати для автоматичного розміру комірок у CollectionView за два простих кроки:

  1. Увімкнення динамічного розміру комірок

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Майте перегляд контейнера і встановіть контейнер контейнерView.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
        }
    }

    ....
}

Ось це ви отримаєте бажаний результат. Щоб отримати повний код, перегляньте наступні списки:

Довідка / Кредити:

Знімок екрана: введіть тут опис зображення


-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.