Wygląda na to, że pytasz o sposób użycia UICollectionView do utworzenia układu takiego jak UITableView. Jeśli naprawdę tego chcesz, właściwym sposobem na zrobienie tego jest niestandardowa podklasa UICollectionViewLayout (może coś w rodzaju SBTableLayout ).
Z drugiej strony, jeśli naprawdę pytasz, czy istnieje czysty sposób na zrobienie tego z domyślnym UICollectionViewFlowLayout, to uważam, że nie ma sposobu. Nawet w przypadku samoczynnych rozmiarów komórek iOS8 nie jest to proste. Podstawowym problemem, jak powiedziałeś, jest to, że mechanizm układu przepływu nie zapewnia możliwości ustalenia jednego wymiaru i umożliwienia odpowiedzi drugiemu. (Ponadto, nawet gdybyś mógł, dodatkowa złożoność wymagałaby dwóch przebiegów układu w celu ustalenia rozmiaru etykiet wielowierszowych. Może to nie pasować do sposobu, w jaki komórki samodopasowujące chcą obliczać wszystkie rozmiary za pomocą jednego wywołania systemLayoutSizeFittingSize).
Jeśli jednak nadal chcesz utworzyć układ podobny do widoku tabeli z układem przepływu, z komórkami, które określają swój własny rozmiar i naturalnie reagują na szerokość widoku kolekcji, oczywiście jest to możliwe. Nadal istnieje bałagan. Zrobiłem to za pomocą „komórki rozmiaru”, tj. Niewyświetlanego UICollectionViewCell, który kontroler przechowuje tylko do obliczania rozmiarów komórek.
To podejście składa się z dwóch części. Pierwsza część jest przeznaczona dla delegata widoku kolekcji do obliczenia prawidłowego rozmiaru komórki poprzez uwzględnienie szerokości widoku kolekcji i użycie komórki określającej rozmiar do obliczenia wysokości komórki.
W swoim UICollectionViewDelegateFlowLayout zaimplementujesz taką metodę:
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
}
Spowoduje to, że widok kolekcji ustawi szerokość komórki na podstawie otaczającego widoku kolekcji, ale następnie poprosi komórkę określającą rozmiar o obliczenie wysokości.
Druga część polega na tym, aby komórka określająca rozmiar używała własnych ograniczeń AL do obliczenia wysokości. Może to być trudniejsze niż powinno, ponieważ wielowierszowe UILabel wymagają efektywnego dwuetapowego procesu tworzenia układu. Praca wykonywana jest metodą preferredLayoutSizeFittingSize
, która wygląda następująco:
/*
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
}
(Ten kod jest zaadaptowany z przykładowego kodu Apple z przykładowego kodu sesji WWDC2014 w „Zaawansowanym widoku kolekcji”).
Kilka punktów, na które warto zwrócić uwagę. Wykorzystuje layoutIfNeeded () do wymuszenia układu całej komórki, aby obliczyć i ustawić szerokość etykiety. Ale to nie wystarczy. Uważam, że musisz również ustawić preferredMaxLayoutWidth
tak, aby etykieta używała tej szerokości z automatycznym układem. I tylko wtedy możesz użyć systemLayoutSizeFittingSize
, aby komórka obliczyła swoją wysokość z uwzględnieniem etykiety.
Czy podoba mi się to podejście? Nie!! Wydaje się zbyt skomplikowane, a układ dwukrotnie. Ale dopóki wydajność nie stanie się problemem, wolałbym wykonać układ dwa razy w czasie wykonywania, niż musieć dwukrotnie definiować go w kodzie, co wydaje się być jedyną alternatywą.
Mam nadzieję, że w końcu komórki samorozmiarowe będą działać inaczej, a to wszystko stanie się dużo prostsze.
Przykładowy projekt pokazujący to w pracy.
Ale dlaczego nie użyć po prostu samokalibrowych komórek?
Teoretycznie, nowe możliwości iOS8 dla "samokalibracji komórek" powinny uczynić to niepotrzebnym. Jeśli zdefiniowałeś komórkę za pomocą automatycznego układu (AL), widok kolekcji powinien być wystarczająco inteligentny, aby umożliwić jej rozmiar i prawidłowy układ. W praktyce nie widziałem żadnych przykładów, które sprawiłyby, że działałyby z etykietami wieloliniowymi. Myślę, że to po części ze względu na własny mechanizm doboru komórka jest nadal buggy.
Ale założę się, że dzieje się tak głównie ze względu na zwykłe zawiłości automatycznego układu i etykiet, które polega na tym, że etykiety UIL wymagają zasadniczo dwuetapowego procesu układania. Nie jest dla mnie jasne, w jaki sposób można wykonać oba kroki w przypadku ogniw samorozmiarowych.
I jak powiedziałem, to naprawdę praca dla innego układu. Istotą układu przepływu jest to, że umieszcza rzeczy, które mają rozmiar, zamiast ustalać szerokość i pozwalać im wybrać wysokość.
A co z preferowanymLayoutAttributesFittingAttributes:?
preferredLayoutAttributesFittingAttributes:
Metoda jest czerwony śledź, myślę. To jest tylko do wykorzystania z nowym mechanizmem samokalibracji komórek. Więc to nie jest odpowiedź, dopóki ten mechanizm jest zawodny.
A co słychać w systemlayoutSizeFittingSize :?
Masz rację, doktorzy są mylący.
Dokumentacja systemLayoutSizeFittingSize:
i systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
obie sugerują, że powinieneś przekazać tylko UILayoutFittingCompressedSize
i UILayoutFittingExpandedSize
jako targetSize
. Jednak sama sygnatura metody, komentarze nagłówka i zachowanie funkcji wskazują, że odpowiadają one dokładnej wartości targetSize
parametru.
W rzeczywistości, jeśli ustawisz UICollectionViewFlowLayoutDelegate.estimatedItemSize
, w celu włączenia nowego mechanizmu samoczynnego określania rozmiaru komórki, ta wartość wydaje się być przekazywana jako targetSize. I UILabel.systemLayoutSizeFittingSize
wydaje się zwracać dokładnie takie same wartości jak UILabel.sizeThatFits
. Jest to podejrzane, biorąc pod uwagę, że argument do systemLayoutSizeFittingSize
ma być przybliżonym celem, a argumentem do sizeThatFits:
ma być maksymalny opisywany rozmiar.
Więcej zasobów
Chociaż smutne jest myślenie, że taki rutynowy wymóg wymaga „zasobów badawczych”, myślę, że tak. Dobre przykłady i dyskusje to: