UICollectionView, tam genişlikli hücreler, otomatik düzen dinamik yüksekliğe izin veriyor mu?


120

Dikey olarak UICollectionView ,

Tam genişlikte hücrelere sahip olmak mümkün mü , ancak dinamik yüksekliğin otomatik düzen tarafından kontrol edilmesine izin veriyor mu? mu?

Bu bana belki de "iOS'ta gerçekten iyi bir cevabı olmayan en önemli soru" olarak dikkat çekiyor.


Önemli:

Vakaların% 99'unda, tam genişlikte hücreler + otomatik düzen dinamik yüksekliği elde etmek için bir tablo görünümü kullanmanız yeterlidir . İşte bu kadar kolay.


Peki, koleksiyon görünümüne ihtiyaç duyduğunuz yerin bir örneği nedir?

Koleksiyon görünümleri, tablo görünümlerinden inanılmaz derecede daha güçlüdür.

Aslında yapmanız gereken basit bir örnekTam genişlikte hücreler + otomatik düzen dinamik yükseklik elde etmek bir koleksiyon görünümü kullanmanız :

Eğer canlandırırsanBir koleksiyon görünümünde iki düzen arasında . Örneğin, cihaz döndüğünde 1 ve 2 sütun düzeni arasında.

Bu, iOS'ta yaygın ve normal bir deyimdir. Ne yazık ki, bu yalnızca bu kalite güvencesinde ortaya çıkan problemi çözerek başarılabilir. : - /


evet, UICollectionViewDelegateFlowLayout sınıfı ile mümkündür
Sunil Prajapati

bu gerçekten bir tableView kullanabileceğiniz anlamına gelmez mi? Bunu aşırı karmaşık hale getirmek için sizin için hangi ek işlevler gerekli olabilir?
Catalina T.

1
Merhaba @CatalinaT. Koleksiyon görünümleri, tablo görünümleri üzerinde çok, çok, birçok ek özelliğe sahiptir. Basit bir örnek, fizik eklemektir. (Ne demek istediğimi bilmiyorsanız, bu iOS'ta "
zıplama

Ayrıca her nasılsa fizik (gibi uygulayın edebilir SpringDampingve initialSpringVelocitytableView kullanarak bu Bouncy Listesinde de bakabilirsiniz ..... tablo Cell) ,,,, pastebin.com/pFRQhkrX Canlandırmak olabilir TableViewCelltabloda willDisplayCell:(UITableViewCell *)cellkabul edebilir @Fattie delgate yöntemiyle aynı sorunu arayanlara yardım etmek için probleminizi çözen cevap
Dhiru 05

ohk, liste görünümünü kullanarak Bouncy listesini denedin mi? @Fattie
Dhiru

Yanıtlar:


124

1. iOS 13+ için Çözüm

Swift 5.1 ve iOS 13 ile Compositional Layout nesnelerini kullanabilirsiniz probleminizi çözmek için .

Aşağıdaki eksiksiz örnek kod, UILabeltam genişlikte çoklu satırın nasıl görüntüleneceğini gösterir 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")
    }

}

Beklenen ekran:

görüntü açıklamasını buraya girin


2. iOS 11+ için Çözüm

Swift 5.1 ve iOS 11 ile, alt sınıfa ayırabilir UICollectionViewFlowLayoutve estimatedItemSizeözelliğini olarak ayarlayabilirsiniz UICollectionViewFlowLayout.automaticSize(bu, sisteme otomatik yeniden boyutlandırma işlevleriyle uğraşmak istediğinizi söyler UICollectionViewCell). Daha sonra hücre genişliğini ayarlamak için layoutAttributesForElements(in:)ve geçersiz layoutAttributesForItem(at:)kılmanız gerekir. Son olarak, hücrenizin preferredLayoutAttributesFitting(_:)yöntemini geçersiz kılmanız ve yüksekliğini hesaplamanız gerekir.

Aşağıdaki tam kod, çok satırlı UILabeltam genişlikte nasıl görüntüleneceğini gösterir UIcollectionViewCell( UICollectionViewgüvenli alan ve UICollectionViewFlowLayoutgirintilerle sınırlandırılmıştır ):

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
    }

}

Aşağıdakiler için bazı alternatif uygulamalar şunlardır 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
}

Beklenen ekran:

görüntü açıklamasını buraya girin


Bölüm başlıkları ekleyene kadar bu benim için iyi çalıştı. Bölüm başlıkları yanlış yerleştirilmiş ve iOS 11'deki diğer hücreleri kapsıyor. Ancak iOS 12'de sorunsuz çalışıyor. Bölüm başlıkları eklerken herhangi bir sorunla karşılaştınız mı?
Nakul

Çok teşekkür ederim. Haftamı kurtardın. 🙇‍♂️
Gaetan

1
CollectionView, boyut değiştiğinde en üste kaydırılır
Sajith

1
harika haberler, @ImanouPetit! Keşke ödül daha büyük olsaydı! Kompozisyonel Düzen nesneleri hakkında harika bilgiler, teşekkürler.
Fattie

1
Merhaba, bunu deniyorum. Viewdidload'da düzeni yukarıda yaptığınız gibi ayarlasam da, koleksiyon görünümünün sıfır olmayan bir düzen parametresi ile başlatılması gerektiğini belirten bir kilitlenme alıyorum. Eksik bir şey mi var? Bu iOS 13 için Xcode 11.
geistmate

29

Sorun

Otomatik yükseklik arıyorsanız ve aynı zamanda tam genişliğe sahip olmak istiyorsunuz, ikisini de kullanarak elde etmek mümkün değil UICollectionViewFlowLayoutAutomaticSize.

Bunu kullanmak istiyorsanız, UICollectionViewaşağıdaki çözüm tam size göre.

Çözüm

Adım-I : Beklenen Hücre yüksekliğini hesaplayın

1. Eğer sadece UILabel in CollectionViewCellsonra ayarladıysanız numberOfLines=0ve beklenen yüksekliği hesaplayan, UIlableüç parametrenin tümünü geç

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. Eğer CollectionViewCellsadece içeriyorsa UIImageViewve Yükseklik'te yüksekliğini almanız gerekenden daha dinamik olması gerekiyorsa UIImage ( kısıtlamalarınız UIImageViewolmalıdır AspectRatio)

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

3. Her ikisini de içeriyorsa, yüksekliklerini hesaplayın ve hepsini toplayın.

ADIM-II : Boyutunu DöndürCollectionViewCell

1. Ekle UICollectionViewDelegateFlowLayoutsizin viewController içinde temsilci

2. Temsilci yöntemini uygulayın

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

Umarım bu sana yardımcı olur.


1
Otomatik mizanpaj ile dikey olarak artan hücre yüksekliğine sahip yatay kaydırmalı bir UICollectionView mümkün müdür?
nr5

Aynı ancak yarı genişlik ve dinamik yüksekliği elde etmek istiyorum
Mihir Mehta

return CGSize(width: view.frame.width/2, height: expectedHeight)Ayrıca Genişliği döndürmektense Dolguyu hesaba katın, aksi takdirde iki hücre tek satıra sığmaz.
Dhiru

@ nr5 Çözümü aldın mı? Benim ihtiyacım seninkiyle aynı. Teşekkürler.
Maulik Bhuptani

@MaulikBhuptani Auto Layout ile bunu tamamen yapamadım. Özel bir Layout sınıfı oluşturmak ve bazı sınıf yöntemlerini geçersiz kılmak gerekiyordu.
nr5

18

Bu sorunu çözmenin birkaç yolu var.

Bunun bir yolu, koleksiyon görünümü akış düzenine tahmini bir boyut verebilmeniz ve hücre boyutunu hesaplamanızdır.

Not: Aşağıdaki yorumlarda belirtildiği gibi, iOS 10'dan itibaren artık hücrelere yapılan çağrıyı tetiklemek için boyut sağlamanıza ve tahmini boyuta ihtiyacınız yoktur func preferredLayoutAttributesFitting(_ layoutAttributes:). Önceden (iOS 9), tercih edilen bir hücreyi sorgulamak istiyorsanız, tahmini bir boyut sağlamanızı gerektiriyordu.LayoutAttributes.

(film şeridi kullandığınızı ve koleksiyon görünümünün IB aracılığıyla bağlandığını varsayarsak)

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

Basit hücreler için bu genellikle yeterli olacaktır. Boyut hala yanlışsa, koleksiyon görünümü hücresinde geçersiz kılabilirsiniz func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes, bu da size hücre boyutu üzerinde daha ince gren kontrolü sağlar. Not: Akış düzenine yine de tahmini bir boyut vermeniz gerekecektir .

Ardından func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes, doğru boyutu döndürmek için geçersiz kılın .

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
}

Alternatif olarak, bunun yerine, içindeki hücrenin boyutunu hesaplamak için bir boyutlandırma hücresi kullanabilirsiniz 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)
}

Bunlar mükemmel üretime hazır örnekler olmasa da doğru yönde başlamanız gerekir. Bunun en iyi uygulama olduğunu söyleyemem, ancak bu benim için, birden çok satıra sarılabilen veya sarılmayabilen birden çok etiket içeren oldukça karmaşık hücrelerde bile işe yarıyor.


@Fattie 'esitmatedItemSize'nin dinamik olarak boyutlandırılmış bir koleksiyon görüntüleme hücresiyle ne kadar alakasız olduğu net değil, bu yüzden düşüncelerinizi duymak isterim. (alay değil)
Eric Murphey

Ek olarak, bu yanıtta yardımcı olabilecek belirli bir şey eksikse ve / veya neyi standart olarak kabul ediyorsanız, bana bildirin. Ödül hakkında tartışmayacağım, sadece bu soruyu gören başkalarına yardımcı olmak istiyorum.
Eric Murphey

1
(imatedItemSize dahil olmak veya olmamak, mevcut iOS'ta hiçbir fark yaratmaz ve "tam genişlik + dinamik yükseklik" sorununda hiçbir şekilde yardımcı olmaz.) Sondaki kod, uçlar bu günlerde nadiren kullanılıyor ve dinamik yükseklikle çok az alaka (örneğin, birkaç dinamik yükseklik metin görünümünden). yine de teşekkürler
Şişko

Ben de bu çözüme ulaştım, ancak benim durumumda, iOS 11, Xcode 9.2'de, hücre içeriği hücrenin kendisinden kopuk görünüyordu - sabit boyutun tam yüksekliğine veya genişliğine genişleyecek bir görüntü elde edemedim. Ayrıca CV'nin ayar kısıtlamaları olduğunu fark ettim preferredLayoutAttributesFitting. Daha sonra, bahsedilen işlevde, tahminiItemSize, FWIW'den sabit boyuta bağlanan bir kısıtlama ekledim, aşağıdaki cevabıma bakın.
Chris Conover

16

Bu sorun için oldukça kolay bir çözüm buldum: CollectionViewCell'imin içinde aslında sadece bir arka plan olan bir UIView () var. Tam genişlik elde etmek için aşağıdaki Bağlantıları ayarlıyorum

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

"Sihir" ilk satırda gerçekleşir. WidthAnchor'yı ekranın genişliğine dinamik olarak ayarlıyorum. Ayrıca önemli olan, CollectionView'unuzun eklerini çıkarmaktır. Aksi takdirde hücre görünmez. Böyle bir arka plan görünümüne sahip olmak istemiyorsanız, onu görünmez yapın.

FlowLayout aşağıdaki ayarları kullanır

layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

Sonuç, dinamik yüksekliğe sahip tam genişlikte boyutlu bir hücredir.

görüntü açıklamasını buraya girin


1
kutsal inek - bu harika! çok açık ! parlak !
Şişko

1
sadece TBC @ inf1783, bunu UICollectionView'da mı yapıyorsunuz? (tablo görünümü değil) - doğru mu?
Şişko

1
@Fattie Evet, bu bir UICollectionView;)
inf1783

fantastik @ inf1783, bunu denemek için sabırsızlanıyorum. Bu arada, bunu yapmanın bir başka iyi yolu da UIView'i alt sınıflara ayırmak ve basitçe içsel bir genişlik eklemek (sanırım aynı etkiye sahip olacak ve muhtemelen storyboard zamanında da çalışmasını sağlayacaktır)
Fattie

1
Bunu her denediğimde sonsuz döngüye giriyorum UICollectionViewFlowLayout davranışı tanımlı değil hata: /
Skodik.o

7

ÇALIŞMA!!! IOS üzerinde test edildi: 12.1 Swift 4.1

Hiçbir kısıtlama olmadan çalışan çok basit bir çözümüm var.

görüntü açıklamasını buraya girin

My 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 sınıfı:

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

}

Ana bileşen, Customcell'deki systemLayoutSizeFitting işlevidir. Ayrıca Cell içindeki görünümün genişliğini kısıtlamalarla ayarlamalıyız.


6

CollectionViewCell'e genişlik kısıtlaması eklemelisiniz

class SelfSizingCell: UICollectionViewCell {

  override func awakeFromNib() {
      super.awakeFromNib()
      contentView.translatesAutoresizingMaskIntoConstraints = false
      contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
  }
}

sonunda biri iki satırda mükemmel yaptı
Omar N Shamali

Bu, hücreyi sabit bir genişlikle sınırlayacak, neredeyse kendi kendine boyutlandıracaktır. Bir iPad'de aygıtı döndürürseniz veya ekran boyutunu ayarlarsanız, sabit kalacaktır.
Thomas Verbeek

4
  1. estimatedItemSizeAkış düzeninizin seti :

    collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
  2. Hücrede bir genişlik sınırlaması tanımlayın ve bunu süpervizörün genişliğine eşit olacak şekilde ayarlayın:

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

Tam örnek

İOS 11'de test edildi.


3

Şahsen ben, boyutu ölçmek için gerçek bir Cell kullanırken, AutoLayout'un boyutu belirlerken, her bir Hücrenin farklı bir boyuta sahip olabileceği bir UICollectionView'e sahip olmanın en iyi yollarını buldum.

Blog yazılarımdan birinde bundan bahsetmiştim

Umarım bu, istediğinizi elde etmenize yardımcı olur. % 100 emin değilim, ancak UITableView'den farklı olarak AutoLayout ile birlikte kullanarak tam otomatik hücre yüksekliğine sahip olabileceğinize inanıyorum.

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionView, AutoLayout'un boyutu belirlemesine izin vermenin böyle bir yoluna sahip değildir çünkü UICollectionViewCell, ekranın tüm genişliğini doldurmaz.

Ama işte size bir soru : Tam ekran genişliğinde hücrelere ihtiyacınız varsa, neden otomatik boyutlandırma hücreleriyle birlikte gelen eski bir UITableView yerine UICollectionView kullanmakla uğraşıyorsunuz?


aşağıdaki Aprit, UICollectionViewFlowLayoutAutomaticSize'ın iOS10'da akış düzeninde artık mevcut olduğunu söylüyor ...
Fattie

Ben de bunu gördüm. Görünüşe göre bu şimdi iOS 10'da bir şey. Bununla ilgili WWDC konuşmasını az önce izledim: developer.apple.com/videos/play/wwdc2016/219 Ancak bu, hücreyi içeriğine uyacak şekilde tamamen otomatik olarak boyutlandıracak. UICollectionViewCell ile üst öğesi (en azından StoryBoards'da değil) arasında bir sınırlama kuramadığınız için hücreye (AutoLayout ile) ekran genişliğini doldurmasını nasıl söyleyeceğinizden emin değilim
xxtesaxx

sağ. Bu inanılmaz derecede aşikar soruna gerçek bir çözüme yakın görünmüyoruz. : /
Şişko

Konuşmaya göre, sizeThatFits () veya preferLayoutAttributesFitting () öğelerinin üzerine yazabilir ve boyutu kendiniz hesaplayabilirsiniz, ancak OP'nin istediği şey bu değildi. Her neyse, tam genişlikte bir UICollectionViewCell'e ihtiyaç duyduğu zaman ve bu özel durumda bir UITableView kullanmamasının neden bu kadar önemli olduğu için kullanım durumuyla hala ilgileniyorum (eminim bazı durumlar olabilir ama sonra sanırım) bazı hesaplamaları kendiniz
yapmakla uğraşmanız yeterlidir

düşündüğünüzde, koleksiyon görünümleriyle basitçe "sütunlar == 1" (ve sonra aygıt yan durduğunda sütunlar == 2) ayarlayamamanız inanılmaz.
Şişko

3

Eric'in cevabıyla ilgili yorumuma göre, benim çözümüm onunkine çok benziyor, ancak sabit boyutu sınırlamak için PreferSizeFor ... 'a bir kısıtlama eklemek zorunda kaldım.

    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
    }

Bu sorunun birkaç kopyası var, burada ayrıntılı olarak yanıtladım ve burada çalışan bir örnek uygulama sağladım.


2

Bunun "gerçekten iyi bir cevap" olup olmadığından emin değilim, ama bunu başarmak için kullandığım şey bu. Akış düzenim yatay ve genişliği otomatik düzen ile ayarlamaya çalışıyorum, bu yüzden durumunuza benzer.

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

Ardından, otomatik düzen kısıtlamasının değiştirildiği görünüm denetleyicisinde, bir NSNotification başlatırım.

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

UICollectionView alt sınıfımda şu bildirimi dinliyorum:

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

ve düzeni geçersiz kılın:

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

Bu sizeForItemAt, koleksiyon görünümünün yeni boyutu kullanılarak yeniden çağrılmasına neden olur . Sizin durumunuzda, düzende bulunan yeni kısıtlamalara göre güncelleme yapabilmelidir.


sadece TBC Mark gerçekten bir hücrenin boyutunu değiştirdiğinizi mi kastediyorsunuz ? (yani hücre görünür ve X boyutudur, sonra uygulamanızda bir şey olur ve Y boyutu olur ..?) thx
Fattie

Evet. Kullanıcı ekranda bir öğeyi sürüklediğinde, hücrenin boyutunu güncelleyen bir olay tetiklenir.
Mark Suman

1

Aşağıdaki yerlerde de viewDidLayoutSubviews, set estimatedItemSizetam genişlikte (düzen UICollectionViewFlowLayout nesneye atıfta) için:

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

Hücrenizde, kısıtlamalarınızın hücrenin hem üstüne hem de altına dokunduğundan emin olun (aşağıdaki kod, kısıtlamaları ayarlamayı basitleştirmek için Kartografi kullanır, ancak isterseniz bunu NSLayoutConstraint veya IB ile yapabilirsiniz):

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, siz hücreler artık otomatik olarak büyüyecek!


0

İPhone genişliğine uyum sağlamak için dinamik genişliğe ihtiyacım olduğu için çözümlerin hiçbiri benim için çalışmıyordu.

    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

İOS 10'dan, bunu yapmak için akış düzeninde yeni API'ye sahibiz.

Tek yapmanız gereken, flowLayout.estimatedItemSizeyeni bir sabite ayarlamaktır UICollectionViewFlowLayoutAutomaticSize.

Kaynak


Hmm, bunun onu tam genişlikte yaptığından oldukça eminsin ? Yani, esasen bir sütun mu?
Şişko

UICollectionViewCell genişliğini açıkça tam genişliğe ayarlarsanız olabilir.
Arpit Dongre

Hayır, genişliği tam yüksekliğe sabitlemez ve yukarıdaki Eric'in cevabını takip etmediğiniz veya aşağıda / daha sonra benimkini uygulamadığınız sürece tahmini boyut değerini yok sayar.
Chris Conover

Tamam, maalesef bunun problemle kesinlikle bir bağlantısı yok! heh! : O
Şişko

0

AutoLayout, CollectionView'daki hücrelerin otomatik olarak boyutlandırılması için 2 kolay adımda kullanılabilir:

  1. Dinamik hücre boyutlandırmayı etkinleştirme

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Bir kapsayıcı görünümüne sahip olun ve collectionView(:cellForItemAt:)contentView genişliğini collectionView genişliğini sınırlamak için containerView.widthAnchor.constraint öğesini ayarlayın .
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
        }
    }

    ....
}

İşte istenen sonucu alacaksınız. Tam kod için aşağıdaki esaslara bakın:

Referans / Kredi:

Ekran görüntüsü: görüntü açıklamasını buraya girin


-6

CollectionViewController'ınızda UICollectionViewDelegateFlowLayout sınıfını miras almanız gerekir. Ardından işlevi ekleyin:

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

Bunu kullanarak, ekran genişliğinin genişlik boyutuna sahip olursunuz.

Ve artık tableViewController olarak satırlara sahip bir collectionViewController'a sahipsiniz.

Her hücrenin yüksekliğinin dinamik olarak boyutlandırılmasını istiyorsanız, belki ihtiyacınız olan her hücre için özel hücreler oluşturmalısınız.


7
Bu tam olarak sorunun cevabı değil :) Bu 100'lük sabit bir yükseklik verir, yani otomatik düzen var olmadan önce programlanmış olan budur.
Şişko
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.