boundingRectWithSize for NSAttributedString zwraca nieprawidłowy rozmiar

151

Próbuję uzyskać prostokąt dla przypisanego ciągu, ale wywołanie boundingRectWithSize nie uwzględnia rozmiaru, który przekazuję i zwraca prostokąt o wysokości pojedynczej linii w przeciwieństwie do dużej wysokości (jest to długi ciąg). Eksperymentowałem, przekazując bardzo dużą wartość wysokości, a także 0, jak w poniższym kodzie, ale zwracany prostokąt jest zawsze taki sam.

CGRect paragraphRect = [attributedText boundingRectWithSize:CGSizeMake(300,0.0)
  options:NSStringDrawingUsesDeviceMetrics
  context:nil];

Czy to jest zepsute, czy muszę zrobić coś innego, aby zwróciło prostokąt dla zawiniętego tekstu?

RunLoop
źródło
5
Czy zdarzyło Ci się mieć styl akapitowy z obcinaniem / przycinaniem lineBreakMode?
danyowdee
1
jeśli to czytasz, ponieważ UILabel mierzy / zawija do nieprawidłowej szerokości, spójrz na stackoverflow.com/questions/46200027/… . w szczególności ustawienie NSAllowsDefaultLineBreakStrategy na false podczas uruchamiania aplikacji.
eric

Odpowiedzi:

312

Wygląda na to, że nie podałeś właściwych opcji. Do pakowania etykiet należy zapewnić co najmniej:

CGRect paragraphRect =
  [attributedText boundingRectWithSize:CGSizeMake(300.f, CGFLOAT_MAX)
  options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
  context:nil];

Uwaga: jeśli oryginalna szerokość tekstu jest mniejsza niż 300. f nie będzie zawijania linii, więc upewnij się, że rozmiar zszywania jest prawidłowy, w przeciwnym razie nadal uzyskasz błędne wyniki.

Ed McManus
źródło
25
Każdy musi spojrzeć na tę odpowiedź, ponieważ działa. Być może potrzebna jest jaśniejsza dokumentacja dla tej metody i ogólnie przypisanych ciągów; nie jest oczywiste, gdzie należy szukać.
macserv
31
Uwaga! To nie zawsze działa! Czasami zwracana szerokość jest większa niż szerokość parametru size. Dokumentacja metody Even Apples stwierdza: „Ograniczenia określone w parametrze size stanowią dla modułu renderującego wskazówki dotyczące rozmiaru ciągu. Jednak rzeczywisty prostokąt ograniczający zwracany przez tę metodę może być większy niż ograniczenia, jeśli potrzebne jest dodatkowe miejsce renderuje cały ciąg. "
Klaas
75
API dla NSAttributedString i boundingRectWithSize jest absolutnie szokujące.
malhal
27
Innym problemem jest to, że wysokość (i przypuszczalnie szerokość) zwracana w paragraphRect jest prawie zawsze wartością ułamkową. Więc może wyjść mówiąc 141,3 jako wysokość. Musisz użyć wyniku z ceilf(paragraphRect.size.height), aby został zaokrąglony w górę. Cały czas o tym zapominam i zastanawiam się, dlaczego moje etykiety wciąż się przycinają.
jamone
39
Zawsze boundingRectWithSize:zawijam swoje obliczenia w CGRectIntegral (), co CGRectIntegral rounds the rectangle’s origin downward and its size upward to the nearest whole integersw tym przypadku zaokrągla wysokość i szerokość, aby zapewnić, że nie nastąpi przycinanie, jeśli wysokość lub szerokość są wartością ułamkową.
runmad
47

Z jakiegoś powodu boundingRectWithSize zawsze zwraca nieprawidłowy rozmiar. Znalazłem rozwiązanie. Istnieje metoda UItextView -sizeThatFits, która zwraca właściwy rozmiar dla zestawu tekstowego. Dlatego zamiast używać boundingRectWithSize, utwórz UITextView z losową ramką i wywołaj jego sizeThatFits z odpowiednią szerokością i wysokością CGFLOAT_MAX. Zwraca rozmiar, który będzie miał odpowiednią wysokość.

   UITextView *view=[[UITextView alloc] initWithFrame:CGRectMake(0, 0, width, 10)];   
   view.text=text;
   CGSize size=[view sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
   height=size.height; 

Jeśli obliczasz rozmiar w pętli while, nie zapomnij dodać, że w puli autorelease, ponieważ zostanie utworzonych n liczba utworzonych UITextView, pamięć uruchomieniowa aplikacji wzrośnie, jeśli nie użyjemy puli autoreleasepool.

shoan
źródło
1
To działa. Innymi sposobami, które próbowałem, nigdy nie mogłem dostać się do pracy. Myślę, że mój problem wynika ze złożoności przypisywanej struny, którą przypisałem. Pochodził z pliku RTF i używał różnych czcionek, odstępów między wierszami, a nawet cieni, i pomimo używania UsesLineFragmentOrigin i UsesFontLeading nigdy nie mogłem uzyskać wyniku, który byłby wystarczająco wysoki, a problem był tym gorszy, im dłuższy był przypisany ciąg. Domyślam się, że dla stosunkowo prostych łańcuchów może działać metoda boundingRectWithSize . Naprawdę potrzebują lepszej metody pracy z tym, imo.
John Bushnell
Czy nie sądzisz, że tworzenie takiej ilości UITextViewrazy, ile heightForRowAtIndexPathwywołań metody jest przesadą ? to wymaga czasu
János
Zgadzam się z tobą @ János, ale to było jedyne rozwiązanie, które zadziałało dla mnie.
shoan
btw poprosiłem o właściwe rozwiązanie: stackoverflow.com/questions/32495744/…
János
to jedyne rozwiązanie, które działa. Ten problem ma 9 lat i Apple najwyraźniej nie chce go rozwiązać lub ich inżynierowie są zbyt słabi, aby znaleźć rozwiązanie tego problemu. Mowa tutaj o iOS 11, wciąż zawierającym ten sam błąd.
Duck
31

Ed McManus z pewnością dostarczył klucza do tego, aby to zadziałało. Znalazłem przypadek, który nie działa

UIFont *font = ...
UIColor *color = ...
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                     font, NSFontAttributeName,
                                     color, NSForegroundColorAttributeName,
                                     nil];

NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString: someString attributes:attributesDictionary];

[string appendAttributedString: [[NSAttributedString alloc] initWithString: anotherString];

CGRect rect = [string boundingRectWithSize:constraint options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];

rect nie będzie miał prawidłowej wysokości. Zauważ, że inny ciąg (który jest dołączany do ciągu ) został zainicjowany bez słownika atrybutów. Jest to uzasadniony inicjator dla anotherString, ale boundingRectWithSize : w tym przypadku nie podaje dokładnego rozmiaru.

Paul Heller
źródło
33
Dziękuję Ci! Okazuje się, że KAŻDA część NSAttributedString musi mieć ustawiony słownik z co najmniej ustawieniami NSFontAttributeName i NSForegroundColorAttributeName, jeśli chcesz, aby boundingRectWithSize faktycznie działało! Nigdzie nie widzę tego udokumentowanego.
Ben Wheeler
3
Zauważ, że wydaje się również, że różne NSAttributedStrings, które są połączone w jeden, muszą wszystkie używać TEGO SAMEGO słownika (a tym samym tej samej czcionki), aby boundingRectWithSize działało poprawnie!
Ben Wheeler
1
To rozwiązanie jest bardzo podobne do tego, którego używam dla mojej kategorii UILabel dla „shrink to fit”. Kluczem dla mnie, aby to działało, było utworzenie prostokąta ograniczającego o wysokości FLT_MAX. Bez tego miałbym prostokąt tylko dla jednej linii.
dre1138
+1 przez cały czas. To naprawdę mnie dopadło, więc dziękuję za rozpracowanie tego.
Wex
Miałem ten problem. Użyłem spacji i znaków nowej linii NSMutableAttributedStringbez atrybutów i otrzymałem zły rozmiar.
vbezhenar
28

Moja ostateczna decyzja po długim badaniu:
- boundingRectWithSizefunkcja zwraca prawidłowy rozmiar tylko dla nieprzerwanej sekwencji znaków! W przypadku, gdy łańcuch zawiera spacje lub coś innego (nazywane przez Apple „Niektóre z glifów”) - nie jest możliwe uzyskanie rzeczywistego rozmiaru prostego potrzebnego do wyświetlenia tekstu!
Zastąpiłem spacje w moich napisach literami i od razu uzyskałem poprawny wynik.

Apple mówi tutaj: https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrectwithsize

Ta metoda zwraca rzeczywiste granice glifów w ciągu. Niektóre glify (na przykład spacje) mogą nakładać się na ograniczenia układu określone przez przekazany rozmiar, więc w niektórych przypadkach wartość szerokości składnika rozmiaru elementu zwracana wartość CGRectmoże przekroczyć wartość szerokości parametru rozmiaru. "

Konieczne jest więc znalezienie innego sposobu obliczenia rzeczywistego współczynnika ...


Po długim badaniu procesu ostatecznie znaleziono rozwiązanie !!! Nie jestem pewien, czy będzie dobrze działać we wszystkich przypadkach z tym związanych UITextView, ale wykryto główną i ważną rzecz!

boundingRectWithSizefunction, jak również CTFramesetterSuggestFrameSizeWithConstraints(i wiele innych metod) obliczy rozmiar i część tekstu poprawnie, jeśli zostanie użyty poprawny prostokąt. Na przykład - UITextViewma textView.bounds.size.width- i ta wartość nie jest rzeczywistym prostokątem używanym przez system podczas rysowania tekstu UITextView.

Znalazłem bardzo interesujący parametr i wykonałem proste obliczenia w kodzie:

CGFloat padding = textView.textContainer.lineFragmentPadding;  
CGFloat  actualPageWidth = textView.bounds.size.width - padding * 2;

A magia działa - teraz wszystkie moje teksty są poprawne! Cieszyć się!

Юрий Сталоверов
źródło
1
tak, zauważyłem, że też nie trzyma ograniczeń szerokości, zawsze wychodzi większy. To naprawdę nie jest fajne, odstąpili od metody pracy i teraz musimy sobie z tym poradzić.
Boris Gafurov
1
Panie, jesteś moim nowym bohaterem! To doprowadzało mnie do szału. Ustawiam wstawki textContainers, więc musiałem użyć textView.textContainer.size.width zamiast textView.bounds.size.witdh.
GCBenson
Nie mogłem wystarczająco głosować. Tak dziwne, że spacje nie są uwzględniane w obliczeniach ograniczających.
Chase Holland
1
Zastąpienie spacji jakimś
fikcyjnym
1
uratowało to moją karierę i relacje z zespołem kontroli jakości. Jestem ci winien piwo!
lucaslt89
14

Wersja Swift four

let string = "A great test string."
let font = UIFont.systemFont(ofSize: 14)
let attributes: [NSAttributedStringKey: Any] = [.font: font]
let attributedString = NSAttributedString(string: string, attributes: attributes)
let largestSize = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)

//Option one (best option)
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
let textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(), nil, largestSize, nil)

//Option two
let textSize = (string as NSString).boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], attributes: attributes, context: nil).size

//Option three
let textSize = attributedString.boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], context: nil).size

Pomiar tekstu za pomocą CTFramesetter działa najlepiej, ponieważ zapewnia rozmiary całkowite i dobrze obsługuje znaki emoji i inne znaki Unicode.

David Rees
źródło
10

Nie udało mi się z żadną z tych sugestii. Mój ciąg zawierał wypunktowane znaki Unicode i podejrzewam, że powodowały żal w obliczeniach. Zauważyłem, że UITextView radził sobie dobrze z rysunkiem, więc spojrzałem na to, aby wykorzystać jego obliczenia. Zrobiłem co następuje, co prawdopodobnie nie jest tak optymalne jak metody rysowania NSString, ale przynajmniej jest dokładne. Jest to również nieco bardziej optymalne niż inicjowanie UITextView tylko do wywołania -sizeThatFits:.

NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(width, CGFLOAT_MAX)];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:formattedString];
[textStorage addLayoutManager:layoutManager];

const CGFloat formattedStringHeight = ceilf([layoutManager usedRectForTextContainer:textContainer].size.height);
Ricky
źródło
Mogły to nie być kropki Unicode, mogły to być spacje na końcu ciągu. Zobacz odpowiedź powyżej: stackoverflow.com/a/25941139/337934 .
SamB
9

Jeśli chcesz uzyskać prostokąt ograniczający, obcięcie ogona, to pytanie może ci pomóc.

CGFloat maxTitleWidth = 200;

NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = NSLineBreakByTruncatingTail;

NSDictionary *attributes = @{NSFontAttributeName : self.textLabel.font,
                             NSParagraphStyleAttributeName: paragraph};

CGRect box = [self.textLabel.text
              boundingRectWithSize:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)
              options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
              attributes:attributes context:nil];
Roman B.
źródło
9

Okazuje się, że KAŻDA część NSAttributedString musi mieć ustawiony słownik z co najmniej ustawieniami NSFontAttributeName i NSForegroundColorAttributeName, jeśli chcesz, aby boundingRectWithSize faktycznie działało!

Nigdzie nie widzę tego udokumentowanego.

Ben Wheeler
źródło
Dzięki, to było dla mnie rozwiązanie, z wyjątkiem tego, że stwierdziłem, że potrzebny jest tylko NSFontAttributeName, a nie NSForegroundColorAttributeName
Marmoy
7

@warrenm Przykro mi to mówić, że metoda framesetter nie zadziałała.

Mam to Ta funkcja może pomóc nam określić rozmiar ramki potrzebny dla zakresu ciągów NSAttributedString w zestawie SDK telefonu iPhone / iPada dla danej szerokości:

Może służyć do dynamicznej wysokości komórek UITableView

- (CGSize)frameSizeForAttributedString:(NSAttributedString *)attributedString
{
    CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
    CGFloat width = YOUR_FIXED_WIDTH;

    CFIndex offset = 0, length;
    CGFloat y = 0;
    do {
        length = CTTypesetterSuggestLineBreak(typesetter, offset, width);
        CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(offset, length));

        CGFloat ascent, descent, leading;
        CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

        CFRelease(line);

        offset += length;
        y += ascent + descent + leading;
    } while (offset < [attributedString length]);

    CFRelease(typesetter);

    return CGSizeMake(width, ceil(y));
}

Podziękowania dla HADDAD ISSA >>> http://haddadissa.blogspot.in/2010/09/compute-needed-heigh-for-fixed-width-of.html

Karan Alangat
źródło
Nie działa dla mnie na żadnym urządzeniu (iPhone 4 i 5). Zarówno na urządzeniu z iOS7.1.2, jak i 4s Simulator z iOS8.3 :(
sanjana
Potrzebujesz importu?
jose920405
@KaranAlangat yes#import <CoreText/CoreText.h>
jose920405
7

Zauważyłem, że preferowane rozwiązanie nie obsługuje łamania linii.

Odkryłem, że to podejście działa we wszystkich przypadkach:

UILabel* dummyLabel = [UILabel new];
[dummyLabel setFrame:CGRectMake(0, 0, desiredWidth, CGFLOAT_MAX)];
dummyLabel.numberOfLines = 0;
[dummyLabel setLineBreakMode:NSLineBreakByWordWrapping];
dummyLabel.attributedText = myString;
[dummyLabel sizeToFit];
CGSize requiredSize = dummyLabel.frame.size;
dmc
źródło
W moim przypadku powinienem wykonać obliczenia w wątku w tle, a użycie elementów UI jest niedozwolone
Lubbo
4

Miałem ten sam problem z brakiem dokładnego rozmiaru przy użyciu tych technik i zmieniłem swoje podejście, aby to działało.

Mam długi przypisany ciąg, który próbowałem dopasować do widoku przewijania, aby wyświetlał się poprawnie bez obcięcia. Aby tekst działał niezawodnie, w ogóle nie ustawiłem wysokości jako ograniczenia, a zamiast tego pozwoliłem, aby wewnętrzny rozmiar przejął. Teraz tekst wyświetla się poprawnie bez obcięcia i nie muszę obliczać wysokości.

Przypuszczam, że gdybym musiał niezawodnie uzyskać wysokość, utworzyłbym widok, który jest ukryty i te ograniczenia, i uzyskałbym wysokość ramy po zastosowaniu ograniczeń.

Brennan
źródło
2
Czy możesz dodać przykładowy kod, aby to zademonstrować? Dzięki.
Nathan Buggia,
3

Jestem trochę spóźniony do gry - ale próbowałem znaleźć sposób, który działa, aby znaleźć obwiednię, która będzie pasować do przypisanego ciągu, aby utworzyć pierścień ostrości, tak jak robi to edycja pliku w Finderze. wszystko, czego próbowałem, zawiodło, gdy na końcu ciągu znajdują się spacje lub wiele spacji w ciągu. boundingRectWithSizenie udaje się również z tego powoduCTFramesetterCreateWithAttributedString .

Użycie NSLayoutManagerponiższego kodu wydaje się załatwiać sprawę we wszystkich przypadkach, które znalazłem do tej pory i zwraca prostokąt, który idealnie ogranicza ciąg. Bonus: jeśli zaznaczysz tekst, krawędzie zaznaczenia idą w górę do granic zwróconego prostego. Poniższy kod używa layoutManager z pliku NSTextView.

NSLayoutManager* layout = [self layoutManager];
NSTextContainer* container = [self textContainer];

CGRect focusRingFrame = [layout boundingRectForGlyphRange:NSMakeRange(0, [[self textStorage] length]) inTextContainer:container];
Chris Walken
źródło
2
textView.textContainerInset = UIEdgeInsetsZero;
NSString *string = @"Some string";
NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:12.0f], NSForegroundColorAttributeName:[UIColor blackColor]};
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
[textView setAttributedText:attributedString];
CGRect textViewFrame = [textView.attributedText boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.view.frame)-8.0f, 9999.0f) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
NSLog(@"%f", ceilf(textViewFrame.size.height));

Działa doskonale na wszystkich czcionkach!

Dmitry Coolerov
źródło
1

Miałem ten sam problem, ale zauważyłem, że ograniczenie wysokości zostało ustawione poprawnie. Więc zrobiłem co następuje:

-(CGSize)MaxHeighForTextInRow:(NSString *)RowText width:(float)UITextviewWidth {

    CGSize constrainedSize = CGSizeMake(UITextviewWidth, CGFLOAT_MAX);

    NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                          [UIFont fontWithName:@"HelveticaNeue" size:11.0], NSFontAttributeName,
                                          nil];

    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:RowText attributes:attributesDictionary];

    CGRect requiredHeight = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];

    if (requiredHeight.size.width > UITextviewWidth) {
        requiredHeight = CGRectMake(0, 0, UITextviewWidth, requiredHeight.size.height);
    }

    return requiredHeight.size;
}
Marcel
źródło
1
    NSDictionary *stringAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                      [UIFont systemFontOfSize:18], NSFontAttributeName,
                                      [UIColor blackColor], NSForegroundColorAttributeName,
                                      nil];

    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:myLabel.text attributes:stringAttributes];
    myLabel.attributedText = attributedString; //this is the key!

    CGSize maximumLabelSize = CGSizeMake (screenRect.size.width - 40, CGFLOAT_MAX);

    CGRect newRect = [myLabel.text boundingRectWithSize:maximumLabelSize
                                                       options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                    attributes:stringAttributes context:nil];

    self.myLabelHeightConstraint.constant = ceilf(newRect.size.height);

Wypróbowałem wszystko na tej stronie i nadal miałem jeden przypadek dla UILabel, który nie był poprawnie formatowany. Faktyczne ustawienie atrybutu attributeText na etykiecie ostatecznie rozwiązało problem.

Lil
źródło
1

Zauważyłem tylko, że prostokąt, z którego powrócił, (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)contextmiałby większą szerokość niż ta, którą przekazałem. Kiedy to się stanie, mój ciąg zostałby obcięty. Rozwiązałem to w ten sposób:

NSString *aLongString = ...
NSInteger width = //some width;            
UIFont *font = //your font;
CGRect rect = [aLongString boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
                                        options:(NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin)
                                     attributes:@{ NSFontAttributeName : font,
                                                   NSForegroundColorAttributeName : [UIColor whiteColor]}
                                        context:nil];

if(rect.size.width > width)
{
    return rect.size.height + font.lineHeight;
}
return rect.size.height;

Aby uzyskać więcej kontekstu; Miałem tekst wieloliniowy i próbowałem znaleźć odpowiednią wysokość, aby go wyświetlić. BoundRectWithSize czasami zwracało szerokość większą niż to, co określiłem, więc kiedy użyłem mojej przeszłej szerokości i obliczonej wysokości do wyświetlenia mojego tekstu, to ucięty. Z testowania, gdy boundingRectWithSize użyło niewłaściwej szerokości, wartość spowodowałaby skrócenie wysokości o 1 wiersz. Sprawdzę więc, czy szerokość jest większa, a jeśli tak, dodajmy linię lineHeight czcionki, aby zapewnić wystarczającą ilość miejsca, aby uniknąć obcięcia.

odyth
źródło
Jak wysokość linii wpływa na szerokość?
Roi Mulia
Wysokość linii @RoiMulia nie wpływa na szerokość. Zaktualizowałem moją odpowiedź, aby podać więcej kontekstu, jak to naprawiło mój błąd.
odyth
0
    NSAttributedString *attributedText =[[[NSAttributedString alloc]
                                          initWithString:joyMeComment.content
                                          attributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:TextFont]}] autorelease];

    CGRect paragraphRect =
    [attributedText boundingRectWithSize:CGSizeMake(kWith, CGFLOAT_MAX)
                                 options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                 context:nil];
    contentSize = paragraphRect.size;

    contentSize.size.height+=10;
    label.frame=contentSize;

jeśli ramka etykiety nie dodaje 10, ta metoda nigdy nie zadziała! mam nadzieję, że to może ci pomóc! powodzenia.

MICHEAL LV
źródło
0
Add Following methods in ur code for getting correct size of attribute string 
1.
    - (CGFloat)findHeightForText:(NSAttributedString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font
 {
    UITextView *textView = [[UITextView alloc] init];
    [textView setAttributedText:text];
    [textView setFont:font];
    CGSize size = [textView sizeThatFits:CGSizeMake(widthValue, FLT_MAX)];
    return size.height;

}

2. Call on heightForRowAtIndexPath method
     int h = [self findHeightForText:attrString havingWidth:yourScreenWidth andFont:urFont];
Anny
źródło
W moim przypadku powinienem wykonać obliczenia w wątku w tle, a użycie elementów UI jest niedozwolone
Lubbo
0

Chciałbym dodać swoje przemyślenia, ponieważ miałem dokładnie ten sam problem.

Używałem, UITextViewponieważ miał ładniejsze wyrównanie tekstu (justowanie, które w tamtym czasie nie było dostępne UILabel), ale w celu „symulacji” nieinteraktywnego, nieprzewijalnegoUILabel , całkowicie wyłączyłem przewijanie, odbijanie i interakcję z użytkownikiem .

Oczywiście problem polegał na tym, że tekst był dynamiczny i chociaż szerokość byłaby stała, wysokość powinna być przeliczana za każdym razem, gdy ustawiam nową wartość tekstu.

boundingRectWithSizew ogóle nie działało dobrze, z tego, co widziałem, UITextViewdodawało trochę marginesu na górze, który boundingRectWithSizenie mógł się liczyć, stąd pobrana wysokość boundingRectWithSizebyła mniejsza niż powinna.

Ponieważ tekst nie miał być aktualizowany szybko, jest używany tylko do niektórych informacji, które mogą być aktualizowane najczęściej co 2-3 sekundy, zdecydowałem się na następujące podejście:

/* This f is nested in a custom UIView-inherited class that is built using xib file */
-(void) setTextAndAutoSize:(NSString*)text inTextView:(UITextView*)tv
{
    CGFloat msgWidth = tv.frame.size.width; // get target's width

    // Make "test" UITextView to calculate correct size
    UITextView *temp = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, msgWidth, 300)]; // we set some height, really doesn't matter, just put some value like this one.
    // Set all font and text related parameters to be exact as the ones in targeted text view
    [temp setFont:tv.font];
    [temp setTextAlignment:tv.textAlignment];
    [temp setTextColor:tv.textColor];
    [temp setText:text];

    // Ask for size that fits :P
    CGSize tv_size = [temp sizeThatFits:CGSizeMake(msgWidth, 300)];

    // kill this "test" UITextView, it's purpose is over
    [temp release];
    temp = nil;

    // apply calculated size. if calcualted width differs, I choose to ignore it anyway and use only height because I want to have width absolutely fixed to designed value
    tv.frame = CGRectMake(tv.frame.origin.x, tv.frame.origin.y, msgWidth, tv_size.height );
}

* Powyższy kod nie jest bezpośrednio kopiowany z mojego źródła, musiałem go dostosować / wyczyścić z wielu innych rzeczy, które nie są potrzebne w tym artykule. Nie bierz tego za kod kopiuj-wklej i zadziała.

Oczywistą wadą jest to, że ma on przydzielanie i zwalnianie dla każdego połączenia.

Ale zaletą jest to, że unikasz zależności od zgodności między sposobem, w jaki boundingRectWithSize rysuje tekst i oblicza jego rozmiar, a implementacją rysowania tekstu w UITextView (lub UILabelktóre również można użyć po prostu zastąpić UITextViewz UILabel). W ten sposób można uniknąć wszelkich „błędów”, które może mieć Apple.

PS Wydawałoby się, że nie powinieneś potrzebować tego "temp" UITextViewi możesz po prostu zapytać sizeThatFitsbezpośrednio od celu, jednak to nie zadziałało dla mnie. Chociaż logika powiedziałaby, że powinno działać, a przydzielanie / zwalnianie tymczasowych UITextViewnie jest potrzebne, tak się nie stało. Ale to rozwiązanie działało bezbłędnie dla każdego tekstu, który bym wstawił.

Sinisa
źródło
W moim przypadku powinienem wykonać obliczenia w wątku w tle, a użycie elementów UI jest niedozwolone
Lubbo
0

Ok, więc spędziłem dużo czasu na debugowaniu tego. Dowiedziałem się, że maksymalna wysokość tekstu zdefiniowana przezboundingRectWithSize pozwala na wyświetlanie tekstu przez moje UITextViewjest mniejsza niż rozmiar ramki.

W moim przypadku ramka ma maksymalnie 140 punktów, ale UITextView toleruje teksty maksymalnie 131 punktów.

Musiałem to rozgryźć ręcznie i zakodować „prawdziwą” maksymalną wysokość.

Oto moje rozwiązanie:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    NSString *proposedText = [textView.text stringByReplacingCharactersInRange:range withString:text];
    NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:proposedText];
    CGRect boundingRect;
    CGFloat maxFontSize = 100;
    CGFloat minFontSize = 30;
    CGFloat fontSize = maxFontSize + 1;
    BOOL fit;
    NSLog(@"Trying text: \"%@\"", proposedText);
    do {
        fontSize -= 1;
        //XXX Seems like trailing whitespaces count for 0. find a workaround
        [attributedText addAttribute:NSFontAttributeName value:[textView.font fontWithSize:fontSize] range:NSMakeRange(0, attributedText.length)];
        CGFloat padding = textView.textContainer.lineFragmentPadding;
        CGSize boundingSize = CGSizeMake(textView.frame.size.width - padding * 2, CGFLOAT_MAX);
        boundingRect = [attributedText boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil];
        NSLog(@"bounding rect for font %f is %@; (max is %f %f). Padding: %f", fontSize, NSStringFromCGRect(boundingRect), textView.frame.size.width, 148.0, padding);
        fit =  boundingRect.size.height <= 131;
    } while (!fit && fontSize > minFontSize);
    if (fit) {
        self.textView.font = [self.textView.font fontWithSize:fontSize];
        NSLog(@"Fit!");
    } else {
        NSLog(@"No fit");
    }
    return fit;
}
Antzi
źródło