Ik probeer self-sizing UICollectionViewCells
te laten werken met automatische lay-out, maar het lukt me niet om de cellen de grootte aan te passen aan de inhoud. Ik heb moeite om te begrijpen hoe de grootte van de cel wordt bijgewerkt op basis van de inhoud van de inhoud van de cel.
Dit is de configuratie die ik heb geprobeerd:
- Aangepaste
UICollectionViewCell
met eenUITextView
in de contentView. - Scrollen voor de
UITextView
is uitgeschakeld. - De horizontale beperking van contentView is: “H:|[_textView(320)]”, d.w.z. de
UITextView
wordt links van de cel vastgezet met een expliciete breedte van 320. - De verticale beperking van contentView is: “V:|-0-[_textView]”, d.w.z. de
UITextView
vastgemaakt aan de bovenkant van de cel. - De
UITextView
heeft een hoogtebeperking die is ingesteld op een constante waarvan deUITextView
rapporten in de tekst passen.
Zo ziet het eruit met de celachtergrond ingesteld op rood en de UITextView
achtergrond ingesteld op Blauw:
Ik heb het project waarmee ik heb gespeeld op GitHub hiergezet.
Antwoord 1, autoriteit 100%
Dit antwoord is verouderd vanaf iOS 14 met de toevoeging van compositorische lay-outs. Overweeg om de nieuwe API
bij te werken
Bijgewerkt voor Swift 5
preferredLayoutAttributesFittingAttributes
hernoemd naar preferredLayoutAttributesFitting
en gebruik automatisch aanpassen
Bijgewerkt voor Swift 4
systemLayoutSizeFittingSize
hernoemd naar systemLayoutSizeFitting
Bijgewerkt voor iOS 9
Nadat ik zag dat mijn GitHub-oplossing onder iOS 9 kapot ging, kreeg ik eindelijk de tijd om het probleem volledig te onderzoeken. Ik heb de repo nu bijgewerkt met verschillende voorbeelden van verschillende configuraties voor zelfgrootte cellen. Mijn conclusie is dat self-sizing cellen in theorie geweldig zijn, maar in de praktijk rommelig. Een woord van waarschuwing wanneer u doorgaat met zelfgrootte cellen.
TL;DR
Bekijk mijn GitHub-project
Zelfgrootte cellen worden alleen ondersteund met stroomlay-out, dus zorg ervoor dat u dat gebruikt.
Er zijn twee dingen die je moet instellen om de zelfgrootte cellen te laten werken.
#1. Stel estimatedItemSize
in op UICollectionViewFlowLayout
De stroomlay-out wordt dynamisch van aard zodra u de eigenschap estimatedItemSize
instelt.
self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
#2. Ondersteuning toevoegen voor dimensionering op uw celsubklasse
Dit is er in 2 smaken; Automatische lay-out ofaangepaste overschrijving van preferredLayoutAttributesFittingAttributes
.
Cellen maken en configureren met automatische lay-out
Ik zal hier niet in detail op ingaan, want er is een briljante SO berichtover het configureren van beperkingen voor een cel. Wees op uw hoede dat Xcode 6 brakeen heleboel dingen met iOS 7 dus, als je iOS 7 ondersteunt, moet je dingen doen zoals ervoor zorgen dat het masker voor automatisch aanpassen is ingesteld op de contentView van de cel en dat de grenzen van de contentView worden ingesteld als de grenzen van de cel wanneer de cel wordt geladen (dwz awakeFromNib
).
Waar u zich wel van bewust moet zijn, is dat uw cel ernstiger moet worden beperkt dan een cel met tabelweergave. Als u bijvoorbeeld wilt dat uw breedte dynamisch is, heeft uw cel een hoogtebeperking nodig. Evenzo, als u wilt dat de hoogte dynamisch is, heeft u een breedtebeperking voor uw cel nodig.
Implementeer preferredLayoutAttributesFittingAttributes
in uw aangepaste cel
Als deze functie wordt aangeroepen, is uw weergave al geconfigureerd met inhoud (d.w.z. cellForItem
is aangeroepen). Ervan uitgaande dat uw beperkingen correct zijn ingesteld, zou u een implementatie als deze kunnen hebben:
//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
}
OPMERKINGOp iOS 9 veranderde het gedrag een beetje dat crashes in uw implementatie zou kunnen veroorzaken als u niet voorzichtig bent (Bekijk meer hier). Wanneer u preferredLayoutAttributesFittingAttributes
implementeert, moet u ervoor zorgen dat u het frame van uw lay-outkenmerken slechts één keer wijzigt. Als u dit niet doet, roept de lay-out uw implementatie voor onbepaalde tijd op en crasht uiteindelijk. Een oplossing is om de berekende grootte in uw cel te cachen en deze ongeldig te maken wanneer u de cel opnieuw gebruikt of de inhoud ervan wijzigt, zoals ik heb gedaan met de eigenschap isHeightCalculated
.
Ervaar uw lay-out
Op dit punt zou je ‘functionerende’ dynamische cellen in je collectionView moeten hebben. Ik heb de kant-en-klare oplossing nog niet voldoende gevonden tijdens mijn tests, dus voel je vrij om commentaar te geven als je dat hebt gedaan. Het voelt nog steeds alsof UITableView
de strijd om dynamische dimensionering IMHO wint.
##Voorbehoud
Houd er rekening mee dat als u prototypecellen gebruikt om de geschatteItemSize te berekenen, dit niet werkt als uw XIB gebruikt maatklassen. De reden hiervoor is dat wanneer u uw cel laadt vanuit een XIB, de grootteklasse wordt geconfigureerd met Undefined
. Dit wordt alleen verbroken op iOS 8 en hoger, aangezien op iOS 7 de grootteklasse wordt geladen op basis van het apparaat (iPad = Regular-Any, iPhone = Compact-Any). U kunt de geschatteItemSize instellen zonder de XIB te laden, of u kunt de cel laden vanuit de XIB, deze toevoegen aan de collectionView (hiermee wordt de traitCollection ingesteld), de lay-out uitvoeren en deze vervolgens uit de superview verwijderen. Je kunt ook je cel de getter traitCollection
laten overschrijven en de juiste eigenschappen teruggeven. Het is aan jou.
Laat het me weten als ik iets heb gemist, ik hoop dat ik heb geholpen en veel succes met coderen
Antwoord 2, autoriteit 19%
In iOS10 is er een nieuwe constante genaamd UICollectionViewFlowLayout.automaticSize
(voorheen UICollectionViewFlowLayoutAutomaticSize
), dus in plaats daarvan:
self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)
u kunt dit gebruiken:
self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Het presteert beter, vooral wanneer cellen in je collectieweergave een constante breedte hebben.
Toegang tot stroomindeling:
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
Swift 5 bijgewerkt:
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
Antwoord 3, autoriteit 13%
Een paar belangrijke wijzigingen in Daniel Galasko’s antwoordlosten al mijn problemen op. Helaas heb ik niet genoeg reputatie om (nog) rechtstreeks commentaar te geven.
In stap 1 voegt u bij het gebruik van Automatische lay-out eenvoudig een enkele bovenliggende UIView toe aan de cel. ALLES in de cel moet een subweergave van de ouder zijn. Dat beantwoordde al mijn problemen. Hoewel Xcode dit automatisch toevoegt voor UITableViewCells, doet het dat niet (maar zou het moeten) voor UICollectionViewCells. Volgens de docs:
Als u het uiterlijk van uw cel wilt configureren, voegt u de weergaven toe die nodig zijn om de inhoud van het gegevensitem als subweergaven te presenteren aan de weergave in de eigenschap contentView. Voeg subviews niet rechtstreeks toe aan de cel zelf.
Sla stap 3 dan helemaal over. Het is niet nodig.
Antwoord 4, autoriteit 10%
In iOS 10+ is dit een heel eenvoudig proces van twee stappen.
-
Zorg ervoor dat al uw celinhoud binnen een enkele UIView wordt geplaatst (of binnen een afstammeling van UIView zoals UIStackView, wat de automatische lay-out aanzienlijk vereenvoudigt). Net als bij het dynamisch wijzigen van de grootte van UITableViewCells, moeten er voor de hele weergavehiërarchie beperkingen zijn geconfigureerd, van de buitenste container tot de binnenste weergave. Dat omvat beperkingen tussen de UICollectionViewCell en de directe onderliggende weergave
-
Instrueer de stroomlay-out van uw UICollectionView automatisch op maat
yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Antwoord 5, autoriteit 4%
-
FlowLayout toevoegen aan viewDidLoad()
override func viewDidLoad() { super.viewDidLoad() if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize(width: 1, height:1) } }
-
Stel ook een UIView in als mainContainer voor uw cel en voeg alle vereiste weergaven erin toe.
-
Raadpleeg deze geweldige, verbluffende tutorial voor meer informatie:
UICollectionView met autosizing cel met autolayout in iOS 9 & amp; 10
Antwoord 6, autoriteit 4%
BEWERK 19-11-19: Gebruik voor iOS 13 UICollectionViewCompositionalLayout met geschatte hoogtes. Verspil geen tijd aan het omgaan met deze kapotte API.
Na hier enige tijd mee te hebben geworsteld, merkte ik dat het formaat wijzigen niet werkt voor UITextViews als je scrollen niet uitschakelt:
let textView = UITextView()
textView.scrollEnabled = false
Antwoord 7, autoriteit 2%
contentBekijk ankermysterie:
In een bizar geval dit
contentView.translatesAutoresizingMaskIntoConstraints = false
zou niet werken. Vier expliciete ankers toegevoegd aan de contentView en het werkte.
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)
])
}
}
en zoals gewoonlijk
estimatedItemSize = UICollectionViewFlowLayout.automaticSize
in YourLayout: UICollectionViewFlowLayout
Wie weet? Kan iemand helpen.
Tegoed
https://www.vadimbulavin.com/collection-view-cells -zelf aanpassen/
vond de tip daar – heb hem nergens anders gezien in alle 1000-artikelen hierover.
Antwoord 8
Ik heb een dynamische celhoogte van de collectieweergave gemaakt. Hier is git hub repo.
En zoek uit waarom preferredLayoutAttributesFittingAttributes meer dan eens wordt aangeroepen. Eigenlijk wordt er minimaal 3 keer gebeld.
1e voorkeurLayoutAttributesFittingAttributes:
(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);
De layoutAttributes.frame.size.height heeft de huidige status 57,5.
2e voorkeurLayoutAttributesFittingAttributes:
(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);
De hoogte van het celframe is gewijzigd in 534,5zoals verwacht. Maar de collectieweergave is nog steeds nul.
3e voorkeurLayoutAttributesFittingAttributes:
(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);
Je kunt zien dat de hoogte van de collectieweergave is gewijzigd van 0 in 477.
Het gedrag is vergelijkbaar met scroll handvat:
1. Before self-sizing cell
2. Validated self-sizing cell again after other cells recalculated.
3. Did changed self-sizing cell
In het begin dacht ik dat deze methode maar één keer kon worden aangeroepen. Dus ik codeerde als volgt:
CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;
Deze regel:
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
veroorzaakt systeemoproep oneindige lus en app-crash.
Elke grootte die wordt gewijzigd, valideert de geprefereerde LayoutAttributesFittingAttributes van alle cellen keer op keer totdat de posities van elke cel (d.w.z. frames) niet meer veranderen.
Antwoord 9
Naast bovenstaande antwoorden,
Zorg ervoor dat u de eigenschap estimatedItemSizevan UICollectionViewFlowLayoutop een bepaalde grootte instelt en nietsizeForItem:atIndexPathimplementeert gedelegeerde methode.
Dat is het.
Antwoord 10
De oplossing bestaat uit 3 eenvoudige stappen:
- Dynamische celgrootte inschakelen
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
- Stel de containerView.widthAnchor.constraint van
collectionView(:cellForItemAt:)
in om de breedte van contentView te beperken tot de breedte van 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
}
}
....
}
Omdat je zelf-sizing van UITextView wilt inschakelen, heeft het een extra stap om;
3. Bereken en stel de heightAnchor.constant van UITextView in.
Dus, wanneer de breedte van contentView is ingesteld, passen we de hoogte van UITextView aan in didSet
of maxWidth
.
In UICollectionViewCell:
var maxWidth: CGFloat? {
didSet {
guard let maxWidth = maxWidth else {
return
}
containerViewWidthAnchor.constant = maxWidth
containerViewWidthAnchor.isActive = true
let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
let newSize = self.textView.sizeThatFits(sizeToFitIn)
self.textViewHeightContraint.constant = newSize.height
}
}
Met deze stappen krijgt u het gewenste resultaat.
Volledig uitvoerbare gist
Referentie: Vadim Bulavinblogpost – Collection View Cells Self-Sizing: stapsgewijze zelfstudie
Schermafbeelding:
Antwoord 11
Als u de UICollectionViewDelegateFlowLayout-methode implementeert:
- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath
Als u collectionview performBatchUpdates:completion:
aanroept, gebruikt de hoogte van de grootte sizeForItemAtIndexPath
in plaats van
preferredLayoutAttributesFittingAttributes
.
Het weergaveproces van performBatchUpdates:completion
doorloopt de methode preferredLayoutAttributesFittingAttributes
maar negeert uw wijzigingen.
Antwoord 12
Aan wie het kan helpen,
Ik had die vervelende crash als estimatedItemSize
was ingesteld. Zelfs als ik 0 retourneerde in numberOfItemsInSection
. Daarom waren de cellen zelf en hun automatische lay-out niet de oorzaak van de crash… De collectionView crashte gewoon, zelfs als hij leeg was, alleen maar omdat estimatedItemSize
was ingesteld op zelf-sizing.
In mijn geval heb ik mijn project gereorganiseerd, van een controller met een collectionView naar een collectionViewController, en het werkte.
Kijk maar.
Antwoord 13
Voor iedereen die alles zonder geluk heeft geprobeerd, dit is het enige waardoor het voor mij werkte.
Voor de labels met meerdere regels in de cel, probeer deze magische lijn toe te voegen:
label.preferredMaxLayoutWidth = 200
Meer info: hier
Proost!
Antwoord 14
De bovenstaande voorbeeldmethode compileert niet. Hier is een gecorrigeerde versie (maar niet getest of het werkt.)
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
}
Antwoord 15
Meer informatie bijwerken:
-
Als u
flowLayout.estimatedItemSize
gebruikt, raadt u aan een latere versie van iOS8.3 te gebruiken. Vóór iOS8.3 crasht het[super layoutAttributesForElementsInRect:rect];
.
De foutmelding is*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
-
Ten tweede, in iOS8.x-versie, zal
flowLayout.estimatedItemSize
ervoor zorgen dat een andere instelling voor sectie-inzet niet werkte. d.w.z. functie:(UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:
.