Dynamiczna szerokość komórki UICollectionView w zależności od szerokości etykiety

94

Mam UICollectionView, który ładuje komórki z komórki wielokrotnego użytku, która zawiera etykietę. Tablica zawiera treść dla tej etykiety. Mogę łatwo zmienić szerokość etykiety w zależności od szerokości treści za pomocą sizeToFit. Ale nie mogę dopasować komórki do etykiety.

Oto kod

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    arrayOfStats =  @[@"time:",@"2",@"items:",@"10",@"difficulty:",@"hard",@"category:",@"main"];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:     (NSInteger)section{
    return [arrayOfStats count];
}

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

    return CGSizeMake(??????????);
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{

    return 1;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{

    Cell *cell = (Cell *) [collectionView dequeueReusableCellWithReuseIdentifier:@"qw" forIndexPath:indexPath];
    cell.myLabel.text = [NSString stringWithFormat:@"%@",[arrayOfStats objectAtIndex:indexPath.item]];
    // make label width depend on text width
    [cell.myLabel sizeToFit];

    //get the width and height of the label (CGSize contains two parameters: width and height)
    CGSize labelSize = cell.myLbale.frame.size;

    NSLog(@"\n width  = %f height = %f", labelSize.width,labelSize.height);

    return cell;
}
miazga
źródło
podobny problem ... stackoverflow.com/questions/24915443/… ???
Fattie

Odpowiedzi:

85

W sizeForItemAtIndexPathzamian rozmiar tekstu

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

    return [(NSString*)[arrayOfStats objectAtIndex:indexPath.row] sizeWithAttributes:NULL];
}
Basheer_CAD
źródło
4
Nie możesz sobie wyobrazić, jak Ci dziękuję! To naprawdę działa. Teraz muszę tylko rozwiązać problem [cell.myLabel sizeToFit], ponieważ pojawia się w pełnym rozmiarze dopiero po przewinięciu. Ale nie byłem nawet blisko twojego rozwiązania.
miazga
dziękuję za pomoc, ale wciąż mam jeden problem. Kiedy odkomentuję element [cell.myLabel sizeToFit], mam obcięte słowa i litery u dołu, ale po przewinięciu jest ok (słowa mają normalny rozmiar, a litery nieco podskakują). Jeśli skomentuję i wyłączę komunikat [cell.myLabel sizeToFit] (postanowiłem pobawić się z IB i działa dobrze), mam wycięte słowa na końcu i na dole. Zrobiłem zrzut ekranu goo.gl/HaoqQV Nie jest zbyt ostry na wyświetlaczach bez siatkówki, ale widać, że litery się ucięły . Twoja sugestia, jak rozwiązać problem, zostanie naprawdę doceniona!
miazga
2
zamiast sizeToFit, użyj sizeWithAttributes, aby uzyskać CGSize tekstu, a następnie ustaw ramkę etykiety na nowy rozmiar.
Basheer_CAD
dziękuję za sugestię, ale nadal mam wyciętą etykietę myLabel na dole i na końcu. Może się mylę, wdrażając twoją sugestię. Oto mój kod
miazga
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"qw" forIndexPath:indexPath]; cell.myLbale.text = [NSString stringWithFormat:@"%@",[arrayOfStats objectAtIndex:indexPath.item]]; CGSize textSize; textSize = [[arrayOfStats objectAtIndex:indexPath.item] sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12.0f]}]; [cell.myLbale sizeThatFits:textSize]; //[cell.myLbale sizeToFit]; return cell; }
miazga
50

Swift 4.2+

Zasada jest taka:

  1. Upewnij się, że delegowanie zostało skonfigurowane (np. collectionView.delegate = self)

  2. Implement UICollectionViewDelegateFlowLayout(zawiera niezbędną sygnaturę metody).

  3. collectionView...sizeForItemAtMetoda wywołania .

  4. Nie ma potrzeby, aby most-cast String, aby NSStringdo połączenia size(withAttributes:metody. Swift Stringma to po wyjęciu z pudełka.

  5. Atrybuty są takie same, jak ustawiłeś (NS)AttributedString, np. Rodzina czcionki, rozmiar, grubość itp. Parametr opcjonalny.


Przykładowe rozwiązanie:

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return "String".size(withAttributes: nil)
    }
}

Ale najprawdopodobniej chciałbyś określić konkretne atrybuty ciągu odpowiadające twojej komórce, stąd ostateczny wynik będzie wyglądał następująco:

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // dataArary is the managing array for your UICollectionView.
        let item = dataArray[indexPath.row]
        let itemSize = item.size(withAttributes: [
            NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 14)
        ])
        return itemSize
    }
}

Dlaczego NIE NALEŻY używać UILabel do obliczania rozmiaru? Oto sugerowane rozwiązanie:

let label = UILabel(frame: CGRect.zero)
label.text = textArray[indexPath.item]
label.sizeToFit()

Tak, uzyskasz ten sam wynik. Wygląda na uproszczone i może wydawać się rozwiązaniem, które należy wykonać. Ale to niewłaściwe, ponieważ: 1) jest drogie, 2) napowietrzne i 3) brudne.

Jest to kosztowne, ponieważ UILabel jest złożonym obiektem UI, który jest tworzony przy każdej iteracji, ilekroć twoja komórka ma się pokazać, nawet jeśli nie potrzebujesz tego tutaj. Jest to ogólne rozwiązanie, ponieważ wystarczy uzyskać rozmiar tekstu, ale posuwasz się nawet do utworzenia całego obiektu interfejsu użytkownika. I z tego powodu jest brudny.

Hexfire
źródło
1
nie zapomnij ustawićcollectionView.delegate == self // or whatever-object-which-do-it
Fitsyu
Świetna odpowiedź. Chociaż rozmiar, który dostawałem był nieco mniejszy niż powinien, na różnych długościach sznurków postanowiłem sam nieco zmodyfikować rozmiar. Dodanie dodatkowych 5 punktów do szerokości CGSize(width: title.size(withAttributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)]).width + 5, height: 50)
Starsky
37

Znalazłem małą sztuczkę do szybkiego 4.2

Dla dynamicznej szerokości i stałej wysokości:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let label = UILabel(frame: CGRect.zero)
        label.text = textArray[indexPath.item]
        label.sizeToFit()
        return CGSize(width: label.frame.width, height: 32)
    }

Dla dynamicznej wysokości i stałej szerokości:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let label = UILabel(frame: CGRect.zero)
            label.text = textArray[indexPath.item]
            label.sizeToFit()
            return CGSize(width: 120, height: label.frame.height)
        }
Hassan Izhar
źródło
8
Uważaj, używając tego. Tworzenie i rysowanie nowego UILabel dla każdego obliczenia komórki jest bardzo kosztowne.
AnthonyR
trzeba dodać UICollectionViewDelegateFlowLayout
cristianego
3
Aby odnieść się do komentarza na temat tego, że tworzenie fałszywej etykiety jest kosztowne, może mógłbyś po prostu utworzyć jedną fikcyjną etykietę zamiast kilku. Wszystko, czego naprawdę chcesz od tego, to rozmiar tekstu z atrybutów etykiety. Ostatecznie jednak jest to w zasadzie to samo, co obliczono rozmiar tekstu za pomocą sizeWithAttributes, więc może jest to preferowana odpowiedź.
Stephen Paul
@Hassan Dzięki bracie, to zadziałało dla mnie, ale znalazłem problem z szerokością, więc dodałem zwrot CGSize (width: label.frame.width + 50, height: 32). Wtedy zadziałało, myślę, że ta odpowiedź powinna znajdować się na pierwszej liście.
Arshad Shaik,
27

Sprawdź poniższy kod, który możesz podać bardzo krótki rozmiar CG.

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

    NSString *testString = @"SOME TEXT";
    return [testString sizeWithAttributes:NULL];
}
Raza.najam
źródło
@Vineesh TP Dziękuję za odpowiedź, na pewno to sprawdzę i dam znać!
miazga
+1000 Ten zdecydowanie działa. Twój i Basheer powinien mieć razem zielony znacznik wyboru.
rocky szop
Masz pomysł, jak to zrobić za pomocą Swift 3?
UKDataGeek
1
Dzięki człowiekowi, to czas na imprezę !!
Ravi
18

W Swift 3

let size = (arrayOfStats[indexPath.row] as NSString).size(attributes: nil)
Johnson
źródło
14

Szybki 4

let size = (arrayOfStats[indexPath.row] as NSString).size(withAttributes: nil)
Barath
źródło
0

// dodaj w widoku didload

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    [layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
    layout.estimatedItemSize = CGSizeMake(self.breadScrumbCollectionView.frame.size.width, 30); 
self.breadScrumbCollectionView.collectionViewLayout = layout;
Apurva Gaikwad
źródło