UICollectionView Samoziarniste komórki z automatycznym układem

207

Próbuję uzyskać samokierowanie UICollectionViewCellsprzy użyciu Auto Layout, ale wydaje mi się, że nie mogę zmusić komórek do dostosowania się do zawartości. Mam problem ze zrozumieniem, w jaki sposób rozmiar komórki jest aktualizowany na podstawie zawartości zawartości contentView komórki.

Oto konfiguracja, którą wypróbowałem:

  • Niestandardowy UICollectionViewCellz UITextViewtreściąView.
  • Przewijanie UITextViewjest wyłączone.
  • Ograniczeniem poziomym contentView jest: „H: | [_textView (320)]”, tj. UITextViewJest on przypięty do lewej części komórki o jawnej szerokości 320.
  • Ograniczenie pionowe contentView to: „V: | -0 - [_ textView]”, tj. Przypięte UITextViewdo górnej części komórki.
  • UITextViewMa wysokość wiązania zestaw do stałej którejUITextView raporty będą pasować do tekstu.

Oto, jak to wygląda z tłem komórki ustawionym na czerwony i UITextView tło ustawione na niebieski: czerwone tło komórki, niebieskie tło UITextView

Tutaj umieściłem projekt, z którym bawiłem się na GitHub .

rawbee
źródło
Czy wdrażasz metodę sizeForItemAtIndexPath?
Daniel Galasko
@DanielGalasko Nie jestem. Rozumiem, że nowa funkcja samopoziomowania komórek w iOS 8 nie wymaga już tego.
rawbee
nie jestem pewien, skąd masz te informacje, ale nadal musisz to zrobić, spójrz na fragment WWDC asciiwwdc.com/2014/sessions/226
Daniel Galasko
ale znowu zależy to od przypadku użycia, upewnij się, że zaimplementowano oszacowanyItemSize
Daniel Galasko
1
2019 - Koniecznie spójrz na to: stackoverflow.com/a/58108897/294884
Fattie

Odpowiedzi:

309

Zaktualizowano dla Swift 5

preferredLayoutAttributesFittingAttributeszmieniono nazwę na preferredLayoutAttributesFittingi używaj automatycznego dostosowywania


Zaktualizowano dla Swift 4

systemLayoutSizeFittingSize przemianowany na systemLayoutSizeFitting


Zaktualizowano dla iOS 9

Po tym, jak moje rozwiązanie GitHub zepsuło się pod iOS 9, w końcu miałem czas na pełne zbadanie problemu. Zaktualizowałem teraz repozytorium, aby zawierało kilka przykładów różnych konfiguracji dla komórek samokierujących. Doszedłem do wniosku, że komórki samoklejające są świetne w teorii, ale w praktyce są nieuporządkowane. Należy zachować ostrożność podczas wybierania komórek samokierujących.

TL; DR

Sprawdź mój projekt GitHub


Komórki samopoziomujące są obsługiwane tylko z układem przepływu, więc upewnij się, że tego używasz.

Istnieją dwie rzeczy, które musisz skonfigurować, aby komórki o własnych rozmiarach działały.

1. Ustaw estimatedItemSizenaUICollectionViewFlowLayout

Układ przepływu stanie się dynamiczny z natury po ustawieniu estimatedItemSizewłaściwości.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Dodaj obsługę zmiany rozmiaru w podklasie komórki

Występuje w 2 smakach; Auto-układ lub niestandardowe zastąpienie preferredLayoutAttributesFittingAttributes.

Twórz i konfiguruj komórki za pomocą automatycznego układu

Nie będę wchodził w szczegóły na ten temat, ponieważ istnieje genialny post SO dotyczący konfigurowania ograniczeń dla komórki. Bądź ostrożny, że Xcode 6 złamał wiele rzeczy w iOS 7, więc jeśli wspierasz iOS 7, musisz zrobić takie rzeczy, jak upewnić się, że maska ​​autoresizingMas jest ustawiona na contentView komórki i że granice contentView są ustawione jako granice komórki, gdy komórka jest załadowana (tjawakeFromNib .).

Należy pamiętać, że komórka musi być poważniej ograniczona niż komórka widoku tabeli. Na przykład, jeśli chcesz, aby szerokość była dynamiczna, twoja komórka potrzebuje ograniczenia wysokości. Podobnie, jeśli chcesz, aby wysokość była dynamiczna, będziesz potrzebować ograniczenia szerokości do swojej komórki.

Implementuj preferredLayoutAttributesFittingAttributesw niestandardowej komórce

Po wywołaniu tej funkcji widok został już skonfigurowany z zawartością (tj. cellForItemZostał wywołany). Zakładając, że ograniczenia zostały odpowiednio ustawione, możesz mieć taką implementację:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

UWAGA W systemie iOS 9 zachowanie zmieniło się nieco, co może spowodować awarie implementacji, jeśli nie będziesz ostrożny (zobacz więcej tutaj ). Po wdrożeniu preferredLayoutAttributesFittingAttributesmusisz upewnić się, że zmienisz ramkę atrybutów układu tylko raz. Jeśli tego nie zrobisz, układ wywoła twoją implementację w nieskończoność i ostatecznie ulegnie awarii. Jednym z rozwiązań jest buforowanie obliczonego rozmiaru w komórce i unieważnianie go za każdym razem, gdy ponownie użyjesz komórki lub zmienisz jej zawartość, tak jak zrobiłem to z tą isHeightCalculatedwłaściwością.

Poznaj swój układ

W tym momencie powinieneś mieć „funkcjonujące” dynamiczne komórki w swojej kolekcjiViewView. Nie znalazłem jeszcze gotowego rozwiązania wystarczającego podczas moich testów, więc możesz to skomentować, jeśli tak. Nadal wydaje się, że UITableViewwygrywa bitwę o dynamiczne zmiany rozmiaru IMHO.

Ostrzeżenia

Pamiętaj, że jeśli używasz prototypowych komórek do obliczania oszacowanegoItemSize - to się zepsuje, jeśli twój XIB używa klas wielkości . Powodem tego jest to, że po załadowaniu komórki z XIB zostanie skonfigurowana klasa wielkości Undefined. Zostanie to zerwane tylko w systemie iOS 8 i nowszym, ponieważ w systemie iOS 7 klasa wielkości zostanie załadowana na podstawie urządzenia (iPad = zwykły-dowolny, iPhone = kompaktowy-dowolny). Możesz ustawić oszacowaną wartość Bez ładowania XIB lub załadować komórkę z XIB, dodać ją do collectionView (spowoduje to ustawienie funkcji traitCollection), wykonać układ, a następnie usunąć z superwizji. Alternatywnie możesz również sprawić, że twoja komórka zastąpi traitCollectiongetter i zwróci odpowiednie cechy. To zależy od Ciebie.

Daj mi znać, jeśli coś przeoczyłem, mam nadzieję, że pomogłem i powodzenia w kodowaniu


Daniel Galasko
źródło
2
Próbuję zaimplementować to samo z układem przepływu, jednak gdy zwrócę go newFrame ze zaktualizowanym rozmiarem, układ nie wydaje się ponownie obliczać pozycji y, które nadal są oparte na wysokości w oszacowanej wielkości. Czego brakuje?
anna
@ czy dzwonisz na stronę invalidateLayout w układzie?
Daniel Galasko
1
Ach, znalazłem różnicę, ustawiłeś dość wysoko szacunkową wysokość (400), a ja robiłem minimum (44). To pomogło. Ale contentSize wciąż nie ustawia się poprawnie, dopóki nie przewiniesz się przez całą drogę, więc nadal nie jest zbyt użyteczny. Przy okazji zmieniłem UITextView na UILabel, to samo zachowanie.
anna
17
Wydaje się, że jest to zasadniczo zepsute na iOS 9 GM. Ustawienie estimatedItemSizeprowadzi do awarii - wydaje się, że istnieje duży błąd w sposobie, w jaki układ automatyczny próbuje przetworzyć UICollectionViewCell.
Wes Campaigne,
3
Czy ktoś napotyka podobny problem?
Tony
47

W iOS10 jest nowa stała o nazwie UICollectionViewFlowLayout.automaticSize(dawniej UICollectionViewFlowLayoutAutomaticSize), więc zamiast tego:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

możesz użyć tego:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Ma lepszą wydajność, szczególnie gdy komórki w twoim widoku kolekcji mają stałą szerokość

Dostęp do układu przepływu:

override func viewDidLoad() {
   super.viewDidLoad()

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

Aktualizacja Swift 5:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}
pawel221
źródło
8
Gdzie to ustawiasz? w viewdidload? Jak uzyskać kontrolę nad flowlayout?
UKDataGeek
1
Udało mi się to zaimplementować - ale ma dziwne zachowanie, które powoduje, że centruje komórki, jeśli jest jedna komórka. Oto pytanie o przepełnienie stosu na ten temat
zaskoczyło
1
Możesz uzyskać dostęp do układu przepływu za pośrednictwem kolekcjiViews collectionViewLayouti po prostu sprawdzić, czy jest on typuUICollectionViewFlowLayout
d4Rk
4
sprawia, że ​​moje komórki są bardzo małe, bezużyteczne
user924 14.04.19
44

Kilka kluczowych zmian w odpowiedzi Daniela Galasko naprawiło wszystkie moje problemy. Niestety nie mam wystarczającej reputacji, aby komentować bezpośrednio (jeszcze).

W kroku 1, gdy korzystasz z automatycznego układu, po prostu dodaj do komórki pojedynczego rodzica UIView. WSZYSTKO w komórce musi być widokiem rodzica. To odpowiedziało na wszystkie moje problemy. Xcode dodaje to automatycznie dla UITableViewCells, ale nie (ale powinno) dla UICollectionViewCells. Według dokumentów :

Aby skonfigurować wygląd komórki, dodaj widoki potrzebne do prezentacji zawartości elementu danych jako widoki podrzędne do widoku we właściwości contentView. Nie dodawaj bezpośrednio widoków podrzędnych do samej komórki.

Następnie całkowicie pomiń krok 3. To nie jest potrzebne.

Matt Koala
źródło
1
Ciekawy; jest to specjalnie dla Xibów, ale mimo to dziękuję, spróbuję zmieszać się z projektem Github i zobaczę, czy mogę się rozmnażać. Może podziel odpowiedź na Xib kontra programistyczny
Daniel Galasko,
Bardzo pomocne . Dzięki!
ProblemSlover,
2
iOS 9, Xcode 7. Komórki są protoyowane w scenorysie, ustaw niestandardową podklasę. Próbując utworzyć właściwość o nazwie contentView, Xcode narzeka, że ​​jest w konflikcie z istniejącą własnością. Próbowałem dodać widoki podrzędne self.contentViewi ustawić ograniczenia, a następnie aplikacja ulega awarii.
Nicolas Miari,
@NicolasMiari Nie wiem, jak to zrobić programowo, moim rozwiązaniem jest dodanie pojedynczego uiviewu w XIB, gdzie wszystko się w nim znajduje. Nie musisz tworzyć nieruchomości ani niczego.
Matt Koala,
Mam do czynienia z tym samym problemem, co @NicolasMiari. Gdy ustawiam oszacowaną wartośćContentSize dla komórek prototypowanych w serii ujęć z ograniczeniami autoukładu w systemie iOS 9 / Xcode 7, aplikacja ulega awarii z wyjątkiem złego dostępu i bez pomocnego stacktrace
wasabi
34

W iOS 10+ jest to bardzo prosty 2-etapowy proces.

  1. Upewnij się, że cała zawartość komórki jest umieszczona w jednym UIView (lub w potomku UIView, takim jak UIStackView, co znacznie upraszcza autoukład). Podobnie jak w przypadku dynamicznego zmieniania rozmiaru UITableViewCells, cała hierarchia widoku musi mieć skonfigurowane ograniczenia, od najbardziej zewnętrznego kontenera do najbardziej wewnętrznego widoku. Obejmuje to ograniczenia między UICollectionViewCell a bezpośrednim widokiem dziecka

  2. Poinstruuj flowlayout UICollectionView, aby automatycznie dobierał rozmiar

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Marmoy
źródło
Jestem ciekawy, jak zawinięcie zawartości komórek w UIView poprawiłoby działanie automatycznego układu? Czy myślałem, że będzie lepiej działał przy płytszej hierarchii?
James Heald
1
Nie wspominałem o wydajności, tylko o prostocie. Komórki samopoziomujące będą działać tylko wtedy, gdy masz ograniczenia obejmujące całą drogę od lewej do prawej oraz od góry do dołu. Osiągnięcie tego jest najłatwiejsze, gdy wszystkie widoki są zapakowane w jeden pojemnik. Jeśli ten kontener jest UIStackView, łatwiej jest, jeśli jest to dowolny potomek UIView.
Marmoy,
Czy możesz mi powiedzieć, jak ustawić stałą wysokość (np. Ustaw wysokość 25, a szerokość zostanie automatycznie zmieniona) dla UICollectionViewFlowLayoutAutomaticSize.
Svetoslav Atanasov
Używając Swift 4.1, Xcode mówi mi, UICollectionViewFlowLayout.automaticSizeże został przemianowany na UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth
14
  1. Dodaj flowLayout do viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Ponadto ustaw UIView jako mainContainer dla swojej komórki i dodaj do niej wszystkie wymagane widoki.

  3. Zapoznaj się z tym niesamowitym, oszałamiającym samouczkiem, aby uzyskać dodatkowe informacje: UICollectionView z funkcją automatycznego dopasowywania komórek za pomocą funkcji automatycznego układania w iOS 9 i 10

dianakarenms
źródło
11

EDYCJA 19.11.19: W systemie iOS 13 wystarczy użyć UICollectionViewCompositionalLayout z szacowanymi wysokościami. Nie trać czasu na radzenie sobie z tym zepsutym API.

Po dłuższej walce z tym zauważyłem, że zmiana rozmiaru nie działa w UITextViews, jeśli nie wyłączysz przewijania:

let textView = UITextView()
textView.scrollEnabled = false
noobular
źródło
kiedy próbuję przewijać, komórka widoku kolekcji nie jest gładka. Czy masz na to jakieś rozwiązanie?
Sathish Kumar Gurunathan
6

contentView anchor mystery:

W jednym dziwnym przypadku to

    contentView.translatesAutoresizingMaskIntoConstraints = false

nie działałoby Dodano cztery wyraźne kotwice do contentView i zadziałało.

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

i jak zwykle

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

w YourLayout: UICollectionViewFlowLayout

Kto wie? Może komuś pomóc.

Kredyt

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

natknąłem się na końcówkę - nigdy nie widziałem go nigdzie indziej we wszystkich tysiącach artykułów na ten temat.

Fattie
źródło
3

Zrobiłem dynamiczną wysokość komórki widoku kolekcji. Oto repozytorium git hub .

I dowiedz się, dlaczego preferowanyLayoutAttributesFittingAttributes jest wywoływany więcej niż jeden raz. W rzeczywistości zostanie wywołany co najmniej 3 razy.

Obraz dziennika konsoli: wprowadź opis zdjęcia tutaj

1st preferowanyLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Wysokość layoutAttributes.frame.size.height ma aktualny status 57,5 .

2nd preferowany LayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Wysokość ramki komórki zmieniła się na 534,5 zgodnie z naszymi oczekiwaniami. Ale widok kolekcji wciąż ma zerową wysokość.

3 preferowanyLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

Możesz zobaczyć, że wysokość widoku kolekcji została zmieniona z 0 na 477 .

Zachowanie jest podobne do obsługi przewijania:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

Na początku myślałem, że ta metoda wywołuje tylko raz. Więc kodowałem w następujący sposób:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

Ta linia:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

spowoduje wywołanie nieskończonej pętli systemowej i awarię aplikacji.

Każda zmiana rozmiaru spowoduje, że wszystkie komórki będą miały preferowanyLayoutAttributesFittingAttributes, dopóki pozycje każdej komórki (tj. Ramki) nie będą się już zmieniać.

Yang Young
źródło
2

Oprócz powyższych odpowiedzi

Po prostu upewnij się, że ustawiłeś właściwość szacowanegoItemSize UICollectionViewFlowLayout na pewien rozmiar i nie implementujesz sizeForItem: atIndexPath metodę delegata.

Otóż ​​to.

Murat Yasar
źródło
1

Jeśli zaimplementujesz metodę UICollectionViewDelegateFlowLayout:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

Kiedy zadzwonisz collectionview performBatchUpdates:completion:, użyjesz wysokości sizeForItemAtIndexPathzamiast preferredLayoutAttributesFittingAttributes .

Proces renderowania performBatchUpdates:completionprzejdzie przez metodę, preferredLayoutAttributesFittingAttributesale ignoruje twoje zmiany.

Yang Young
źródło
Widziałem to niewłaściwe zachowanie, ale tylko wtedy, gdy szacowany rozmiar wynosi zero. Czy na pewno ustawiasz szacowany rozmiar?
dgatwood
1

Komukolwiek to może pomóc,

Miałem ten paskudny wypadek, jeśli estimatedItemSizezostał ustawiony. Nawet jeśli zwróciłem 0 w numberOfItemsInSection. Dlatego same komórki i ich automatyczny układ nie były przyczyną awarii ... CollectionView po prostu się zawiesił, nawet gdy jest pusty, tylko dlatego, żeestimatedItemSize został ustawiony na samorozmiar.

W moim przypadku zreorganizowałem mój projekt, od kontrolera zawierającego collectionView do collectionViewController i zadziałało.

Domyśl.

Jean Le Moignan
źródło
1
Spróbuj zadzwonić collectionView.collectionViewLayout.invalidateLayout()po collectionView.reloadData().
chengsam
dla ios10 i niższych dodaj ten kod `override func viewWillLayoutSubviews () {super.viewWillLayoutSubviews () jeśli #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma
1

Dla każdego, kto spróbował wszystkiego bez powodzenia, jest to jedyna rzecz, która sprawiła, że ​​działało to dla mnie. W przypadku etykiet wielowierszowych wewnątrz komórki spróbuj dodać tę magiczną linię:

label.preferredMaxLayoutWidth = 200

Więcej informacji: tutaj

Twoje zdrowie!

HelloimDarius
źródło
0

Powyższa przykładowa metoda się nie kompiluje. Oto poprawiona wersja (ale niesprawdzona, czy działa).

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}
użytkownik3246173
źródło
0

Zaktualizuj więcej informacji:

  • Jeśli używasz flowLayout.estimatedItemSize, sugeruj użycie późniejszej wersji iOS 8.3. Przed iOS 8.3 nastąpi awaria [super layoutAttributesForElementsInRect:rect];. Komunikat o błędzie to

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • Po drugie, w wersji iOS8.x flowLayout.estimatedItemSizespowoduje, że różne ustawienia wstawek sekcji nie działały. czyli funkcja: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.

Yang Young
źródło
0

Rozwiązanie składa się z 4 ważnych kroków:

  1. Włączanie dynamicznego doboru komórek

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Jawnie włącz automatyczny układ i przypnij contentViewdo krawędzi komórki, ponieważ contentViewzapobiega samozaklejeniu, ponieważ nie ma włączonego automatycznego układu.
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. Ustaw contentView.widthAnchor.constraint od, collectionView(:cellForItemAt:)aby ograniczyć szerokość contentView do szerokości collectionView.

Dzieje się to w następujący sposób; będziemy ustawić komórki maxWidthzmiennej szerokości CollectionView pod adresem collectionView(:cellForItemAt:)iw maxWidth„s didSetmetody będziemy ustawić widthAnchor.constant do maxwidth.

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
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

Ponieważ chcesz włączyć automatyczne dostosowywanie rozmiaru UITextView, ma on dodatkowy krok do;

4. Oblicz i ustaw wysokośćAnchor.constant w UITextView.

Tak więc, ilekroć ustawiona jest szerokość contentView, dostosowujemy wysokość UITextView wraz didSetz maxWidth.

Inside UICollectionViewCell:

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

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

Kroki te zapewnią pożądany rezultat. Oto pełna runnable Gist

Dzięki Vadimowi Bulavinowi za jego jasny i pouczający post na blogu - Kolekcja Wyświetl komórki Samowyrównanie : samouczek krok po kroku

Harley
źródło
-2

Próbowałem użyć, estimatedItemSizeale było wiele błędów podczas wstawiania i usuwania komórek, jeśli estimatedItemSizenie były dokładnie równe wysokości komórki. Przestałem ustawiać estimatedItemSizei zaimplementowałem dynamiczne komórki za pomocą prototypowej komórki. oto jak to się robi:

utwórz ten protokół:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

zaimplementuj ten protokół w swoim własnym UICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

teraz spraw, aby twój kontroler był zgodny UICollectionViewDelegateFlowLayouti zawiera w tym polu:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

zostanie użyty jako komórka prototypowa do powiązania danych, a następnie określi, w jaki sposób dane wpłynęły na wymiar, który ma być dynamiczny

wreszcie UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)należy wprowadzić:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

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

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

i to wszystko. Zwracanie wielkości komórki collectionView(:layout:sizeForItemAt:)w ten sposób uniemożliwia mi użycie estimatedItemSize, a wstawianie i usuwanie komórek działa idealnie.

but
źródło