通過boundingRectWithSize:options:attributes:context:計算文本尺寸

之前用Text Kit寫Reader的時候,在分頁時要計算一段文本的尺寸大小,之前使用了NSString類的sizeWithFont:constrainedToSize:lineBreakMode:方法,但是該方法已經被iOS7 Deprecated了,而iOS7新出了一個boudingRectWithSize:options:attributes:context方法來代替:

先來看看iOS7 SDK包中關於boudingRectWithSize:options:attributes:context方法的定義:


  1. // NOTE: All of the following methods will default to drawing on a baseline, limiting drawing to a single line.  
  2. // To correctly draw and size multi-line text, pass NSStringDrawingUsesLineFragmentOrigin in the options parameter.  
  3. @interface NSString (NSExtendedStringDrawing)  
  4. - (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  5. - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  6. @end  

關於該方法,NSAttributedString其實也有一個同名的方法:

  1. @interface NSAttributedString (NSExtendedStringDrawing)  
  2. - (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(6_0);  
  3. - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(6_0);  
  4. @end  
該方法在iOS6就可以使用了。


關於該類,有一篇關於NSAttributedString UIKit Additions Reference翻譯的文章:http://blog.csdn.net/kmyhy/article/details/8895643

裏面就說到了該方法:

boundingRectWithSize:options:context:

 返回文本繪製所佔據的矩形空間。

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context

參數

size

寬高限制,用於計算文本繪製時佔據的矩形塊。

The width and height constraints to apply when computing the string’s bounding rectangle.

options

文本繪製時的附加選項。可能取值請參考“NSStringDrawingOptions”

context

context上下文。包括一些信息,例如如何調整字間距以及縮放。最終,該對象包含的信息將用於文本繪製。該參數可爲 nil 。

返回值

一個矩形,大小等於文本繪製完將佔據的寬和高。

討論

可以使用該方法計算文本繪製所需的空間。size 參數是一個constraint ,用於在繪製文本時作爲參考。但是,如果繪製完整個文本需要更大的空間,則返回的矩形大小可能比 size 更大。一般,繪製時會採用constraint 提供的寬度,但高度則會根據需要而定。

特殊情況

爲了計算文本塊的大小,該方法採用默認基線。

如果 NSStringDrawingUsesLineFragmentOrigin未指定,矩形的高度將被忽略,同時使用單線繪製。(由於一個 bug,在 iOS6 中,寬度會被忽略)

兼容性

  • iOS 6.0 以後支持。

聲明於

NSStringDrawing.


另外,關於參數(NSStringDrawingOptions)options

  1. // NOTE: All of the following methods will default to drawing on a baseline, limiting drawing to a single line.  
  2. // To correctly draw and size multi-line text, pass NSStringDrawingUsesLineFragmentOrigin in the options parameter.  
  3. @interface NSString (NSExtendedStringDrawing)  
  4. - (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  5. - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  6. @end  

  1. typedef NS_ENUM(NSInteger, NSStringDrawingOptions) {  
  2.     NSStringDrawingTruncatesLastVisibleLine = 1 << 5, // Truncates and adds the ellipsis character to the last visible line if the text doesn't fit into the bounds specified. Ignored if NSStringDrawingUsesLineFragmentOrigin is not also set.  
  3.     NSStringDrawingUsesLineFragmentOrigin = 1 << 0, // The specified origin is the line fragment origin, not the base line origin  
  4.     NSStringDrawingUsesFontLeading = 1 << 1, // Uses the font leading for calculating line heights  
  5.     NSStringDrawingUsesDeviceMetrics = 1 << 3, // Uses image glyph bounds instead of typographic bounds  
  6. } NS_ENUM_AVAILABLE_IOS(6_0);  

NSStringDrawingTruncatesLastVisibleLine:

如果文本內容超出指定的矩形限制,文本將被截去並在最後一個字符後加上省略號。如果沒有指定NSStringDrawingUsesLineFragmentOrigin選項,則該選項被忽略。

NSStringDrawingUsesLineFragmentOrigin:

繪製文本時使用 line fragement origin 而不是 baseline origin。

The origin specified when drawing the string is the line fragment origin and not the baseline origin.

NSStringDrawingUsesFontLeading:

計算行高時使用行距。(譯者注:字體大小+行間距=行距)

NSStringDrawingUsesDeviceMetrics:

計算佈局時使用圖元字形(而不是印刷字體)。

Use the image glyph bounds (instead of the typographic bounds) when computing layout.



簡單寫了一個Demo來看看該方法的使用,並比較了一下各個options的不同,首先是代碼:

  1. NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:textView.text];  
  2. textView.attributedText = attrStr;  
  3. NSRange range = NSMakeRange(0, attrStr.length);  
  4. NSDictionary *dic = [attrStr attributesAtIndex:0 effectiveRange:&range];   // 獲取該段attributedString的屬性字典  
  5. // 計算文本的大小  
  6. CGSize textSize = [textView.text boundingRectWithSize:textView.bounds.size // 用於計算文本繪製時佔據的矩形塊  
  7.                                               options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading // 文本繪製時的附加選項  
  8.                                            attributes:dic        // 文字的屬性  
  9.                                               context:nil].size; // context上下文。包括一些信息,例如如何調整字間距以及縮放。該對象包含的信息將用於文本繪製。該參數可爲nil  
  10. NSLog(@"w = %f", textSize.width);  
  11. NSLog(@"h = %f", textSize.height);  
  1. // NOTE: All of the following methods will default to drawing on a baseline, limiting drawing to a single line.  
  2. // To correctly draw and size multi-line text, pass NSStringDrawingUsesLineFragmentOrigin in the options parameter.  
  3. @interface NSString (NSExtendedStringDrawing)  
  4. - (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  5. - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  6. @end  

再看看不同的options下控制檯的輸出結果:

  1. NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading  
  2. 2013-09-02 21:04:47.470 BoudingRect_i7_Demo[3532:a0b] w = 322.171875  
  3. 2013-09-02 21:04:47.471 BoudingRect_i7_Demo[3532:a0b] h = 138.000015  
  4.   
  5. NSStringDrawingUsesLineFragmentOrigin // The specified origin is the line fragment origin, not the base line origin  
  6. 2013-09-02 17:35:40.547 BoudingRect_i7_Demo[1871:a0b] w = 318.398438  
  7. 2013-09-02 17:35:40.549 BoudingRect_i7_Demo[1871:a0b] h = 69.000000  
  8.   
  9. NSStringDrawingTruncatesLastVisibleLine // Truncates and adds the ellipsis character to the last visible line if the text doesn't fit into the bounds specified. Ignored if NSStringDrawingUsesLineFragmentOrigin is not also set.  
  10. 2013-09-02 17:37:38.398 BoudingRect_i7_Demo[1902:a0b] w = 1523.408203  
  11. 2013-09-02 17:37:38.400 BoudingRect_i7_Demo[1902:a0b] h = 13.800000  
  12.   
  13. NSStringDrawingUsesFontLeading // Uses the font leading for calculating line heights  
  14. 2013-09-02 17:40:45.903 BoudingRect_i7_Demo[1932:a0b] w = 1523.408203  
  15. 2013-09-02 17:40:45.905 BoudingRect_i7_Demo[1932:a0b] h = 13.800000  
  16.   
  17. NSStringDrawingUsesDeviceMetrics // Uses image glyph bounds instead of typographic bounds  
  18. 2013-09-02 17:42:03.283 BoudingRect_i7_Demo[1956:a0b] w = 1523.408203  
  19. 2013-09-02 17:42:03.284 BoudingRect_i7_Demo[1956:a0b] h = 13.800000  

其中如果options參數爲NSStringDrawingUsesLineFragmentOrigin,那麼整個文本將以每行組成的矩形爲單位計算整個文本的尺寸。(在這裏有點奇怪,因爲字體高度大概是13.8,textView中大概有10行文字,此時用該選項計算出來的只有5行,即高度爲69,而同時使用NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin卻可以得出文字剛好有10行,即高度爲138,這裏要等iOS7官方的文檔出來再看看選項的說明,因爲畢竟以上文檔是iOS6的東西)

如果爲NSStringDrawingTruncatesLastVisibleLine或者NSStringDrawingUsesDeviceMetric,那麼計算文本尺寸時將以每個字或字形爲單位來計算。

如果爲NSStringDrawingUsesFontLeading則以字體間的行距(leading,行距:從一行文字的底部到另一行文字底部的間距。)來計算。

各個參數是可以組合使用的,如NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine。


根據該方法我調整了一下Reader的分頁方法:(主要是將被iOS7 Deprecated的sizeWithFont:constrainedToSize:lineBreakMode:方法改成了boudingRectWithSize:options:attributes:context:方法來計算文本尺寸)

  1. /* 判斷是否需要分頁和進行分頁動作 */  
  2. -(BOOL)paging  
  3. {  
  4.     /* 獲取Settings中設定好的字體(主要是獲取字體大小) */  
  5.     static const CGFloat textScaleFactor = 1.; // 設置文字比例  
  6.     NSString *textStyle = [curPageView.textView tkd_textStyle]; // 設置文字樣式  
  7.     preferredFont_ = [UIFont tkd_preferredFontWithTextStyle:textStyle scale:textScaleFactor]; //設置prferredFont(包括樣式和大小)  
  8.     NSLog(@"paging: %@", preferredFont_.fontDescriptor.fontAttributes); // 在控制檯中輸出字體的屬性字典  
  9.       
  10.       
  11.     /* 設定每頁的頁面尺寸 */  
  12.     NSUInteger height = (int)self.view.bounds.size.height - 40.0; // 頁面的高度  
  13.       
  14.       
  15.     /* 獲取文本的總尺寸 */  
  16.     NSDictionary *dic = preferredFont_.fontDescriptor.fontAttributes;  
  17.     CGSize totalTextSize = [bookItem.content.string boundingRectWithSize:curPageView.textView.bounds.size  
  18.                                                                  options:NSStringDrawingUsesLineFragmentOrigin  
  19.                                                               attributes:dic  
  20.                                                                  context:nil].size;  
  21.     NSLog(@"w = %f", totalTextSize.width);  
  22.     NSLog(@"h = %f", totalTextSize.height);  
  23.       
  24.       
  25.     /* 開始分頁 */  
  26.     if (totalTextSize.height < height) {  
  27.         /* 如果一頁就能顯示完,直接顯示所有文本 */  
  28.         totalPages_   = 1;             // 設定總頁數爲1  
  29.         charsPerPage_ = [bookItem.content length]; // 設定每頁的字符數  
  30.         textLength_   = [bookItem.content length]; // 設定文本總長度  
  31.         return NO;                     // 不用分頁  
  32.     }  
  33.     else {  
  34.         /* 計算理想狀態下的頁面數量和每頁所顯示的字符數量,用來作爲參考值用 */  
  35.         textLength_                       = [bookItem.content length];                   // 文本的總長度  
  36.         NSUInteger referTotalPages        = (int)totalTextSize.height / (int)height + 1; // 理想狀態下的總頁數  
  37.         NSUInteger referCharactersPerPage = textLength_ / referTotalPages;               // 理想狀態下每頁的字符數  
  38.         // 輸出理想狀態下的參數信息  
  39.         NSLog(@"textLength             = %d", textLength_);  
  40.         NSLog(@"referTotalPages        = %d", referTotalPages);  
  41.         NSLog(@"referCharactersPerPage = %d", referCharactersPerPage);  
  42.           
  43.           
  44.         /* 根據referCharactersPerPage和text view的高度開始動態調整每頁的字符數 */  
  45.         // 如果referCharactersPerPage過大,則直接調整至下限值,減少調整的時間  
  46.         if (referCharactersPerPage > 1000) {  
  47.             referCharactersPerPage = 1000;  
  48.         }  
  49.           
  50.         // 獲取理想狀態下的每頁文本的範圍和pageText及其尺寸  
  51.         NSRange range       = NSMakeRange(referCharactersPerPage, referCharactersPerPage); // 一般第一頁字符數較少,所以取第二頁的文本範圍作爲調整的參考標準  
  52.         NSString *pageText  = [bookItem.content.string substringWithRange:range]; // 獲取該範圍內的文本  
  53.         NSLog(@"%@", pageText);  
  54.           
  55.           
  56.         NSRange ptrange = NSMakeRange(0, pageText.length);  
  57.         NSDictionary *ptdic = [[bookItem.content attributedSubstringFromRange:ptrange] attributesAtIndex:0 effectiveRange:&ptrange];  
  58.         CGSize pageTextSize = [pageText boundingRectWithSize:curPageView.textView.bounds.size  
  59.                                                      options:NSStringDrawingUsesLineFragmentOrigin  
  60.                                                   attributes:ptdic  
  61.                                                      context:nil].size;  
  62.           
  63.         // 若pageText超出text view的顯示範圍,則調整referCharactersPerPage  
  64.         NSLog(@"height = %d", height);  
  65.         while (pageTextSize.height > height) {   
  66.             NSLog(@"pageTextSize.height = %f", pageTextSize.height);  
  67.             referCharactersPerPage -= 2;                                      // 每頁字符數減2  
  68.             range                   = NSMakeRange(0, referCharactersPerPage); // 重置每頁字符的範圍  
  69.             ptdic = [[bookItem.content attributedSubstringFromRange:range] attributesAtIndex:0 effectiveRange:&range];  
  70.             CGSize pageTextSize = [pageText boundingRectWithSize:curPageView.textView.bounds.size  
  71.                                                          options:NSStringDrawingUsesLineFragmentOrigin  
  72.                                                       attributes:ptdic  
  73.                                                          context:nil].size;  
  74.             pageText                = [bookItem.content.string substringWithRange:range];        // 重置pageText  
  75.               
  76.             pageTextSize = [pageText boundingRectWithSize:curPageView.textView.bounds.size  
  77.                                                   options:NSStringDrawingUsesLineFragmentOrigin  
  78.                                                attributes:ptdic  
  79.                                                   context:nil].size; // 獲取pageText的尺寸  
  80.         }  
  81.           
  82.         // 根據調整後的referCharactersPerPage設定好charsPerPage_  
  83.         charsPerPage_ = referCharactersPerPage;   
  84.         NSLog(@"cpp: %d", charsPerPage_);  
  85.           
  86.         // 計算totalPages_  
  87.         totalPages_ = (int)bookItem.content.length / charsPerPage_ + 1;  
  88.         NSLog(@"ttp: %d", totalPages_);  
  89.           
  90.         // 計算最後一頁的字符數,防止範圍溢出  
  91.         charsOfLastPage_ = textLength_ - (totalPages_ - 1) * charsPerPage_;  
  92.         NSLog(@"colp: %d", charsOfLastPage_);  
  93.           
  94.         // 分頁完成  
  95.         return YES;  
  96.     }  
  97. }  
這樣就看不到礙眼的黃色警告標誌了。

重要的是,由於該方法計算文本的尺寸更爲準確,所以可以使得分頁後頁與頁之間的連貫性好了很多,而且每頁的空間利用率都提高了很多,每頁的文字幾乎鋪滿了整個頁面。
  1. // NOTE: All of the following methods will default to drawing on a baseline, limiting drawing to a single line.  
  2. // To correctly draw and size multi-line text, pass NSStringDrawingUsesLineFragmentOrigin in the options parameter.  
  3. @interface NSString (NSExtendedStringDrawing)  
  4. - (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  5. - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(7_0);  
  6. @end  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章