字符串渲染

在本期中咱們已經討論了不少關於字符串不一樣的話題,從編碼到本地化再到語法分析。但多數狀況下,字符串最終仍是須要被繪製到屏幕上供用戶查看、交互。這篇文章涵蓋了最基本、最好的練習,以及在用戶界面上呈現字符串可能遇到的常見陷阱。html

如何將字符串繪製到屏幕上

簡單起見,咱們先看看 UIKit 在字符串渲染方面爲咱們提供了哪些控件。以後咱們將討論一下對於字符串的渲染,iOS 和 OS X 系統中有哪些類似和不一樣。ios

UIKit 提供了不少能夠在屏幕上顯示和編輯文本的類。每個類都是爲特定使用狀況準備的,因此爲了不沒必要要的問題,爲你手上的任務挑選正確的工具是很是重要的。面試

小編這裏推薦一個羣:691040931 裏面有大量的書籍和麪試資料,不少的iOS開發者都在裏面交流技術

UILabel

UILabel 是將文本繪製到屏幕上最簡單的方式。它是 UIView 的一個子類,用來顯示少許的只讀文本。文本能夠被展現在一行或多行,若是文本不能適應指定的空間咱們還可使用不一樣的方式裁剪。儘管 label 使用的方式很簡單,可是這裏有幾個技巧仍是值得提一提的。bash

label 默認只顯示一行,可是你能夠將 numberOfLines 屬性設爲其餘值來改變這一行爲。將它設置爲一個大於 1 的值,文本的行數將會被限制爲這個指定的值;若是設置爲 0,則是告訴 label 無論文本佔多少行都顯示出來。app

經過設置 text 屬性,Label 能夠顯示簡單的純文本,而設置 attributedText 屬性則可讓 label 顯示富文本。當使用純文本的時候,你可使用 label 的 fonttextColortextAlignmentshadowColor 和 shadowOffset 屬性改變它的外觀,若是你但願改變程序內全部 Label 的風格,你也可使用 [UILabel appearance] 這個方法來進行全局的更改。框架

Attributed strings 提供了更加靈活的格式,字符串的不一樣部分可使用不一樣的格式。讓咱們看看常見佈局部分,下面給出 attributed strings 一些示例。(下文「常見佈局」那一節給出了具體的關於 Attributed String 的一些例子。)工具

除了經過上文提到的那些屬性來調整 UILabel 的顯示風格外,你還能夠經過設置 UILabel 的 adjustsFontSizeToWidthminimumScaleFactoradjustsLetterSpacingToFitWidth 這 3 個 BOOL 值的屬性讓 UILabel 根據所顯示的文本的內容自動地進行調整。若是你很是在乎用戶界面的美觀,那麼你就不要開啓這些屬性,由於這會使文字的顯示效果變得不那麼美觀,可是有的時候,好比在進行程序不一樣語言本土化的時候,你會遇到一些很棘手的問題,除了使用這些選項外很難找到別的解決辦法。不信的話,你能夠打開 iPhone,在設置中把系統語言改成德語,而後你就會發現蘋果官方出品的程序裏處處都是被壓扁變了形的醜陋不堪的文本。這種處理方法並不完美,但有時卻頗有用。佈局

若是你使用這些選項讓 UIKit 壓縮你的文本以適配,若是壓縮的時候想讓文本保持在同一條基線上或須要對齊到左上角,那麼你能夠定義 baselineAdjustment 屬性。然而,這個選項只對單行 label 起做用。post

當你使用上述方法讓文本自動縮放以適配 UILabel 時,你可使用 baselineAdjustment 這個屬性來調整縮放時文本是水平對齊仍是對齊到 Label 的左上角。注意,這個屬性僅在單行的 Lable (即 numberOfLines 屬性值爲1時)中生效。字體

UITextField

像 label 同樣,text fields 能夠處理純文本或帶屬性的文本。但 label 只能顯示文本而已,text field 還能夠處理用戶的輸入。然而 text field 只限於單行文本。UITextField 是 UIControl 的一個子類,它會**掛鉤 (hook into)到響應鏈,而且當用戶開始或結束編輯時分發(deliver)**這些行爲消息,若是想要獲得更多的控制權,你能夠實現 text field 的代理

Text field 有一系列控制文本輸入行爲的選項。UITextField 實現了 UITextInputTraits 協議,這個協議須要你指定鍵盤外觀和操做的各類細節,好比,須要顯示哪一種鍵盤,返回按鈕的響應事件是什麼。

當沒有文本輸入的時候 Text field 還能夠顯示一個佔位符,在右手邊顯示一個標準的清除按鈕,控制任意左右兩個輔助視圖。你還能夠爲其設置一個背景圖片,這樣咱們就能夠用一個可變大小的圖片爲 text field 自定義邊框風格了。

但每當你須要輸入多行文本的時候,你就須要使用到 UITextField 的大哥了...

UITextView

Text view 是顯示或編輯大量文本的理想選擇。 UITextView 是 UIScrollView 的一個子類,因此它能容許用戶先後滾動達處處理溢出文本的目的。和 text field 同樣, text view 也能處理純文本和帶屬性的文本。Text view 也實現了 UITextInputTraits 協議來控制鍵盤的行爲和外觀。

text view 除了處理多行文本的能力外,它最大的賣點就是你可使用、定製整個 Text Kit 堆棧。你能夠爲 layout managertext container 或 text storage 自定義行爲或者替換爲你自定義的子類。你能夠看看 Max 的這篇 Text Kit 方面的文章

不幸的是,UITextView 在 iOS 7 中還有些問題,目前仍是 1.0 版本。它是基於 OS X Text Kit 從頭開始從新實現的。在 iOS 7 以前,它是基於 Webkit 的,而且功能不多。咱們能夠看看 Peter 和 Brent 關於這方面的文章。

Mac中又是什麼狀況呢?

如今咱們已經討論過了 UIKit 中基本的 text 類,下面繼續解釋一下這些類在 AppKit 中結構的不一樣之處。

首先,AppKit 中並無相似 UILabel 的控件。而顯示文本最基本的類是 NSTextField。咱們將 text field 設爲不可編輯、不可選擇,這樣便等同於 iOS 中的 UILabel 了。雖然 NSTextField 聽起來相似於 UITextField,但 NSTextField 並不限制於單行文本。

NSTextView,換句話說,就是等同於 UITextView,它也爲咱們揭露了整個 Cocoa Text System 堆棧。但它還包含了不少額外的功能。很大的緣由是由於 Mac 是一個具備指針設備(鼠標)的電腦。最值得注意的是包含了設置、編輯製表符的標尺。

Core Text

上面咱們討論的全部類最終都使用 Core Text 佈局、繪製真實的符號。Core Text 是一個很是強大的 framework ,它已經超出咱們這篇文章討論的範圍。可是若是你曾經須要經過徹底自定義的方式繪製文本(例如,貝塞爾曲線),那你須要詳細的瞭解一下。

Core Text 在任何繪圖方面都爲你提供了充分的靈活性。然而,Core Text 很是難於操做。它是一個複雜的 Core Foundation / C API。Core Text 在排版方面給了你充分的訪問權限。

在 Table View 中顯示動態文本

可能和全部人都打過交道的字符串繪製就是最多見的可變高度的 table view cells。你能在社交媒體應用中見到這種。 table view 的 delegate 有一個方法:tableView:heightForRowAtIndexPath:,這即是用來計算高度的。iOS 7以前,很難經過一種可靠的方式使用它。

在咱們的示例中,咱們將會在 table view 中顯示一列語錄:

Table view with quotes

首先,爲了實現徹底的自定義,咱們建立一個 UITableViewCell 的子類。在這個子類中,咱們須要親自爲咱們的 label 佈局:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.textLabel.frame = CGRectInset(self.bounds, 
                                       MyTableViewCellInset,
                                       MyTableViewCellInset);
}

複製代碼

MyTableViewCellInset 被定義爲一個常量,因此咱們能夠將它用在 table view 的 delegate 的高度計算中。最簡單、準確計算高度的方法是將字符串轉換成帶屬性的字符串,而後計算出帶屬性字符串的高度。咱們使用 table view 的寬度減去兩倍的 MyTableViewCellInset 常量(前面和後面的空間)。爲了計算真實的高度,咱們須要使用 boundingRectWithSize:options:context: 這個方法。

第一個參數是限制 text 大小的。咱們只須要關心寬度的限制,所以咱們爲高度傳一個最大值常量 CGFLOAT_MAX。第二個參數是很是重要的:若是你傳一個其餘值,bounding rect 無疑會出錯。若是你想要調整字體縮放或進行追蹤,你可使用第三個參數。最終,一旦咱們獲得 boundingRect,咱們須要再次加上 inset:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat labelWidth = self.tableView.bounds.size.width - MyTableViewCellInset*2;
    NSAttributedString *text = [self attributedBodyTextAtIndexPath:indexPath];
    NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin |
                                     NSStringDrawingUsesFontLeading;
    CGRect boundingRect = [text boundingRectWithSize:CGSizeMake(labelWidth, CGFLOAT_MAX)
                                             options:options
                                             context:nil];

    return (CGFloat) (ceil(boundingRect.size.height) + MyTableViewCellInset*2);    
}

複製代碼

對於 bounding rect 的結果還有兩件敏感的事情,除非你讀了文檔,否則這兩件事你不必定會知道:返回值 size 是小數,文檔中讓咱們使用 ceil 將結果四捨五入。最終的結果多是會比實際的大一點。

請注意,由於咱們的 text 是純文本,咱們建立的 attributedBodyTextAtIndexPath: 方法也會在 tableView:cellForRowAtIndexPath: 中用到。這樣,咱們須要確保他們保持同步。

還有,經過閱讀文檔(以下截圖),咱們發現 iOS 7 發佈後,不少方法都被棄用了。若是你經過查找網頁或 StackOverflow,你會發現不少測量字符高度的變通方法。由於蘋果對文本框架進行了重大檢修(在內部實現中,全部的東西都使用 TextKit 進行繪製了,而不是 WebKit),因此請使用新方法。

Deprecated string measuring methods

另外一個動態調整 table view cell 大小的選擇就是使用 Auto Layout,你能夠在這篇博文中找到更詳細的說明。而後你能夠利用 contained lables 的 intrinsicContentSize。然而,如今自動佈局比手動計算要慢不少。但是對於原型開發,這很完美:它容許你快速調整 constraints 而且移動事物(特別當你 cell 中不止一個控件時這顯得特別重要)。一旦你完成產品的設計迭代,而後你就能夠用手動佈局的方式從新編寫代碼。

使用 Text Kit 和 NSAttributedString 進行佈局

使用 Text Kit,你將會擁有使人驚訝的靈活性來建立專業級別的文本佈局。隨着這些靈活性帶來的是如何組合爲數衆多的選項來完成複雜的佈局。

咱們準備給出幾個示例並強調一些常見的佈局問題,同時給出解決方案。

經典的文本

首先,讓咱們看一些經典的文本。咱們將會使用 Jacomy-Régnier 的 Histoire des nombres et de la numération mécanique,並設爲 Bodoni 字體。最終截屏效果以下所示:

Layout-Example-1

這些都是由 Text Kit 完成的。兩段文字之間的裝飾也是文本,使用的是 Bodoni Ornaments 字體。

咱們爲文體風格使用調整好的 text。第一段從最左邊開始,接下來的段落都會插入空格.

這有三種不一樣的風格:文體風格,首行縮進的變化文體風格,裝飾物風格。

讓咱們先設置 body1stAttributes

CGFloat const fontSize = 15;

NSMutableDictionary *body1stAttributes = [NSMutableDictionary dictionary];
body1stAttributes[NSFontAttributeName] = [UIFont fontWithName:@"BodoniSvtyTwoITCTT-Book" 
                                                         size:fontSize];
NSMutableParagraphStyle *body1stParagraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
body1stParagraph.alignment = NSTextAlignmentJustified;
body1stParagraph.minimumLineHeight = fontSize + 3;
body1stParagraph.maximumLineHeight = body1stParagraph.minimumLineHeight;
body1stParagraph.hyphenationFactor = 0.97;
body1stAttributes[NSParagraphStyleAttributeName] = body1stParag
raph;

複製代碼

將字體設置爲 BodoniSvtyTwoITCTT。這是字體的 PostScript 名。若是想尋找字體名,咱們可使用 +[UIFont familyNames] 首先獲得可用的字體系列集合。一個字體系列就是咱們所熟知的字型。每一個字型或字體系列有一個或多個字體。爲了獲得這些字體的名字,咱們可使用 +[UIFont fontNamesForFamilyName:]。注意一下,當你處理多樣字體時,UIFontDescriptor 類很是有用,好比,當你想要知道一個給定的字體是什麼版本的斜體。

許多設置位於 NSParagraphStyle。咱們建立一個默認風格的可變拷貝並作些調整。在咱們的例子中,咱們將會爲字體大小加上 3 pt

接着,咱們會爲這些段落的屬性建立一個拷貝並修改他們來建立 boddyAttributes,(注意,這是咱們段落的屬性,跟上文的 body1stParagraph 已經不是同一個了):

NSMutableDictionary *bodyAttributes = [body1stAttributes mutableCopy];
NSMutableParagraphStyle *bodyParagraph = 
  [bodyAttributes[NSParagraphStyleAttributeName] mutableCopy];
bodyParagraph.firstLineHeadIndent = fontSize;
bodyAttributes[NSParagraphStyleAttributeName] = bodyParagraph;

複製代碼

咱們簡單的建立了一個屬性字典的可變拷貝,同時爲了改變段落風格咱們也須要建立一個可變拷貝。將firstLineHeadIndent 設爲和字體大小同樣,咱們便會獲得想要的空格縮進

接着,裝飾段落風格:

NSMutableDictionary *ornamentAttributes = [NSMutableDictionary dictionary];
ornamentAttributes[NSFontAttributeName] = [UIFont fontWithName:@"BodoniOrnamentsITCTT"
                                                          size:36];
NSMutableParagraphStyle *ornamentParagraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
ornamentParagraph.alignment = NSTextAlignmentCenter;
ornamentParagraph.paragraphSpacingBefore = fontSize;
ornamentParagraph.paragraphSpacing = fontSize;
ornamentAttributes[NSParagraphStyleAttributeName] = ornamentParagraph;

複製代碼

這個很容易理解。咱們使用裝飾字體並將文本居中對齊。此外,在裝飾字符的先後咱們都要加空白段落。

數據表格

接下來是顯示數字的 table。咱們想要將分數的小數點對齊顯示,即英語中的 「.」:

Layout-Example-2

爲了達到這個目的,咱們須要指定 table 將中心停在分隔符上。

對於上面這個示例,咱們簡單的作一下:

NSCharacterSet *decimalTerminator = [NSCharacterSet 
  characterSetWithCharactersInString:decimalFormatter.decimalSeparator];
NSTextTab *decimalTab = [[NSTextTab alloc] 
   initWithTextAlignment:NSTextAlignmentCenter
                location:100
                 options:@{NSTabColumnTerminatorsAttributeName:decimalTerminator}];
NSTextTab *percentTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentRight
                                                        location:200
                                                         options:nil];
NSMutableParagraphStyle *tableParagraphStyle = 
  [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
tableParagraphStyle.tabStops = @[decimalTab, percentTab];

複製代碼

列表

另外一個常見的使用狀況就像 list 這樣:

Layout-Example-3

(圖片來自 Robert's Rules of Order,做者爲 Henry M. Robert)

縮進相對容易設置。咱們須要確保序列號 「(1)」 和 text 或者着重號和 text 之間有一個製表符。而後咱們像這樣調整段落的風格:

NSMutableDictionary *listAttributes = [bodyAttributes mutableCopy];
NSMutableParagraphStyle *listParagraph = 
  [listAttributes[NSParagraphStyleAttributeName] mutableCopy];
listParagraph.headIndent = fontSize * 3;
listParagraph.firstLineHeadIndent = fontSize;
NSTextTab *listTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentNatural
                                                     location:fontSize * 3 
                                                      options:nil];
listParagraph.tabStops = @[listTab];
listAttributes[NSParagraphStyleAttributeName] = listParagraph;

複製代碼

咱們將 headIndent 設置爲真實文本的縮進,將 firstLineHeadIndent 設置爲咱們但願着重號具備的縮進。最終,和 headIndent 同樣,咱們須要在相同的位置增長一個製表符。着重號後的製表符會確保這行文本從正確的位置開始繪製。


小編這裏推薦一個羣:691040931 裏面有大量的書籍和麪試資料,不少的iOS開發者都在裏面交流技術

資料截圖.png

原文 String Rendering

相關文章
相關標籤/搜索