Zamiennik przestarzałego rozmiaruWithFont: w iOS 7?

320

W iOS 7 sizeWithFont:jest teraz przestarzały. Jak teraz przekazać obiekt UIFont do metody zamiany sizeWithAttributes:?

James Kuang
źródło

Odpowiedzi:

521

Użyj sizeWithAttributes:zamiast tego, co teraz zajmuje NSDictionary. Przekaż parę z kluczem UITextAttributeFonti obiektem czcionki w następujący sposób:

CGSize size = [string sizeWithAttributes:
    @{NSFontAttributeName: [UIFont systemFontOfSize:17.0f]}];

// Values are fractional -- you should take the ceilf to get equivalent values
CGSize adjustedSize = CGSizeMake(ceilf(size.width), ceilf(size.height));
James Kuang
źródło
60
podczas pracy z NSStringi UILabel(nie zawsze tak jest, ale często tak), aby zapobiec powielony kod / etc, można również wymienić [UIFont systemFontOfSize:17.0f]z label.font- ułatwia utrzymanie kodu poprzez odniesienie istniejących danych w porównaniu z wami wpisanie go wielokrotnie lub odwołanie stałych całego miejsce itp.
toblerpwn 30.10.13
8
@toblerpwn możliwe, że etykieta nie istnieje i próbujesz obliczyć etykietę teoretyczną.
botbot,
5
Jak mam użyć tego przykładu, aby uzyskać rozmiar. Wysokość z etykietą, która ma stałą szerokość, na przykład 250? LUB jeśli jego etykieta z autolatyout witch zajmuje procent szerokości i przechodzę do trybu krajobrazu.
Pedroinpeace,
12
@ Pedroinpeace Zamiast tego użyłbyś boundingRectWithSize:options:attributes:context:, przekazując CGSizeMake(250.0f, CGFLOAT_MAX)w większości przypadków.
James Kuang,
9
Coś jeszcze do odnotowania sizeWithAttributes: nie jest w 100% równoważny. sizeWithFont służy do zaokrąglania wielkości do wartości całkowitych (pikseli). Sugeruj użycie ceilf na wynikowej wysokości / szerokości lub możesz uzyskać rozmyte artefakty (szczególnie non retina HW), jeśli użyjesz tego do obliczeń pozycyjnych.
Nick H247,
172

Myślę, że ta funkcja była przestarzała, ponieważ ta seria NSString+UIKitfunkcji ( sizewithFont:...itp.) UIStringDrawingByła oparta na bibliotece, co nie było bezpieczne dla wątków. Jeśli spróbujesz uruchomić je nie w głównym wątku (jak każda inna UIKitfunkcja), otrzymasz nieprzewidziane zachowania. W szczególności, jeśli uruchomiłeś funkcję na wielu wątkach jednocześnie, prawdopodobnie spowoduje to awarię aplikacji. Właśnie dlatego w iOS 6 wprowadzili boundingRectWithSize:...metodę NSAttributedString. Został on zbudowany na NSStringDrawingbibliotekach i jest bezpieczny dla wątków.

Jeśli spojrzysz na nową NSString boundingRectWithSize:...funkcję, poprosi ona o tablicę atrybutów w taki sam sposób jak NSAttributeString. Gdybym musiał zgadywać, ta nowa NSStringfunkcja w iOS 7 jest jedynie opakowaniem NSAttributeStringfunkcji z iOS 6.

W tej notatce, jeśli wspierasz tylko iOS 6 i iOS 7, zdecydowanie zmieniłbym wszystkie twoje NSString sizeWithFont:...na NSAttributeString boundingRectWithSize. Zaoszczędzi ci dużo bólu głowy, jeśli zdarzy ci się mieć dziwną, wielowątkową obudowę narożną! Oto jak przekonwertowałem NSString sizeWithFont:constrainedToSize::

Co kiedyś:

NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
CGSize size = [text sizeWithFont:font 
               constrainedToSize:(CGSize){width, CGFLOAT_MAX}];

Można zastąpić:

NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
NSAttributedString *attributedText =
    [[NSAttributedString alloc] initWithString:text 
                                    attributes:@{NSFontAttributeName: font}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                           context:nil];
CGSize size = rect.size;

Uwaga: w dokumentacji wspomniano:

W iOS 7 i nowszych ta metoda zwraca rozmiary ułamkowe (w składniku rozmiaru zwracanego CGRect); aby użyć zwróconego widoku rozmiar do rozmiaru, należy użyć podniesienia jego wartości do najbliższej wyższej liczby całkowitej za pomocą funkcji ceil.

Aby wyciągnąć obliczoną wysokość lub szerokość, która ma zostać użyta do zmiany rozmiaru widoków, użyłbym:

CGFloat height = ceilf(size.height);
CGFloat width  = ceilf(size.width);
Pan T.
źródło
boundingRectWithSize jest nieaktualny w iOS 6.0.
Nirav
2
@Nirav Nie widzę żadnej wzmianki o deprecjacji. Czy możesz wskazać mi, gdzie jest przestarzałe? Dzięki! ( developer.apple.com/library/ios/documentation/uikit/reference/… )
Pan T
2
Może @Nirav oznaczało, że nie było dostępne dla NSString w iOS 6 (o czym pośrednio wspomniano w odpowiedzi)?
newenglander
1
@ VagueExplanation Właśnie próbowałem, a boundingRectForSize nadal nie jest przestarzałe dla NSAttributString. To również nie mówi, że jest przestarzałe w dokumentacji.
Pan T
5
Fantastyczny do wspominania o użyciu ceilf (). Nigdzie indziej o tym nie wspominałem, a mój rozmiar był zawsze nieco za mały. Dzięki!
Jonathan Brown
29

Jak widać sizeWithFontna stronie Apple Developer, jest on przestarzały, więc musimy go użyć sizeWithAttributes.

#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)

NSString *text = @"Hello iOS 7.0";
if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
    // code here for iOS 5.0,6.0 and so on
    CGSize fontSize = [text sizeWithFont:[UIFont fontWithName:@"Helvetica" 
                                                         size:12]];
} else {
    // code here for iOS 7.0
   CGSize fontSize = [text sizeWithAttributes: 
                            @{NSFontAttributeName: 
                              [UIFont fontWithName:@"Helvetica" size:12]}];
}
Ayush
źródło
17
W takim przypadku lepiej użyć [NSObject respondsToSelector:]metody takiej jak tutaj: stackoverflow.com/a/3863039/1226304
derpoliuk
16

Utworzyłem kategorię, aby poradzić sobie z tym problemem, oto ona:

#import "NSString+StringSizeWithFont.h"

@implementation NSString (StringSizeWithFont)

- (CGSize) sizeWithMyFont:(UIFont *)fontToUse
{
    if ([self respondsToSelector:@selector(sizeWithAttributes:)])
    {
        NSDictionary* attribs = @{NSFontAttributeName:fontToUse};
        return ([self sizeWithAttributes:attribs]);
    }
    return ([self sizeWithFont:fontToUse]);
}

W ten sposób można tylko trzeba znaleźć / zastąpić sizeWithFont:z sizeWithMyFont:i jesteś dobry, aby przejść.

Rom.
źródło
1
spowoduje to wygenerowanie ostrzeżenia kompilatora na ios7 + w przypadku odniesienia do sizeWithFont lub ostrzeżenia / błędu kompilacji na <ios7 w przypadku odwołania sizeWithAttributes! Prawdopodobnie najlepiej jest użyć makra zamiast sprawdzania respondsToSelector - w razie potrzeby. Ale ponieważ nie możesz już dostarczać do Apple z pakietem ioS6 SDK ... prawdopodobnie tak nie jest!
Nick H247,
Dodaj to, aby ukryć ostrzeżenie #pragma Diagnostyka GCC zignorowała „-Wdeprecated-deklarations”
Ryan Heitner
10

W iOS7 potrzebowałem logiki, aby zwrócić poprawną wysokość dla widoku tabeli: heightForRowAtIndexPath, ale sizeWithAttributes zawsze zwraca tę samą wysokość niezależnie od długości łańcucha, ponieważ nie wie, że zostanie umieszczony w komórce tabeli o stałej szerokości . Uważam, że działa to dla mnie świetnie i oblicza prawidłową wysokość, biorąc pod uwagę szerokość komórki tabeli! Jest to oparte na powyższej odpowiedzi pana T.

NSString *text = @"The text that I want to wrap in a table cell."

CGFloat width = tableView.frame.size.width - 15 - 30 - 15;  //tableView width - left border width - accessory indicator - right border width
UIFont *font = [UIFont systemFontOfSize:17];
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: font}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                           context:nil];
CGSize size = rect.size;
size.height = ceilf(size.height);
size.width  = ceilf(size.width);
return size.height + 15;  //Add a little more padding for big thumbs and the detailText label
użytkownik3055587
źródło
7

Etykiety wieloliniowe wykorzystujące dynamiczną wysokość mogą wymagać dodatkowych informacji, aby prawidłowo ustawić rozmiar. Możesz użyć sizeWithAttributes z UIFont i NSParagraphStyle, aby określić zarówno czcionkę, jak i tryb podziału linii.

Zdefiniowałbyś styl akapitowy i używałeś NSDictionary w następujący sposób:

// set paragraph style
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:NSLineBreakByWordWrapping];
// make dictionary of attributes with paragraph style
NSDictionary *sizeAttributes        = @{NSFontAttributeName:myLabel.font, NSParagraphStyleAttributeName: style};
// get the CGSize
CGSize adjustedSize = CGSizeMake(label.frame.size.width, CGFLOAT_MAX);

// alternatively you can also get a CGRect to determine height
CGRect rect = [myLabel.text boundingRectWithSize:adjustedSize
                                                         options:NSStringDrawingUsesLineFragmentOrigin
                                                      attributes:sizeAttributes
                                                         context:nil];

Jeśli szukasz wysokości, możesz użyć CGSize 'AdjustSize' lub CGRect jako właściwości rect.size.height.

Więcej informacji na temat NSParagraphStyle tutaj: https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSParagraphStyle_Class/Reference/Reference.html

bitsand
źródło
1
Wygląda na to, że zaraz po dostosowaniu rozmiaru wystąpił problem ze składnią.
Chris Prince,
Dzięki za złapanie tego problemu ze składnią, Chris
bits i
6
// max size constraint
CGSize maximumLabelSize = CGSizeMake(184, FLT_MAX)

// font
UIFont *font = [UIFont fontWithName:TRADE_GOTHIC_REGULAR size:20.0f];

// set paragraph style
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;

// dictionary of attributes
NSDictionary *attributes = @{NSFontAttributeName:font,
                             NSParagraphStyleAttributeName: paragraphStyle.copy};

CGRect textRect = [string boundingRectWithSize: maximumLabelSize
                                     options:NSStringDrawingUsesLineFragmentOrigin
                                  attributes:attributes
                                     context:nil];

CGSize expectedLabelSize = CGSizeMake(ceil(textRect.size.width), ceil(textRect.size.height));
Kirit Vaghela
źródło
1
Dzięki temu zaoszczędziłem godziny bólu głowy, dzięki Kirit, zdefiniowanie atrybutów i słowników przed blokiem CGRect było dla mnie tym ... znacznie łatwiejsze do odczytania.
Azin Mehrnoosh
3

Utwórz funkcję, która pobiera instancję UILabel. i zwraca CGSize

CGSize constraint = CGSizeMake(label.frame.size.width , 2000.0);
// Adjust according to requirement

CGSize size;
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0){

    NSRange range = NSMakeRange(0, [label.attributedText length]);

    NSDictionary *attributes = [label.attributedText attributesAtIndex:0 effectiveRange:&range];
    CGSize boundingBox = [label.text boundingRectWithSize:constraint options: NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;

    size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
}
else{
    size = [label.text sizeWithFont:label.font constrainedToSize:constraint lineBreakMode:label.lineBreakMode];
}

return size;
Muhammad Idris
źródło
W moim przypadku dynamicznie wysokość wiersza całkowicie usunąłem metodę HeightForRow i użyłem UITableViewAutomaticDimension. tableView.estimatedRowHeight = 68.0 tableView.rowHeight = UITableViewAutomaticDimension
Eugene Braginets,
3

Alternatywne rozwiązanie

CGSize expectedLabelSize;
if ([subTitle respondsToSelector:@selector(sizeWithAttributes:)])
{
    expectedLabelSize = [subTitle sizeWithAttributes:@{NSFontAttributeName:subTitleLabel.font}];
}else{
    expectedLabelSize = [subTitle sizeWithFont:subTitleLabel.font constrainedToSize:subTitleLabel.frame.size lineBreakMode:NSLineBreakByWordWrapping];
}
Śmiałek
źródło
1
Spowoduje to wygenerowanie ostrzeżenia kompilatora zarówno dla iOS6, jak i 7. I będzie miało zupełnie inne zachowanie dla każdego!
Nick H247,
3

Opierając się na @bitsand, jest to nowa metoda, którą właśnie dodałem do mojej kategorii NSString + Dodatki:

- (CGRect) boundingRectWithFont:(UIFont *) font constrainedToSize:(CGSize) constraintSize lineBreakMode:(NSLineBreakMode) lineBreakMode;
{
    // set paragraph style
    NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    [style setLineBreakMode:lineBreakMode];

    // make dictionary of attributes with paragraph style
    NSDictionary *sizeAttributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName: style};

    CGRect frame = [self boundingRectWithSize:constraintSize options:NSStringDrawingUsesLineFragmentOrigin attributes:sizeAttributes context:nil];

    /*
    // OLD
    CGSize stringSize = [self sizeWithFont:font
                              constrainedToSize:constraintSize
                                  lineBreakMode:lineBreakMode];
    // OLD
    */

    return frame;
}

Po prostu używam rozmiaru wynikowej ramki.

Chris Prince
źródło
2

Nadal możesz używać sizeWithFont. ale w iOS> = 7.0 metoda powoduje zawieszanie się, jeśli ciąg znaków zawiera spacje początkowe i końcowe oraz linie końcowe \n.

Przycinanie tekstu przed użyciem

label.text = [label.text stringByTrimmingCharactersInSet:
             [NSCharacterSet whitespaceAndNewlineCharacterSet]];

Dotyczy to również sizeWithAttributesi [label sizeToFit].

również za każdym razem, gdy masz nsstringdrawingtextstorage message sent to deallocated instanceurządzenie z iOS 7.0, zajmuje się tym.

Hasan
źródło
2

Lepsze wykorzystanie wymiarów automatycznych (Swift):

  tableView.estimatedRowHeight = 68.0
  tableView.rowHeight = UITableViewAutomaticDimension

Uwaga: 1. Prototyp UITableViewCell powinien być odpowiednio zaprojektowany (na przykład nie zapomnij ustawić UILabel.numberOfLines = 0 itd.) 2. Usuń metodę HeightForRowAtIndexPath

wprowadź opis zdjęcia tutaj

WIDEO: https://youtu.be/Sz3XfCsSb6k

Eugene Braginets
źródło
Należy wziąć statyczną wysokość komórki, która jest zdefiniowana w prototypowej komórce scenorysu
Kirit Vaghela
Dodałem te dwie linie i upewniłem się, że mój UILabel jest ustawiony na 0 liczbę linii. Nie działał zgodnie z reklamą. Co jeszcze zrobiłeś, aby działał z tymi dwoma wierszami kodu?
Czy
1
hm, musisz też przypiąć ograniczenia etykiety do górnej i dolnej części komórki
Eugene Braginets,
1
Nie, to powinno wystarczyć. Czy masz ograniczenia autoukładu dla etykiety przypiętej do podglądu?
Eugene Braginets
1
boundingRectWithSize:options:attributes:context:
Holden
źródło
1

Zaakceptowana odpowiedź w Xamarin będzie (użyj sizeWithAttributes i UITextAttributeFont):

        UIStringAttributes attributes = new UIStringAttributes
        { 
            Font = UIFont.SystemFontOfSize(17) 
        }; 
        var size = text.GetSizeUsingAttributes(attributes);
Alex Sorokoletov
źródło
1

Jako odpowiedź @Ayush:

Jak widać sizeWithFontna stronie Apple Developer, jest on przestarzały, więc musimy go użyć sizeWithAttributes.

Załóżmy, że w 2019+ prawdopodobnie używasz Swift i Stringzamiast Objective-c, a NSStringoto poprawny sposób uzyskania rozmiaru Stringz predefiniowaną czcionką:

let stringSize = NSString(string: label.text!).size(withAttributes: [.font : UIFont(name: "OpenSans-Regular", size: 15)!])
Romulo BM
źródło
Jak możesz to wykorzystać do określenia rozmiaru czcionki?
Joseph Astrahan
@JosephAstrahan nie służy to do określania rozmiaru czcionki, lecz do uzyskania rozmiaru (CGSize) ciągu o określonej czcionce. Jeśli chcesz uzyskać rozmiar czcionki etykiety, możesz łatwo użyć label.font
Romulo BM
Korzystając z Twojego pomysłu, stworzyłem sposób, aby uzyskać mniej więcej rozmiar czcionki ( stackoverflow.com/questions/61651614/... ), label.font nie działałoby w moim przypadku z powodu pewnych skomplikowanych problemów z klikaniem etykiet tylko wtedy, gdy znana jest dokładna czcionka, którą możesz przeczytać w moim poście.
Joseph Astrahan
0
- (CGSize) sizeWithMyFont:(UIFont *)fontToUse
{
    if ([self respondsToSelector:@selector(sizeWithAttributes:)])
    {
        NSDictionary* attribs = @{NSFontAttributeName:fontToUse};
        return ([self sizeWithAttributes:attribs]);
    }
    return ([self sizeWithFont:fontToUse]);
}
Prosenjit Goswami
źródło
0

Oto odpowiednik monotouch, jeśli ktoś tego potrzebuje:

/// <summary>
/// Measures the height of the string for the given width.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="width">The width.</param>
/// <param name="padding">The padding.</param>
/// <returns></returns>
public static float MeasureStringHeightForWidth(this string text, UIFont font, float width, float padding = 20)
{
    NSAttributedString attributedString = new NSAttributedString(text, new UIStringAttributes() { Font = font });
    RectangleF rect = attributedString.GetBoundingRect(new SizeF(width, float.MaxValue), NSStringDrawingOptions.UsesLineFragmentOrigin, null);
    return rect.Height + padding;
}

które mogą być użyte w ten sposób:

public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
    //Elements is a string array
    return Elements[indexPath.Row].MeasureStringHeightForWidth(UIFont.SystemFontOfSize(UIFont.LabelFontSize), tableView.Frame.Size.Width - 15 - 30 - 15);
}
Roosevelt
źródło
0
CGSize maximumLabelSize = CGSizeMake(label.frame.size.width, FLT_MAX);
CGSize expectedLabelSize = [label sizeThatFits:maximumLabelSize];
float heightUse = expectedLabelSize.height;
użytkownik1802778
źródło
0

Wypróbuj tę składnię:

NSAttributedString *attributedText =
    [[NSAttributedString alloc] initWithString:text 
                                    attributes:@{NSFontAttributeName: font}];
Muruganandam Sathasivam
źródło
-1

W iOS 7 nic z tego nie działało. Oto, co ostatecznie zrobiłem. Umieszczam to w mojej niestandardowej klasie komórek i wywołuję metodę w metodzie heightForCellAtIndexPath.

Moja komórka wygląda podobnie do komórki opisu podczas przeglądania aplikacji w sklepie z aplikacjami.

Najpierw w serii ujęć ustaw etykietę na „atrybutowany tekst”, ustaw liczbę wierszy na 0 (która automatycznie zmieni rozmiar etykiety (tylko iOS 6+)) i ustaw zawijanie słów.

Następnie po prostu sumuję wszystkie wysokości zawartości komórki w mojej niestandardowej klasie komórek. W moim przypadku mam u góry etykietę, która zawsze mówi „Opis” (_descriptionHeadingLabel), mniejszą etykietę o zmiennej wielkości, która zawiera rzeczywisty opis (_descriptionLabel) ograniczenie od góry komórki do nagłówka (_descriptionHeadingLabelTopConstraint) . Dodałem również 3, aby nieco spuścić trochę na dole (mniej więcej tyle samo jabłek umieszcza w komórce typu napisów).

- (CGFloat)calculateHeight
{
    CGFloat width = _descriptionLabel.frame.size.width;
    NSAttributedString *attributedText = _descriptionLabel.attributedText;
    CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX} options: NSStringDrawingUsesLineFragmentOrigin context:nil];

    return rect.size.height + _descriptionHeadingLabel.frame.size.height + _descriptionHeadingLabelTopConstraint.constant + 3;
}

I w moim widoku tabeli delegat:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
    if (indexPath.row == 0) {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"descriptionCell"];
        DescriptionCell *descriptionCell = (DescriptionCell *)cell;
        NSString *text = [_event objectForKey:@"description"];
        descriptionCell.descriptionLabel.text = text;

        return [descriptionCell calculateHeight];
    }

    return 44.0f;
}

Możesz zmienić instrukcję if, aby była nieco „inteligentniejsza” i faktycznie pobierała identyfikator komórki z jakiegoś źródła danych. W moim przypadku komórki będą zakodowane na stałe, ponieważ będzie ich stała liczba w określonej kolejności.

Kubee
źródło
boundingRectWithSizew ios 9.2 obecne problemy, to wyniki inne niż ios <9.2. Znalazłeś lub znasz inny najlepszy sposób na zrobienie tego.
jose920405