Sembra che quello che stai chiedendo sia un modo per usare UICollectionView per produrre un layout come UITableView. Se è davvero quello che vuoi, il modo giusto per farlo è con una sottoclasse UICollectionViewLayout personalizzata (forse qualcosa come SBTableLayout ).
D'altra parte, se stai davvero chiedendo se esiste un modo pulito per farlo con l'UICollectionViewFlowLayout predefinito, allora credo che non ci sia modo. Anche con le celle auto-dimensionanti di iOS8, non è semplice. Il problema fondamentale, come dici tu, è che il meccanismo del layout di flusso non fornisce alcun modo per fissare una dimensione e lasciare che un'altra risponda. (Inoltre, anche se fosse possibile, ci sarebbe ulteriore complessità nella necessità di due passaggi di layout per dimensionare le etichette multilinea. Ciò potrebbe non adattarsi al modo in cui le celle auto-dimensionanti vogliono calcolare tutto il dimensionamento tramite una chiamata a systemLayoutSizeFittingSize.)
Tuttavia, se si desidera comunque creare un layout simile a una vista tabella con un layout di flusso, con celle che determinano la propria dimensione e rispondono naturalmente alla larghezza della vista della raccolta, ovviamente è possibile. C'è ancora il modo disordinato. L'ho fatto con una "cella di dimensionamento", ovvero un UICollectionViewCell non visualizzato che il controller conserva solo per il calcolo delle dimensioni delle celle.
Ci sono due parti in questo approccio. La prima parte è che il delegato della vista della raccolta calcoli la dimensione corretta della cella, prendendo la larghezza della vista della raccolta e utilizzando la cella di dimensionamento per calcolare l'altezza della cella.
Nel tuo UICollectionViewDelegateFlowLayout, implementi un metodo come questo:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
Ciò farà sì che la vista della raccolta imposti la larghezza della cella in base alla vista della raccolta che la racchiude, ma poi chiederà alla cella di dimensionamento di calcolare l'altezza.
La seconda parte consiste nel fare in modo che la cella di dimensionamento utilizzi i propri vincoli AL per calcolare l'altezza. Questo può essere più difficile di quanto dovrebbe essere, a causa del modo in cui le UILabel multi-linea richiedono effettivamente un processo di layout a due fasi. Il lavoro viene svolto nel metodo preferredLayoutSizeFittingSize
, che è così:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(Questo codice è adattato dal codice di esempio Apple dal codice di esempio della sessione WWDC2014 in "Visualizzazione raccolta avanzata".)
Un paio di punti da notare. Utilizza layoutIfNeeded () per forzare il layout dell'intera cella, al fine di calcolare e impostare la larghezza dell'etichetta. Ma non è abbastanza. Credo che tu debba anche impostare in preferredMaxLayoutWidth
modo che l'etichetta utilizzi quella larghezza con il layout automatico. E solo allora puoi usarlo per fare systemLayoutSizeFittingSize
in modo che la cella calcoli la sua altezza tenendo conto dell'etichetta.
Mi piace questo approccio? No!! Sembra troppo complesso e fa il layout due volte. Ma finché le prestazioni non diventano un problema, preferisco eseguire il layout due volte in fase di esecuzione piuttosto che definirlo due volte nel codice, che sembra essere l'unica altra alternativa.
La mia speranza è che alla fine le celle auto-dimensionate funzionino in modo diverso e tutto questo diventi molto più semplice.
Progetto di esempio che lo mostra al lavoro.
Ma perché non utilizzare solo celle auto-dimensionanti?
In teoria, le nuove funzionalità di iOS8 per le "celle auto-dimensionate" dovrebbero renderlo superfluo. Se hai definito una cella con layout automatico (AL), la visualizzazione della raccolta dovrebbe essere abbastanza intelligente da consentire la dimensione e la disposizione corretta. In pratica, non ho visto alcun esempio che lo abbia fatto funzionare con etichette multilinea. Penso che ciò sia in parte dovuto al fatto che il meccanismo cellulare auto-dimensionante è ancora difettoso.
Ma scommetto che è principalmente a causa della solita complessità del layout automatico e delle etichette, ovvero che le etichette UIL richiedono un processo di layout sostanzialmente in due passaggi. Non mi è chiaro come sia possibile eseguire entrambi i passaggi con celle auto-dimensionanti.
E come ho detto, questo è davvero un lavoro per un layout diverso. Fa parte dell'essenza del layout di flusso il fatto di posizionare le cose che hanno una dimensione, piuttosto che fissare una larghezza e consentire loro di scegliere la loro altezza.
E che dire di preferredLayoutAttributesFittingAttributes:?
Il preferredLayoutAttributesFittingAttributes:
metodo è una falsa pista, credo. Questo è solo lì per essere utilizzato con il nuovo meccanismo cellulare auto-dimensionante. Quindi questa non è la risposta finché quel meccanismo è inaffidabile.
E che succede con systemlayoutSizeFittingSize :?
Hai ragione, i documenti creano confusione.
I documenti su systemLayoutSizeFittingSize:
ed systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
entrambi suggeriscono che dovresti solo passare UILayoutFittingCompressedSize
e UILayoutFittingExpandedSize
come targetSize
. Tuttavia, la firma del metodo stessa, i commenti di intestazione e il comportamento delle funzioni indicano che stanno rispondendo al valore esatto del targetSize
parametro.
In effetti, se si imposta il UICollectionViewFlowLayoutDelegate.estimatedItemSize
, per abilitare il nuovo meccanismo di cella auto-dimensionante, quel valore sembra essere passato come targetSize. E UILabel.systemLayoutSizeFittingSize
sembra restituire esattamente gli stessi valori di UILabel.sizeThatFits
. Questo è sospetto, dato che l'argomento a systemLayoutSizeFittingSize
dovrebbe essere un obiettivo approssimativo e l'argomento a sizeThatFits:
dovrebbe essere una dimensione massima circoscrivente.
Più risorse
Sebbene sia triste pensare che un tale requisito di routine dovrebbe richiedere "risorse di ricerca", penso che lo sia. Buoni esempi e discussioni sono: