Określanie jednego wymiaru komórek w UICollectionView za pomocą układu Auto

W systemie iOS 8 UICollectionViewFlowLayout obsługuje automatyczną zmianę rozmiaru komórek na podstawie ich własnego rozmiaru zawartości. Powoduje to zmianę rozmiaru komórek zarówno pod względem szerokości, jak i wysokości w zależności od ich zawartości.

Czy można określić stałą wartość szerokości (lub wysokości) wszystkich komórek i zezwolić na zmianę rozmiaru innych wymiarów?

Dla prostego przykładu rozważ wielowierszową etykietę w komórce z ograniczeniami pozycjonującymi ją po bokach komórki. Etykieta wielowierszowa może być zmieniana w inny sposób sposoby dostosowania tekstu. Komórka powinna wypełnić szerokość widoku kolekcji i odpowiednio dostosować jej wysokość. Zamiast tego komórki mają przypadkowy rozmiar, a nawet powodują awarię, gdy rozmiar komórki jest większy niż nie przewijalny wymiar widoku kolekcji.


IOS 8 wprowadza metodę systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: dla każdej komórki w widoku kolekcji układ wywołuje tę metodę na komórce, przechodząc w szacowanym rozmiarze. Dla mnie sensowne byłoby obejście tej metody na komórce prześlij w podanym rozmiarze i ustaw ograniczenie poziome na wymagane, a niski priorytet na ograniczenie pionowe. W ten sposób wielkość pozioma jest stała do wartości ustawionej w układzie, a rozmiar pionowy może być elastyczny.

Coś takiego:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
    attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    return attributes;
}
Rozmiary podane tą metodą są jednak zupełnie dziwne. Dokumentacja tej metody jest dla mnie bardzo niejasna i wspomina o użyciu stałych UILayoutFittingCompressedSize UILayoutFittingExpandedSize które po prostu reprezentują rozmiar zerowy i dość duży.

Czy parametr size tej metody naprawdę jest tylko sposobem na podanie dwóch stałych? Czy nie ma sposobu, aby osiągnąć zachowanie oczekuję uzyskania odpowiedniej wysokości dla danego rozmiaru?


Alternatywne Rozwiązania

1) dodanie ograniczeń, które będą określać określoną szerokość komórki, pozwala uzyskać prawidłowy układ. Jest to słabe rozwiązanie, ponieważ ograniczenie to powinno być ustawione na rozmiar widoku kolekcji komórki, który nie ma bezpiecznego odniesienie do. Wartość tego ograniczenia może zostać przekazana, gdy komórka jest skonfigurowana, ale to również wydaje się całkowicie sprzeczne z intuicją. Jest to również niewygodne, ponieważ dodawanie ograniczeń bezpośrednio do komórki lub widoku zawartości powoduje wiele problemów.

2) Użyj widoku tabeli. Widoki tabel działają w ten sposób po wyjęciu z pudełka, ponieważ komórki mają stałą szerokość, ale nie pasuje to do innych sytuacji, takich jak układ iPada z komórkami o stałej szerokości w wielu kolumnach.

Author: Anthony Mattox, 2014-10-01

5 answers

Wygląda na to, że to, o co prosisz, jest sposobem użycia UICollectionView do stworzenia układu takiego jak UITableView. Jeśli naprawdę tego chcesz, dobrym sposobem jest użycie niestandardowej podklasy UICollectionViewLayout (może coś w stylu sbtablelayout ).

Z drugiej strony, jeśli naprawdę pytasz, czy istnieje czysty sposób, aby to zrobić z domyślnym UICollectionViewFlowLayout, to wierzę, że nie ma sposobu. Nawet z samowyzwalającymi się komórkami iOS8, nie jest proste. Podstawowym problemem, jak mówisz, jest to, że mechanizm układu przepływu nie zapewnia sposobu, aby naprawić jeden wymiar, a drugi odpowiedzieć. (Ponadto, nawet jeśli można, istnieje dodatkowa złożoność wokół konieczności dwóch przejść układu do rozmiaru etykiet wielowierszowych. Może to nie pasować do tego, jak samorozmiar komórki chcą obliczyć wszystkie rozmiary za pomocą jednego wywołania do systemLayoutSizeFittingSize.)

Jeśli jednak nadal chcesz utworzyć układ podobny do tableview z układem przepływu, z komórkami, które określają swój własny rozmiar i reagują naturalnie na szerokość widoku kolekcji, oczywiście jest to możliwe. Nadal jest bałagan. Zrobiłem to za pomocą "sizing cell", tj. nie wyświetlanej UICollectionViewCell, którą kontroler przechowuje tylko do obliczania rozmiarów komórek.

Istnieją dwie części tego podejścia. Pierwsza część polega na tym, aby delegat widoku kolekcji obliczył prawidłowy rozmiar komórki, biorąc pod uwagę szerokość widoku kolekcji i używając komórki rozmiaru aby obliczyć wysokość komórki.

W uicollectionviewdelegateflowlayout zaimplementujesz metodę taką jak:

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 widoku zamykającej kolekcji, ale następnie poprosi komórkę o obliczenie wysokości.

Druga część polega na tym, aby komórka rozmiaru używała własnych ograniczeń AL do obliczania wysokości. Może to być trudniejsze niż powinno być, ze względu na sposób, w jaki multi-line UILabel ' s skutecznie wymagają dwuetapowego procesu układania. Praca jest wykonywana w metodzie preferredLayoutSizeFittingSize, która wygląda tak:

 /*
 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 "Advanced Collection View".)

Kilka punktów do zauważenia. Używa metody layoutIfNeeded (), aby wymusić układ całej komórki, aby obliczyć i ustawić szerokość etykiety. Ale to nie wystarczy. Uważam, że należy również ustawić preferredMaxLayoutWidth, aby etykieta użyła tego szerokość z automatycznym układem. I tylko wtedy możesz użyć systemLayoutSizeFittingSize, aby komórka obliczyła jej wysokość, biorąc pod uwagę Etykietę.

Czy podoba mi się takie podejście? Nie!! Wydaje się zbyt skomplikowana i robi dwa razy więcej. Ale dopóki wydajność nie staje się problemem, wolę wykonać układ dwa razy w czasie wykonywania, niż zdefiniować go dwa razy w kodzie, co wydaje się być jedyną alternatywą.

Mam nadzieję, że w końcu komórki samoklejące będą działać inaczej i wszystko będzie prostsze.

Przykładowy projekt pokazujący go w pracy.

Ale dlaczego po prostu nie użyć komórek samoklejących?

Teoretycznie, nowe udogodnienia iOS8 dla "komórek samoklejących" powinny uczynić to zbędnym. Jeśli zdefiniowano komórkę z automatycznym układem (AL), widok kolekcji powinien być na tyle inteligentny, aby pozwalał na jego rozmiar i prawidłowe ułożenie. W praktyce nie widziałem żadnych przykładów, które pomogły w pracy z etykietami wielowierszowymi. Myślę, że to częściowo dlatego, że mechanizm samoklejący komórki jest nadal wadliwy.

Ale założę się, że to głównie z powodu zwykłej sztuczki układu Auto i etykiet, czyli tego, że UILabels wymagają zasadniczo dwuetapowego procesu układania. Nie jest dla mnie jasne, w jaki sposób można wykonać oba kroki z samoklejącymi się komórkami.

I jak mówiłem, to jest naprawdę praca dla innego layoutu. Jest częścią istoty flow layout, że pozycjonuje rzeczy, które mają rozmiar, a nie naprawia szerokość i pozwala im wybrać ich wysokość.

A co z preferredLayoutAttributesFittingattributes: ?

Metoda preferredLayoutAttributesFittingAttributes: to chyba czerwony śledź. To jest tylko tam, aby być używane z nowym mechanizmem SELF-sizing cell. Więc to nie jest odpowiedź, dopóki ten mechanizm jest zawodny.

A o co chodzi z systemlayoutSizeFittingSize:?

Masz rację, doktorzy są mylący.

Dokumenty na systemLayoutSizeFittingSize: i systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: sugerują, że należy tylko podaj UILayoutFittingCompressedSize i UILayoutFittingExpandedSize jako targetSize. Jednak sama sygnatura metody, komentarze nagłówka i zachowanie funkcji wskazują, że odpowiadają one dokładnej wartości parametru targetSize.

W rzeczywistości, jeśli ustawisz UICollectionViewFlowLayoutDelegate.estimatedItemSize, aby włączyć nowy mechanizm SELF-sizing cell, ta wartość wydaje się być przekazywana jako targetSize. I UILabel.systemLayoutSizeFittingSize wydaje się zwracać dokładnie te same wartości co UILabel.sizeThatFits. Jest to podejrzane, biorąc pod uwagę, że argument systemLayoutSizeFittingSize ma być rough target i argument to sizeThatFits: ma być maksymalnym ograniczeniem.

Więcej Zasobów

[17]}choć smutno jest myśleć, że taki rutynowy wymóg powinien wymagać "zasobów badawczych", myślę, że tak. Dobre przykłady i dyskusje to:
 124
Author: algal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-02 14:58:45

Jest czystszy sposób, aby to zrobić niż niektóre z innych odpowiedzi tutaj, i to działa dobrze. Powinien być wydajny (widoki kolekcji ładują się szybko, bez zbędnych przejść automatycznego układu itp.) i nie ma żadnych "magicznych liczb", takich jak Stała szerokość widoku kolekcji. Zmiana rozmiaru widoku kolekcji, np. podczas obracania, a następnie unieważnienie układu, również powinna działać świetnie.

1. Utwórz podklasę flow layout

class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {

    // Don't forget to use this class in your storyboard (or code, .xib etc)

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
        guard let collectionView = collectionView else { return attributes }
        attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
        return attributes
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let allAttributes = super.layoutAttributesForElementsInRect(rect)
        return allAttributes?.flatMap { attributes in
            switch attributes.representedElementCategory {
            case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
            default: return attributes
            }
        }
    }
}

2. Zarejestruj swój widok kolekcji do automatycznego rozmiar

// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)

flowLayout.estimatedItemSize = flowLayout.itemSize

3. Użyj predefiniowanej szerokości + niestandardowej wysokości w podklasie komórki

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    return layoutAttributes
}
 48
Author: Jordan Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-25 00:29:33

W systemie iOS 9 w kilku linijkach kodów można to zrobić w prosty sposób - np. poziomo (ustawiając wysokość widoku kolekcji do wysokości):]}

Uruchom układ przepływu widoku kolekcji za pomocą estimatedItemSize, aby włączyć SELF-sizing cell :

self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);

Zaimplementuj delegata układu widoku kolekcji (w kontrolerze widoku przez większość czasu), collectionView:layout:sizeForItemAtIndexPath:. Celem jest ustawienie stałej wysokości (lub szerokości) na wymiar widoku kolekcji. Wartość 10 może być dowolna, ale należy ustawić ją na wartość, która nie łamie ograniczeń:

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}

Zastąp metodę niestandardowej komórki preferredLayoutAttributesFittingAttributes:, ta część oblicza dynamiczną szerokość komórki na podstawie ograniczeń układu automatycznego i wysokości, którą właśnie Ustawiłeś :

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
    UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
    float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
    CGRect frame = attributes.frame;
    frame.size.width = desiredWidth;
    attributes.frame = frame;
    return attributes;
}
 6
Author: Tanguy G.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-16 10:51:26

Spróbuj ustawić szerokość w preferowanych atrybutach układu:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
    CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGRect newFrame = attr.frame;
    newFrame.size.height = size.height;
    attr.frame = newFrame;
    return attr;
}

Oczywiście chcesz również upewnić się, że poprawnie skonfigurujesz swój układ do:

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];

Oto coś, co umieściłem Github, który używa komórek o stałej szerokości i obsługuje typ dynamiczny, więc wysokość komórek aktualizuje się wraz ze zmianą rozmiaru czcionki systemowej.

 4
Author: Daniel Galasko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-14 07:51:05

Tak można to zrobić za pomocą automatycznego układu programowo i ustawiając ograniczenia w storyboard lub xib. Musisz dodać ograniczenie rozmiaru szerokości, aby pozostała stała i ustawić wysokość większą lub równą.
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/

http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
Mam nadzieję, że będzie to pomocne i rozwiąże twój problem.

 0
Author: Dhaivat Vyas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-08 18:26:15