iOS 和 Mac OS X 的字串渲染

NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

如何將字串繪製到螢幕上

為了簡單起見,我們先看看UIKit在字串渲染方面為我們提供了哪些控制元件。之後我們將討論一下對於字串的渲染, iOS 和 OS X 系統中有哪些相似和不同。

UIKit 提供了很多可以在螢幕上顯示和編輯文字的類。每一個類都是為特定使用情況準備的,所以為了避免不必要的問題,為你手上的任務挑選正確的工具是非常重要的。

UILabel

UILabel是將文字繪製到螢幕上最簡單的方式。它是UIView的一個子類,用來顯示少量的只讀文字。文字可以被展示在一行或多行,如果文字不能適應指定的空間我們還可以使用不同的方式裁剪。儘管labels使用的方式很簡單,但是這裡有幾個技巧還是值得我們提一提的。

labels預設只顯示一行,但是你可以將numberOfLines屬性設為其他值來改變這一行為。將它設定為一個大於1的值,文字的行數將會被限制為這個指定的值,如果設定為0則是告訴label不管文字佔多少行都顯示出來。

通過設定text屬性,Labels可以顯示簡單的純文字,而設定attributedText屬性則可以讓label顯示富文字。當使用純文字的時候,你可以使用label的font,textColor, textAlignment,shadowColor和shadowOffset屬性改變它的外觀,如果你希望改變整個程式所有Label的風格,你也可以使用[UILabel appearance] 這個方法來進行全域性的更改。

Attributed strings提供了更加靈活的風格可供選擇,字串的不同部分可以使用不同的風格。讓我們看看常見佈區域性分,下面給出attributed strings一些示例。(下文“常見佈局”那一節給出了具體的關於 Attributed String 的一些例子。)

除了通過上文提到的那些屬性來調整UILabel 的顯示風格外,你還可以通過設定UILabel的這3個BOOL值的屬性adjustsFontSizeToWidth,minimumScaleFactor,adjustsLetterSpacingToFitWidth 來讓 UILabel 根據所顯示的文字的內容自動地進行調整。如果你非常在意使用者介面的美觀,那麼你就不要開啟這些屬性,因為這會使文字的顯示效果變得不那麼美觀,但是有的時候,比如在進行App的不同語言的本土化的時候,你會遇到一些很棘手的問題,除了使用這些選項外很難找到別的解決辦法。不信的話,你可以開啟 iPhone,在設定中把系統語言改為德語,然後你就會發現蘋果官方出品的程式裡到處都是被壓扁變了形的醜陋不堪的文字。這種處理方法並不完美,但有時卻很有用。

如果你使用這些選項讓UIKit壓縮你的文字以適配,如果壓縮的時候想讓文字保持在同一條基線上或需要對齊到左上角,那麼你可以定義baselineAdjustment屬性。然而,這個選項只對單行labels起作用。

當你使用上述的方法讓文字自動縮放大小以適配你的 UILabel 時,你可以使用 baselineAdjustment 這個屬性來調整縮放時文字的基準線,是保持統一基準線還是對齊到你的 Label 的左上角。注意,這個屬性僅在單行的 Lable (即 numberOfLines 屬性值為1時)中生效。

UITextField

像labels一樣,text fields可以處理純文字或帶屬性的文字。但labels只是能顯示文字而已,text fields還可以處理使用者輸入。然而,text fields只限於單行文字。因此,UITextField是UIControl的一個子類,它會掛鉤到(hook into)響應鏈,並且當使用者開始或結束編輯時分發(deliver)這些行為訊息。如果想要得到更多的控制權,你可以實現text field的代理。

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

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

但每當你需要輸入多行文字的時候,你就需要使用到UITextField的大哥了……

UITextView

Text views是顯示或編輯大量文字的理想選擇。UITextView是UIScrollView的一個子類,所以它能允許使用者前後滾動達到處理溢位文字的目的。和text fields一樣,text views也能處理純文字和帶屬性的文字。Text views也實現了UITextInputTraits協議來控制鍵盤的行為和外觀。

但除了text view處理多行文字的能力外,它最大的賣點就是你可以使用、定製整個Text Kit堆。你可以自定義行為或為layout manager、text container或text storage替換你自定義的子類。objc.io issue #5中有提到Text Kit方面的文章

不幸的是,UITextView在iOS7中還有些問題。目前還是1.0版本。它是基於OS X Text Kit從頭開始重新實現的。iOS7之前,它是基於Webkit並且功能很少。

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,它已經超出我們這篇文章討論的範圍。但是如果你曾經需要通過完全自定義的方式繪製文字(e.g.貝塞爾曲線),那你需要詳細的瞭解一下。

Core Text在任何繪圖方面為你提供了充分的靈活性。然而,Core Text非常難於操作。它是一個複雜的Core Foundation / C API。Core Text 在排版方面給了你充分的使用權。

在Table View中顯示動態Text

可能和所有人都打過交道的字串繪製方法就是最常見的可變高度的table view cells。你能在社交媒體應用中見到這種。table view的delegate有一個方法。tableView:heightForRowAtIndexPath:,這便是用來計算高度的。iOS7之前,很難通過一種可靠的方式使用它。

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

首先,為了實現完全的自定義,我們建立一個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無疑會出錯。如果你想要調整字型縮放and/or追蹤,你可以使用第三個引數。最終,一旦我們得到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 = 1;
return (CGFloat) (ceil(boundingRect.size.height)   MyTableViewCellInset*2);
}

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

請注意,因為我們的text是純文字時,我們建立的attributedBodyTextAtIndexPath:方法也會在tableView:cellForRowAtIndexPath:中用到。這樣,我們需要確保他們保持同步。

還有,看看文件(如下截圖),我們可以看到iOS7釋出後,很多方法都被棄用了。如果你通過查詢網頁或StackOverflow,你會發現很多答案、以及測量字元大小的變通方法。因為text system受到了重大檢修(在內部實現中,所有的東西都使用TextKit進行繪製了,而不是WebKit),所以請使用新方法。

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

使用Text Kit和NSAttributedString佈局

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

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

經典的文字

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

這些都是由Text Kit完成的。兩段文字之間的裝飾也是text,使用的是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] = body1stParagraph;

我們將字型設定為BodoniSvtyTwoITCTT。這是字型的PostScript名。如果想尋找字型名,我們可以使用 [UIFont familyNames]首先得到可用的字型系列集合。一個字型系列就是我們所熟知的字型。每個字型或字型系列有一個或多個字型。為了得到這些字型的名字,我們可以使用 [UIFont fontNamesForFamilyName:]。注意一下,當你處理多樣字型時,UIFontDescriptor類非常有用,e.g.當你想要知道一個給定的字型是什麼版本的斜體。

許多設定位於NSParagraphStyle。我們建立一個預設風格的可變拷貝並做些調整。在我們的例子中,我們將會為字型大小加上3pt。

接著,我們會為這些段落的屬性建立一個拷貝並修改他們來建立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。我們想要將分數的小數點對齊顯示,i.e.英語中的”.”:

為了達到這個目的,我們需要指定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這樣:

縮排相對容易設定。我們需要確保序列號(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一樣,我們需要在相同的位置增加一個製表符。著重號後的製表符會確保這行文字從正確的位置開始繪製。

 

原文 String Rendering
翻譯 answer_huang

相關文章

IOS開發 最新文章