UICollectionView, sel lebar penuh, memungkinkan tinggi dinamis tata letak otomatis?

120

Secara vertikal UICollectionView,

Apakah mungkin untuk memiliki sel lebar penuh , tetapi, biarkan tinggi dinamis dikontrol oleh tata letak otomatis ?

Ini menurut saya mungkin sebagai "pertanyaan paling penting di iOS tanpa jawaban yang benar-benar bagus".


Penting:

Perhatikan bahwa dalam 99% kasus, untuk mencapai lebar penuh sel + tinggi dinamis tata letak otomatis, cukup gunakan tampilan tabel. Semudah itu.


Jadi, apa contoh di mana Anda membutuhkan tampilan koleksi?

Tampilan koleksi jauh lebih kuat daripada tampilan tabel.

Satu contoh langsung di mana Anda benar - benar harus menggunakan tampilan koleksi, untuk mencapai sel lebar penuh + tinggi dinamis tata letak otomatis:

Jika Anda menganimasikan antara dua tata letak dalam tampilan koleksi. Misalnya, antara tata letak kolom 1 dan 2, saat perangkat berputar.

Itu idiom umum dan normal di iOS. Sayangnya itu hanya dapat dicapai dengan menyelesaikan masalah yang diajukan dalam QA ini. : - /

Gendut
sumber
ya dimungkinkan dengan kelas UICollectionViewDelegateFlowLayout
Sunil Prajapati
bukankah ini berarti Anda benar-benar dapat menggunakan tableView? fungsi tambahan apa yang diperlukan bagi Anda, untuk memperumit masalah ini?
Catalina T.
1
Hai @CatalinaT. , tampilan koleksi memiliki banyak, banyak, banyak fitur tambahan di atas tampilan tabel. Contoh sederhananya adalah menambahkan fisika. (Jika Anda tidak tahu apa yang saya maksud, begitulah cara Anda membuat daftar "goyang" di iOS.)
Fattie
Anda juga dapat Menerapkan entah bagaimana fisika (seperti SpringDampingdan initialSpringVelocity) di tabel Cell ..... lihat Daftar Bouncy ini menggunakan tableView ,,,, pastebin.com/pFRQhkrX Anda dapat menganimasikan TableViewCelltabel dalam willDisplayCell:(UITableViewCell *)cellmetode delgate @Fattie yang dapat Anda terima jawaban yang memecahkan masalah Anda untuk membantu orang lain yang mencari masalah yang sama
Dhiru
ohk, apakah Anda mencoba daftar Bouncy menggunakan tampilan daftar? @Fattie
Dhiru

Jawaban:

124

1. Solusi untuk iOS 13+

Dengan Swift 5.1 dan iOS 13, Anda dapat menggunakan objek Tata Letak Komposisi untuk memecahkan masalah Anda.

Kode contoh lengkap berikut menunjukkan cara menampilkan multiline UILabeldi dalam lebar penuh 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")
    }

}

Tampilan yang diharapkan:

masukkan deskripsi gambar di sini


2. Solusi untuk iOS 11+

Dengan Swift 5.1 dan iOS 11, Anda dapat membuat subkelas UICollectionViewFlowLayoutdan menyetel estimatedItemSizepropertinya ke UICollectionViewFlowLayout.automaticSize(ini memberi tahu sistem bahwa Anda ingin menangani autoresisasi UICollectionViewCell). Anda kemudian harus mengganti layoutAttributesForElements(in:)dan layoutAttributesForItem(at:)mengatur lebar sel. Terakhir, Anda harus mengganti preferredLayoutAttributesFitting(_:)metode sel Anda dan menghitung tingginya.

Kode lengkap berikut menunjukkan cara menampilkan multiline UILabeldi dalam lebar penuh UIcollectionViewCell(dibatasi oleh UICollectionViewarea aman dan UICollectionViewFlowLayoutsisipan):

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
    }

}

Berikut beberapa implementasi alternatif untuk 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
}

Tampilan yang diharapkan:

masukkan deskripsi gambar di sini

Imanou Petit
sumber
Ini bekerja dengan baik untuk saya sampai saya menambahkan header bagian. Header bagian salah tempat dan menutupi sel lain di iOS 11. Tetapi berfungsi dengan baik di iOS 12. Apakah Anda menghadapi masalah dengan menambahkan header bagian?
Nakul
Terima kasih banyak. Anda menyelamatkan minggu saya. 🙇‍♂️
Gaetan
1
CollectionView menggulung ke atas setiap kali ukurannya berubah
Sajith
1
berita fantastis, @ImanouPetit! Saya berharap hadiahnya lebih besar! Informasi fantastis tentang objek Tata Letak Komposisi, terima kasih.
Fattie
1
Hai, saya sedang mencoba ini. Meskipun saya menyetel layout di viewdidload seperti yang Anda lakukan di atas, saya mendapatkan error yang menyatakan bahwa collectionview harus diinisialisasi dengan parameter layout non-nil. Apakah ada sesuatu yang saya lewatkan? Ini adalah Xcode 11 untuk iOS 13.
geistmate
29

Masalah

Anda mencari tinggi otomatis dan juga ingin memiliki lebar penuh, tidak mungkin untuk menggunakan keduanya UICollectionViewFlowLayoutAutomaticSize.

Anda ingin menggunakan UICollectionViewjadi di bawah ini adalah solusi untuk Anda.

Larutan

Langkah-I : Hitung ketinggian Cell yang diharapkan

1. Jika Anda memiliki hanya UILabel dalam CollectionViewCelldari yang ditetapkan numberOfLines=0dan yang menghitung ketinggian yang diharapkan UIlable, lewati ketiga parameter

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. Jika Anda CollectionViewCellhanya berisi UIImageViewdan jika itu seharusnya dinamis dalam Ketinggian daripada yang Anda butuhkan untuk mendapatkan ketinggian UIImage (Anda UIImageViewharus memiliki AspectRatiokendala)

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

3. Jika mengandung keduanya maka hitung tinggi mereka dan jumlahkan keduanya.

LANGKAH-II : Kembalikan UkuranCollectionViewCell

1. Tambahkan UICollectionViewDelegateFlowLayoutdelegasi di viewController Anda

2. Menerapkan metode delegasi

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

Saya harap ini akan membantu Anda.

Dhiru
sumber
1
Apakah mungkin memiliki UICollectionView scrolling horizontal dengan ketinggian sel meningkat secara vertikal dengan tata letak otomatis?
nr5
Saya ingin mencapai lebar setengah dan tinggi yang sama tapi dinamis
Mihir Mehta
return CGSize(width: view.frame.width/2, height: expectedHeight)Juga pertahankan Padding dalam akun daripada mengembalikan lebar jika tidak dua sel tidak akan muat dalam satu baris.
Dhiru
@ nr5 Apakah Anda mendapatkan solusinya? Persyaratan saya sama dengan kebutuhan Anda. Terima kasih.
Maulik Bhuptani
@MaulikBhuptani Saya tidak dapat melakukannya sepenuhnya dengan Tata Letak Otomatis. Harus membuat kelas Layout kustom dan mengganti beberapa metode kelas.
nr5
18

Ada beberapa cara untuk mengatasi masalah ini.

Salah satu caranya adalah Anda dapat memberi tata letak aliran tampilan koleksi perkiraan ukuran dan menghitung ukuran sel.

Catatan: Seperti yang disebutkan dalam komentar di bawah, mulai iOS 10 Anda tidak perlu lagi menyediakan dan memperkirakan ukuran untuk memicu panggilan ke sel func preferredLayoutAttributesFitting(_ layoutAttributes:). Sebelumnya (iOS 9) akan meminta Anda untuk memberikan perkiraan ukuran jika Anda ingin meminta sel prefferedLayoutAttributes.

(dengan asumsi Anda menggunakan papan cerita dan tampilan koleksi terhubung melalui IB)

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

Untuk sel sederhana itu biasanya sudah cukup. Jika ukurannya masih salah, dalam sel tampilan koleksi Anda dapat mengganti func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes, yang akan memberi Anda kontrol butiran yang lebih halus atas ukuran sel. Catatan: Anda masih perlu memberikan perkiraan ukuran pada tata letak aliran .

Kemudian ganti func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributesuntuk mengembalikan ukuran yang benar.

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
}

Alternatifnya, sebagai gantinya Anda dapat menggunakan sel ukuran untuk menghitung ukuran sel di file 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)
}

Meskipun ini bukan contoh siap produksi yang sempurna, ini akan membantu Anda memulai dengan arah yang benar. Saya tidak bisa mengatakan ini adalah praktik terbaik, tetapi ini berfungsi untuk saya, bahkan dengan sel yang cukup kompleks yang berisi banyak label, yang mungkin atau mungkin tidak membungkus ke beberapa baris.

Eric Murphey
sumber
@Fattie Tidak jelas bagaimana 'esitmatedItemSize' tidak relevan dengan sel tampilan koleksi berukuran dinamis, jadi saya ingin mendengar pendapat Anda. (bukan sarkasme)
Eric Murphey
Selain itu, jika jawaban ini tidak memiliki sesuatu yang spesifik untuk membantu dan / atau apa yang Anda anggap kanonik, beri tahu saya. Saya tidak akan membantah hadiahnya, saya hanya ingin membantu siapa pun yang melihat pertanyaan ini.
Eric Murphey
1
(termasuk atau tidak termasuk estimasiItemSize tidak membuat perbedaan di iOS saat ini, dan tidak membantu sama sekali, dalam masalah "lebar penuh + tinggi dinamis".) kode di akhir, ujung pena jarang digunakan hari ini dan memiliki sedikit relevansi dengan tinggi dinamis (dari, misalnya, beberapa tampilan teks tinggi dinamis). terima kasih meskipun
Fattie
Saya sampai pada solusi ini juga, tetapi dalam kasus saya, iOS 11, Xcode 9.2, konten sel tampaknya terputus dari sel itu sendiri - Saya tidak bisa mendapatkan gambar untuk diperluas ke tinggi atau lebar penuh dari dimensi tetap. Saya lebih lanjut memperhatikan bahwa CV adalah batasan pengaturan di preferredLayoutAttributesFitting. Saya kemudian menambahkan batasan saya sendiri dalam fungsi tersebut, mengikat dimensi tetap dari estimItemSize, FWIW, lihat jawaban saya di bawah.
Chris Conover
16

Saya menemukan solusi yang cukup mudah untuk masalah itu: Di dalam CollectionViewCell saya, saya mendapat UIView () yang sebenarnya hanya latar belakang. Untuk mendapatkan lebar penuh saya hanya mengatur Jangkar berikut

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

"Keajaiban" terjadi di baris pertama. Saya mengatur widthAnchor secara dinamis ke lebar layar. Yang juga penting adalah mengurangi insets CollectionView Anda. Jika tidak, sel tidak akan muncul. Jika Anda tidak ingin memiliki tampilan latar belakang seperti itu, buat saja tidak terlihat.

FlowLayout menggunakan pengaturan berikut

layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

Hasilnya adalah sel berukuran lebar penuh dengan tinggi dinamis.

masukkan deskripsi gambar di sini

inf1783
sumber
1
Astaga - itu luar biasa! sangat jelas ! brilian!
Fattie
1
hanya TBC @ inf1783, Anda melakukan ini di UICollectionView? (bukan tampilan tabel) - benar?
Gendut
1
@Fattie Ya, ini UICollectionView;)
inf1783
fantastic @ inf1783, tidak sabar untuk mencobanya. Ngomong-ngomong, cara lain yang baik untuk melakukannya adalah dengan membuat subkelas UIView dan cukup menambahkan lebar intrinsik (saya rasa itu akan memiliki efek yang sama, dan itu mungkin akan membuatnya bekerja pada waktu storyboard juga)
Fattie
1
Setiap kali saya mencoba ini, saya berakhir di loop tanpa akhir dengan Perilaku UICollectionViewFlowLayout tidak didefinisikan kesalahan: /
Skodik.o
7

KERJA!!! Diuji di IOS: 12.1 Swift 4.1

Saya memiliki solusi yang sangat sederhana yang hanya berfungsi tanpa pemutusan kendala.

masukkan deskripsi gambar di sini

ViewControllerClass saya

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
    }


}

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

}

Bahan utama adalah systemLayoutSizeFitting fungsi dalam Customcell. Dan juga kita harus mengatur lebar tampilan di dalam Sel dengan batasan.

Abhishek Biswas
sumber
6

Anda harus menambahkan batasan lebar ke CollectionViewCell

class SelfSizingCell: UICollectionViewCell {

  override func awakeFromNib() {
      super.awakeFromNib()
      contentView.translatesAutoresizingMaskIntoConstraints = false
      contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
  }
}
Mecid
sumber
akhirnya seseorang membuatnya sempurna dalam dua baris
Omar N Shamali
Ini akan membatasi sel menjadi lebar tetap, hampir tidak bisa mengukur sendiri. Jika Anda memutar perangkat atau menyesuaikan ukuran layar di iPad, itu akan tetap diperbaiki.
Thomas Verbeek
4
  1. Set estimatedItemSizetata letak aliran Anda:

    collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
  2. Tentukan batasan lebar dalam sel dan setel agar sama dengan lebar tampilan super:

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

Contoh lengkapnya

Diuji di iOS 11.

Roma
sumber
3

Secara pribadi saya menemukan cara terbaik untuk memiliki UICollectionView di mana AutoLayout menentukan ukuran sementara setiap Sel dapat memiliki ukuran yang berbeda adalah dengan mengimplementasikan fungsi UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath saat menggunakan Cell yang sebenarnya untuk mengukur ukurannya.

Saya membicarakan hal ini di salah satu postingan blog saya

Semoga yang satu ini akan membantu Anda mencapai apa yang Anda inginkan. Saya tidak 100% yakin tetapi saya percaya tidak seperti UITableView di mana Anda benar-benar dapat memiliki ketinggian sel otomatis sepenuhnya dengan menggunakan AutoLayout yang tidak berhubungan dengan

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionView tidak memiliki cara yang memungkinkan AutoLayout menentukan ukuran karena UICollectionViewCell tidak harus memenuhi seluruh lebar layar.

Tapi ini pertanyaan untuk Anda : Jika Anda membutuhkan sel lebar layar penuh, mengapa Anda repot-repot menggunakan UICollectionView daripada UITableView lama yang bagus yang dilengkapi dengan sel ukuran otomatis?

xxtesaxx
sumber
tahan, Aprit di bawah ini mengatakan bahwa UICollectionViewFlowLayoutAutomaticSize sekarang tersedia dalam tata letak aliran, di iOS10 ...
Fattie
Saya juga baru saja melihat ini. Rupanya itu adalah hal yang sekarang di iOS 10. Saya baru saja menonton pembicaraan WWDC yang sesuai tentang ini: developer.apple.com/videos/play/wwdc2016/219 Namun, ini akan mengubah ukuran sel sepenuhnya agar sesuai dengan kontennya. Saya tidak yakin bagaimana Anda bisa memberi tahu sel (dengan AutoLayout) untuk mengisi lebar layar karena Anda tidak dapat mengatur batasan antara UICollectionViewCell dan induknya (setidaknya tidak di StoryBoards)
xxtesaxx
Baik. kita tampaknya tidak mendekati solusi aktual untuk masalah yang sangat jelas ini. : /
Fattie
Menurut pembicaraan, Anda masih bisa menimpa sizeThatFits () atau preferLayoutAttributesFitting () dan menghitung ukurannya sendiri tetapi menurut saya bukan itu yang diminta OP. Bagaimanapun, saya masih akan tertarik dengan kasus penggunaan ketika dia membutuhkan UICollectionViewCell lebar penuh dan mengapa akan menjadi masalah besar untuk tidak menggunakan UITableView dalam kasus khusus ini (tentu saja bisa ada kasus tertentu tetapi kemudian saya kira Anda hanya perlu melakukan beberapa perhitungan sendiri)
xxtesaxx
memikirkannya, sungguh luar biasa bahwa Anda tidak bisa begitu saja menyetel "kolom == 1" (dan mungkin kolom == 2 saat perangkat miring), dengan tampilan koleksi.
Fattie
3

Menurut komentar saya atas jawaban Eric, solusi saya sangat mirip dengan miliknya, tetapi saya harus menambahkan batasan di preferSizeFor ... untuk membatasi ke dimensi tetap.

    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
    }

Pertanyaan ini memiliki sejumlah duplikat, saya menjawabnya secara mendetail di sini , dan memberikan contoh aplikasi yang berfungsi di sini.

Chris Conover
sumber
2

Tidak yakin apakah ini memenuhi syarat sebagai "jawaban yang sangat bagus", tapi itulah yang saya gunakan untuk mencapai ini. Tata letak aliran saya horizontal, dan saya mencoba menyesuaikan lebar dengan tata letak otomatis, jadi ini mirip dengan situasi Anda.

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

Kemudian di pengontrol tampilan tempat batasan autolayout diubah, saya mengaktifkan NSNotification.

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

Di subkelas UICollectionView saya, saya mendengarkan pemberitahuan itu:

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

dan membatalkan tata letak:

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

Hal ini menyebabkan sizeForItemAtdipanggil lagi menggunakan ukuran baru tampilan koleksi. Dalam kasus Anda, ini harus dapat diperbarui dengan batasan baru yang tersedia di tata letak.

Mark Suman
sumber
hanya TBC Mark maksud Anda Anda benar-benar mengubah ukuran sel? (mis., sel muncul dan ukurannya X, lalu sesuatu terjadi di aplikasi Anda dan menjadi ukuran Y ..?) thx
Fattie
Iya. Saat pengguna menyeret elemen di layar, sebuah peristiwa yang terjadi akan memperbarui ukuran sel.
Mark Suman
1

Di Anda viewDidLayoutSubviews, setel estimatedItemSizeke lebar penuh (tata letak mengacu pada objek UICollectionViewFlowLayout):

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

Di sel Anda, pastikan bahwa batasan Anda menyentuh bagian atas dan bawah sel (kode berikut menggunakan Kartografi untuk menyederhanakan pengaturan batasan tetapi Anda dapat melakukannya dengan NSLayoutConstraint atau IB jika Anda mau):

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
    }

Voila, sel-sel Anda sekarang akan tumbuh otomatis!

Raphael
sumber
0

Tidak ada solusi yang berfungsi untuk saya karena saya membutuhkan lebar dinamis untuk menyesuaikan antara lebar 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()
        }
    }
Arus iOS
sumber
0

Dari iOS 10, kami memiliki API baru tentang tata letak aliran untuk melakukannya.

Yang harus Anda lakukan adalah menyetel flowLayout.estimatedItemSizeke konstanta baru UICollectionViewFlowLayoutAutomaticSize,.

Sumber

Arpit Dongre
sumber
Hmm, Anda cukup yakin itu membuatnya * lebar penuh ? Jadi, intinya satu kolom?
Fattie
Mungkin jika Anda menyetel lebar UICollectionViewCell menjadi lebar penuh secara eksplisit.
Arpit Dongre
Tidak, itu tidak memperbaiki lebar menjadi tinggi penuh, dan mengabaikan nilai perkiraan ukuran kecuali Anda mengikuti jawaban Eric di atas, atau milik saya di bawah / nanti.
Chris Conover
Oke, sayangnya ini sama sekali tidak ada hubungannya dengan masalah! heh! : O
Fattie
0

AutoLayout dapat digunakan untuk mengubah ukuran sel secara otomatis di CollectionView dalam 2 langkah mudah:

  1. Mengaktifkan ukuran sel dinamis

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Miliki tampilan kontainer dan setel containerView.widthAnchor.constraint dari collectionView(:cellForItemAt:)untuk membatasi lebar contentView menjadi lebar 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
        }
    }

    ....
}

Itu saja Anda akan mendapatkan hasil yang diinginkan. Lihat inti berikut untuk kode lengkap:

Referensi / Kredit:

Tangkapan layar: masukkan deskripsi gambar di sini

JolasJoe
sumber
-6

Anda harus mewarisi kelas UICollectionViewDelegateFlowLayout di collectionViewController Anda. Kemudian tambahkan fungsinya:

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

Menggunakan itu, Anda memiliki ukuran lebar dari lebar layar.

Dan sekarang Anda memiliki collectionViewController dengan baris sebagai tableViewController.

Jika Anda ingin ukuran tinggi setiap sel menjadi dinamis, mungkin Anda harus membuat sel khusus untuk setiap sel yang Anda butuhkan.

Edu
sumber
7
Ini sebenarnya bukan jawaban untuk pertanyaan :) Itu akan memberikan ketinggian tetap 100, yaitu, bagaimana seseorang diprogram bahkan sebelum autolayout ada.
Fattie