iOS文本佈局探討之一——文本佈局框架TextKit淺析

iOS文本佈局探討之一——文本佈局框架TextKit淺析

1、引言

        在iOS開發中,處理文本的視圖控件主要有4中,UILabel,UITextField,UITextView和UIWebView。其中UILabel與UITextField相對簡單,UITextView是功能完備的文本佈局展現類,經過它能夠進行復雜的富文本佈局,UIWebView主要用來加載網頁或者pdf文件,其能夠進行HTML,CSS和JS等文件的解析。數組

        TextKit是一個偏上層的開發框架,在iOS7以上可用,使用它開發者能夠方便靈活處理複雜的文本佈局,知足開發中對文本佈局的各類複雜需求。TextKit其實是基於CoreText的一個上層框架,其是面向對象的,若是TextKit中提供的API沒法知足需求,可使用CoreText中的API進行更底層的開發。app

        官方文檔中的一張圖片很確切,常常會被用來描述TextKit框架在iOS系統文本渲染中所處的位置。框架

2、TextKit框架的結構

        界面在進行文本的渲染時,有下面幾個必要條件:ide

1.要渲染展現的內容。佈局

2.將內容渲染在某個視圖上。字體

3.內容渲染在視圖上的尺寸位置和形狀。spa

在TextKit框架中,提供了幾個類分別對應處理上述的必要條件:.net

1.NSTextStorage對應要渲染展現的內容。code

2.UITextView對應要渲染的視圖。orm

3.NSTextContainer對應渲染的尺寸位置和形狀信息。

除了上述3個類以外,TextKit框架中的NSLayoutManager類做爲協調者來進行佈局操做。

上述關係以下圖所示:

3、使用TextKit進行文本佈局流程

        我的理解,TextKit主要用於更精細的處理文本佈局以及進行復雜的圖文混排佈局,使用TextKit進行文本的佈局展現十分繁瑣,首先須要將顯示內容定義爲一個NSTextStorage對象,以後爲其添加一個佈局管理器對象NSLayoutManager,在NSLayoutManager中,須要進行NSTextContainer的定義,定義多了NSTextContainer對象則會將文本進行分頁。最後,將要展現的NSTextContainer綁定到具體的UITextView視圖上。

示例代碼以下:

//定義Container
    NSTextContainer * container = [[NSTextContainer alloc]initWithSize:CGSizeMake(150, 200)];
    //定義佈局管理類
    NSLayoutManager * layoutManager = [[NSLayoutManager  alloc]init];
    //將container添加進佈局管理類管理
    [layoutManager addTextContainer:container];
    //定義一個Storage
    NSTextStorage * storage = [[NSTextStorage alloc]initWithString:@"The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on."];
    //爲Storage添加一個佈局管理器
    [storage addLayoutManager:layoutManager];
    //將要顯示的container與視圖TextView綁定
    UITextView * textView = [[UITextView alloc]initWithFrame:self.view.frame textContainer:container];
    [self.view addSubview:textView];

上面代碼演示的過程以下圖所示:

須要注意,TextKit進行佈局的核心思路是最終的視圖對應一個文本塊Container,並非一段文本內容Storage,LayoutManager會將完整的內容根據其中Container的尺寸進行分頁,TextView根據須要顯示的部分進行Container的選擇。

4、瞭解NSTextContainer類

        NSTextContainer能夠簡單理解爲建立一個文本區塊,文本內容將在這個區塊中進行渲染,其中經常使用屬性與方法以下:

//初始化方法 設置區塊的尺寸
- (instancetype)initWithSize:(CGSize)size;
//與其綁定的layoutManager 須要注意,不是設置這個屬性 使用[NSLayoutManager addTextContainer:]方式來進行綁定
@property(nullable, assign, NS_NONATOMIC_IOSONLY) NSLayoutManager *layoutManager;
//替換綁定的佈局管理類對象
- (void)replaceLayoutManager:(NSLayoutManager *)newLayoutManager;
//獲取區塊尺寸
@property(NS_NONATOMIC_IOSONLY) CGSize size;
//設置從區塊中剔除某一區域
@property(copy, NS_NONATOMIC_IOSONLY) NSArray<UIBezierPath *> *exclusionPaths;
//設置截斷模式 須要注意 這個屬性的設置只是會影響此區塊的最後一行的截斷模式
@property(NS_NONATOMIC_IOSONLY) NSLineBreakMode lineBreakMode;
//設置每行文本左右空出的間距
@property(NS_NONATOMIC_IOSONLY) CGFloat lineFragmentPadding;
//設置TextView上可輸入的文本最大行數
@property(NS_NONATOMIC_IOSONLY) NSUInteger maximumNumberOfLines;

//這個方法用於提供給子類進行重寫 這裏返回的Rect是能夠佈局文本的區域
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(nullable CGRect *)remainingRect;
//這個BOOL值的屬性決定Container的寬度是否自適應TextView的寬度
@property(NS_NONATOMIC_IOSONLY) BOOL widthTracksTextView;
//這個BOOL值的屬性決定Container的高度是否自適應TextView的高度
@property(NS_NONATOMIC_IOSONLY) BOOL heightTracksTextView;

上面所列舉的方法中,exclusionPaths屬性十分強大,經過設置它,能夠將佈局區域內剔出一塊區域不進行佈局,示例代碼以下:

[super viewDidLoad];
    NSTextContainer * container = [[NSTextContainer alloc]initWithSize:CGSizeMake(300, 500)];

    UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:70 startAngle:0 endAngle:M_PI*2 clockwise:YES];
    container.exclusionPaths = @[path];
    container.lineBreakMode = NSLineBreakByCharWrapping;
    NSLayoutManager * layoutManager = [[NSLayoutManager  alloc]init];
    [layoutManager addTextContainer:container];
    NSTextStorage * storage = [[NSTextStorage alloc]initWithString:@"The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics.The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics.An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics."];
    [storage addLayoutManager:layoutManager];
    UITextView * textView = [[UITextView alloc]initWithFrame:self.view.frame textContainer:container];
    [self.view addSubview:textView];

效果以下圖:

5、關於NSLayoutManager

        顧名思義,NSLayoutManager專門負責對文本的佈局渲染,簡單理解,其從NSTextStorage從拿去展現的內容,將去處理後佈局到NSTextContainer中。

        NSLayoutManager與NSTextContainer的關係爲一對多,放入NSLayoutManager中的NSTextContainer會以有序數組的形式進行管理,在內容佈局時,超出第一個NSTextContainer的內容會被佈局到後一個NSTextContainer中。

        NSLayoutManager中有關NSTextContainer操做的方法以下:

//container數組
@property(readonly, NS_NONATOMIC_IOSONLY) NSArray<NSTextContainer *> *textContainers;
//添加一個container
- (void)addTextContainer:(NSTextContainer *)container;
//在指定位置插入一個container
- (void)insertTextContainer:(NSTextContainer *)container atIndex:(NSUInteger)index;
//刪除一個指定的container
- (void)removeTextContainerAtIndex:(NSUInteger)index;
//注意 這個方法不須要顯式的調用 當佈局Container發生變化時 系統會自動調用
- (void)textContainerChangedGeometry:(NSTextContainer *)container;

與佈局管理相關的屬性與方法以下:

//是否顯示隱形的符號
/*
默認爲NO,若是設置爲YES,則會將空格等隱形字符顯示出來
*/
@property(NS_NONATOMIC_IOSONLY) BOOL showsInvisibleCharacters;
//是否顯示某些佈局控制字符
@property(NS_NONATOMIC_IOSONLY) BOOL showsControlCharacters;
//這個屬性能夠用於設置斷字
/*
這個屬性的取值爲0到1之間 默認爲0 即單詞換行時歷來不會中斷 越接近1 則使用連字符進行單詞換行中斷的機率越大
*/
@property(NS_NONATOMIC_IOSONLY) CGFloat hyphenationFactor;
//是否使用字體定義的行距
/*
默認使用字體所定義的行距信息 經過設置這個屬性爲NO能夠關閉此功能
*/
@property(NS_NONATOMIC_IOSONLY) BOOL usesFontLeading;
//這個屬性設置是否容許對相鄰位置的內容進行佈局 默認爲YES,設置爲NO後將能夠提供大文本佈局的效率
@property(NS_NONATOMIC_IOSONLY) BOOL allowsNonContiguousLayout;

//下面這幾個方法用於移除某一範圍內的佈局
- (void)invalidateGlyphsForCharacterRange:(NSRange)charRange changeInLength:(NSInteger)delta actualCharacterRange:(nullable NSRangePointer)actualCharRange;
- (void)invalidateLayoutForCharacterRange:(NSRange)charRange actualCharacterRange:(nullable NSRangePointer)actualCharRange NS_AVAILABLE(10_5, 7_0);
- (void)invalidateDisplayForCharacterRange:(NSRange)charRange;
- (void)invalidateDisplayForGlyphRange:(NSRange)glyphRange;

6、文本內容類NSTextStorage

        NSTextStorage其實是繼承自NSMutableAttributedString。NSAttributedString是一種自帶屬性的字符串類,關於NSAttributedString的基本用法,以下博客中有介紹:

http://my.oschina.net/u/2340880/blog/397500

        TextKit框架中在對文本進行佈局時,主要關注於3個方面:

1.字符的屬性,例如顏色,字體等。

2.行與段落的屬性,如縮進,行間距等。

3.文檔屬性,包括四周邊距、文檔尺寸等。

這些都由NSAttributedString來進行定義。

        如上所介紹的是TextKit框架的主要工做原理,文字渲染,圖文混排的更多內容,後面博客會繼續探討。有疏漏之處,共同討論進步。

專一技術,熱愛生活,交流技術,也作朋友。

——琿少 QQ羣:203317592

相關文章
相關標籤/搜索