2012-11-29 98 views
132

我想獲取屬性字符串的矩形,但boundingRectWithSize調用不尊重我傳入的大小,並且正在返回與單個線高度相對較大高度的矩形(這是一個很長的字符串)。我已經通過傳遞一個非常大的值作爲高度值,並且在下面的代碼中也是0,但是返回的矩形總是相同的。boundingRectWithSize爲NSAttributedString返回錯誤的大小

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

這是打破了,還是我需要做別的事情,讓它返回包裝文本的矩形?

+3

你手頭正好有一截斷段落樣式/裁剪'lineBreakMode'? – danyowdee

回答

290

看起來你沒有提供正確的選項。用於包裝的標籤,至少提供:

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

注:如果原始文本寬度下300.f不會有換行,所以一定要確保綁定大小是正確的,否則你仍然會得到錯誤結果。

+23

每個人都需要看這個答案,因爲它的工作原理。也許這種方法需要更清晰的文檔,並且一般歸因於字符串;這並不明顯,你需要看看。 – macserv

+0

看到我的評論下面的案件似乎並不奏效。 –

+28

注意!它並不總是工作!有時返回的寬度大於size參數的寬度。即使是Apples方法文檔也指出:「您在size參數中指定的約束是渲染器如何調整字符串大小的指導,但是,如果需要額外的空間,此方法返回的實際邊界矩形可能會大於約束渲染整個字符串「。 – Klaas

113

該方法似乎有許多方面的錯誤。就像你注意到的那樣,它不考慮寬度限制。另一方面,我看到它崩潰,因爲它似乎假設所有屬性都是NSObject類型(例如,它試圖將_isDefaultFace傳遞給CTFontRef)。當提供字符串繪圖上下文時,它也會崩潰,因爲它試圖在後臺添加一個無值屬性給可變屬性字符串。

我鼓勵你完全避免這種方法。如果您可以處理爲需要繪製的每個字符串創建framesetter的開銷,則可以直接使用Core Text來估計字符串大小。它也沒有嚴格遵守寬度約束,但根據我的經驗,它似乎在幾個像素內。

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString); 
CGSize targetSize = CGSizeMake(320, CGFLOAT_MAX); 
CGSize fitSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [attrString length]), NULL, targetSize, NULL); 
CFRelease(framesetter); 
+7

即使我不使用Autolayout(如果您在桌面視圖中使用它,它也是一個完整的災難),它仍然會返回不正確的大小。我結束了不斷使用一個屬性文本參數重新創建UILabel,調用'sizeToFit',實際上返回正確的結果,但只有當屬性文本第一次被設置。 – Eugene

+0

它真的在工作......我驚呆了...... – mariusLAN

+0

+1!整天掙扎着。完美工作, – EsbenB

29

埃德麥克馬納斯肯定提供了一個關鍵,讓這個工作。我發現不工作

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]; 

矩形不會有正確的高度的情況下。請注意,anotherString(它被附加到字符串)被初始化而沒有屬性字典。這是anotherString的一個合法初始值設定項,但boundingRectWithSize:在這種情況下沒有提供準確的大小。

+27

謝謝!原來NSAttributedString的每一部分都必須有一個至少包含NSFontAttributeName和NSForegroundColorAttributeName集的字典集,如果你希望boundingRectWithSize實際工作的話!我沒有看到任何地方記錄。 –

+3

請注意,爲了使boundingRectWithSize正常工作,它也顯示組合在一起的不同NSAttributedStrings必須全部使用SAME字典(並因此使用相同的字體)! –

+1

這個解決方案與我爲我的UILabel類別使用「縮小到適合」類似。讓我開始工作的關鍵是創建具有FLT_MAX高度的邊界矩形。沒有這一點,我似乎只得到一個矩形只有一行。全方位的 – dre1138

3

我有同樣的問題沒有得到一個準確的大小使用這些技術,我改變了我的方法,使其工作。

我有一個很長的歸因字符串,我一直試圖適應一個滾動視圖,以便它正確顯示而不被截斷。我爲確保文本可靠工作所做的工作並未將高度設置爲約束條件,而是讓內部大小接管。現在文本正確顯示而不被截斷,我不必計算高度。

我想,如果我確實需要可靠地獲得高度,我會創建一個隱藏的視圖,這些約束和獲得框架的高度,一旦約束被應用。

+2

你可以添加一個代碼示例來演示這個嗎?謝謝。 –

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; 

如果標籤的框架未添加10此方法將無法使用!希望這可以幫到你!祝你好運。

1

我有同樣的問題,但我認識到高度限制已被正確設置。所以我做了以下幾點:

-(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; 
} 
0

我想補充我的想法,因爲我有完全相同的問題。

我用UITextView因爲它有更好的文本對齊方式(證明,這在當時是不可用UILabel),但爲了「模擬」非交互式非滾動UILabel,我會完全關掉滾動,彈跳和用戶交互。

當然,問題在於文本是動態的,雖然寬度是固定的,但每次設置新文本值時都應重新計算高度。

boundingRectWithSize並沒有爲我工作得很好,在所有的,從我能看到,UITextView被添加在上面的一些保證金其中boundingRectWithSize不會進入計數,因此,從boundingRectWithSize檢索到的高度爲小於它應該是。

由於文本沒有被迅速更新,它只是用於可能更新每隔2-3秒之最,我已經決定下列方法的一些信息:

/* 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); 
} 

*上面的代碼是不能直接從我的源複製,我不得不調整它/清除它從本文不需要的一堆其他東西。不要把它作爲複製粘貼它將工作的代碼。

明顯的缺點是它有alloc和release,每次調用。

但是,好處就是你避免因兼容性之間boundingRectWithSize如何繪製文本,並計算它的大小和UITextView實現文本繪製的(或UILabel也可以使用只是UILabel替換UITextView)。蘋果可能有的任何「錯誤」都可以避免。

P.S.看起來你不應該需要這個「temp」UITextView,並且可以直接從目標請求sizeThatFits,但是這對我沒有任何作用。雖然邏輯會說它應該工作,暫時UITextView不需要分配/釋放,但它沒有。但是這個解決方案對我設置的任何文本都是完美無缺的。

+0

在我的情況下,我應該做後臺線程的計算,並且不允許使用UI元素 – Lubbo

5

@warrenm對不起,說framesetter方法不適合我。

我this.This功能可以幫助我們確定需要的NSAttributedString的iphone字符串範圍框的大小/ iPad的SDK對於給定的寬度:

它可用於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)); 
} 

由於HADDAD ISSA >>>http://haddadissa.blogspot.in/2010/09/compute-needed-heigh-for-fixed-width-of.html

+0

我可以知道downvote的原因嗎? –

+2

它適合我。謝謝:) –

+1

很好,謝謝。 – user346443

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)); 

工程上完美的所有字體!

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); 

我試了一下在這個頁面上,仍然有一個UILabel格式不正確的情況。實際上,在標籤上設置歸檔文本最終解決了問題。

+0

它也解決了我的問題 –

8

如果您想通過截斷尾部來獲得包圍盒,this question可以幫助您。

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]; 
20

我的最終決定後長時間的調查:
- boundingRectWithSize函數返回正確的尺寸僅爲字符的順序不間斷! 如果字符串中包含空格或其他內容(由Apple調用「某些字形」) - 不可能獲得顯示文本所需的實際大小的矩形!
我用字母替換了字符串中的空格,並立即得到正確的結果。

蘋果說,在這裏: https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrectwithsize

「這個方法返回字形的實際邊界字符串中的一些字形(空格,例如)被允許與通過把尺寸指定的佈局約束。傳入,因此在某些情況下,返回的CGRect的尺寸組件的寬度值可能會超出尺寸參數的寬度值。「

所以需要尋找一些其他的方法來計算實際的矩形...


長時間的調查過程解決方案後,終於找到了! 我不確定它適用於所有與UITextView有關的情況,但主要和重要的事情被發現!

boundingRectWithSize函數以及CTFramesetterSuggestFrameSizeWithConstraints(和許多其他方法)將使用正確的矩形時計算大小和文本部分的正確性。 例如 - UITextViewtextView.bounds.size.width - 當在UITextView上繪製文本時,此值不是系統使用的實際矩形。

我發現很有意思的參數和代碼進行簡單的計算:

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

與魔法的作品 - 現在正確計算出我所有的文字! 享受!

+1

yeap,我注意到它不會保留寬度的限制,它總是會變得更大,這真的不是很酷,他們不贊成使用工作方法,現在我們必須處理這個廢話。 –

+0

你先生,我的新英雄!我設置textContainers插圖,所以不得不使用textView.textContainer.size.width而不是textView.bounds.size.witdh。 – GCBenson

+0

這節省了我的一天 –

37

由於某些原因,boundingRectWithSize總是返回錯誤的大小。 我想出了一個解決方案。 有一個UItextView -sizeThatFits方法,它返回文本集的正確大小。 因此,不是使用boundingRectWithSize,而是使用隨機幀創建UITextView,並使用相應的寬度和CGFLOAT_MAX高度調用它的sizeThatFits。 它返回將具有適當高度的大小。

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

如果要計算一個while循環的大小,也沒有忘了補充一點,在一個自動釋放池,一樣會有的UITextView中生成的N個,應用程序的運行時內存會增加,如果我們不要使用autoreleasepool。

+1

這是有效的,我嘗試過的其他方法我永遠無法工作,我認爲我的問題源於屬性的複雜性d字符串我正在分配。它來自RTF文件,並使用各種字體,行間距,甚至陰影,儘管使用UsesLineFragmentOrigin和UsesFontLeading,但我永遠無法得到足夠高的結果,並且問題最嚴重,歸因字符串得到的時間越長。我的猜測是,對於相對簡單的字符串,boundingRectWithSize方法*可能工作。他們真的需要一個更好的方法來處理這個問題,imo。 –

+0

你不覺得用'heightForRowAtIndexPath'方法調用多少時間創建一個'UITextView'是多餘的嗎?它需要時間 –

+0

我同意你@雅諾斯,但這是唯一的解決方案,爲我工作。 – shoan

1

我注意到的一件事是,從(CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context回來的矩形會比我傳入的矩形寬。當發生這種情況時,我的字符串會被截斷。我是這樣解決的:

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; 
6

我沒有運氣與這些建議中的任何一個。我的字符串包含unicode項目符號,我懷疑他們正在計算中引起悲傷。我注意到UITextView正在處理圖紙,所以我希望能夠利用它的計算。我做了以下,這可能不像NSString繪圖方法那麼理想,但至少它是準確的。它也比初始化UITextView稍微更優化,只需撥打-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); 
2

我發現首選解決方案不處理換行符。

我發現這種方法在所有情況下工作:

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; 
+0

在我的情況下,我應該對後臺線程進行計算,並且不允許使用UI元素 – Lubbo

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]; 
+0

在我的情況下,我應該執行後臺線程計算和UI元素的使用是不允許的 – Lubbo

2

林有點太遲了 - 但我一直在試圖找出一個這種方式可以找到適合圍繞屬性字符串的邊界框,以便像Finder中編輯文件那樣創建焦點環。當字符串末尾有空格或字符串內有多個空格時,我所嘗試過的所有內容都會失敗。 boundingRectWithSize對此以及CTFramesetterCreateWithAttributedString失敗。

使用NSLayoutManager下面的代碼似乎在我迄今爲止發現的所有情況下都能做到這一點,並返回一個完全限制字符串的矩形。獎勵:如果您選擇文本,則選擇的邊沿直到返回的矩形的邊界。下面的代碼使用NSTextView的layoutManager。

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

CGRect focusRingFrame = [layout boundingRectForGlyphRange:NSMakeRange(0, [[self textStorage] length]) inTextContainer:container]; 
4

事實證明,一個NSAttributedString的每一個部分都必須有一本字典集至少NSFontAttributeName和NSForegroundColorAttributeName集,如果你想boundingRectWithSize實際工作!

我沒有看到任何地方記錄。

2

斯威夫特版本三

let string = "A great test string." 
let font = UIFont.systemFont(ofSize: 14) 
let attributes: [String: Any] = [NSFontAttributeName: 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 = (alert.alertDescription 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 

測量與CTFramesetter文本效果最好,因爲它提供整數大小和處理的表情符號的等Unicode字符很好。

0

好的,我花了很多時間調試這個。我發現boundingRectWithSize允許我的UITextView顯示的文本的最大文本高度低於幀大小。

在我的情況下,框架是至多140pt,但UITextView最多可容忍文本131pt。

我不得不手動弄清楚「真實」的最大高度。

這裏是我的解決方案:

- (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; 
} 
相關問題