先上一张图:

这是使用UITextView时用到的iOS7新增加的类:NSTextContainer、NSLayoutManager、NSTextStorage及其相互关系:

这三个新出的类还没有在官方出独立的class reference,但是在新出的UIKit_Framework上已经有这些类的相关说明及使用方法,当然官方还会更新。

以下是摘自文档的部分语句:

首先是NSTextContainer:

The NSTextContainer class defines a region in which text is laid out.

An NSTextContainer object defines rectangular regions, and you can define exclusion paths inside the textcontainer'sboundingrectanglesothattextflowsaroundtheexclusionpathasitislaidout.

接着是NSLayoutManager:

An NSLayoutManager object coordinates the layout and display of characters held in an NSTextStorage object. It maps Unicode character codes to glyphs, sets the glyphs in a series of NSTextContainer objects, and displays them in a series of text view objects.

最后是NSTextStorage:

NSTextStorage is a semiconcrete subclass of NSMutableAttributedString that manages a set of client NSLayoutManagerobjects,notifyingthemofanychangestoitscharactersorattributessothattheycanrelay and redisplay the text as needed.

按照个人理解:

NSTextStorage保存并管理UITextView要展示的文字内容,该类是NSMutableAttributedString的子类,由于可以灵活地往文字添加或修改属性,所以非常适用于保存并修改文字属性。

NSLayoutManager用于管理NSTextStorage其中的文字内容的排版布局。

NSTextContainer则定义了一个矩形区域用于存放已经进行了排版并设置好属性的文字。

以上三者是相互包含相互作用的层次关系。

接下来是三种类的使用:

    CGRect textViewRect = CGRectInset(self.view.bounds, 10.0, 20.0);

    // NSTextContainer
NSTextContainer *container = [[NSTextContainer alloc] initWithSize:CGSizeMake(textViewRect.size.width, CGFLOAT_MAX)]; // new in iOS 7.0
container.widthTracksTextView = YES; // Controls whether the receiveradjusts the width of its bounding rectangle when its text view is resized // NSLayoutManager
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; // new in iOS 7.0
[layoutManager addTextContainer:container]; // NSTextStorage subclass
self.textStorage = [[TextStorage alloc] init]; // new in iOS 7.0
[self.textStorage addLayoutManager:layoutManager];

首先是初始化类对象,然后通过add方法来建立三者之间的关系。

最后必须注意要在UITextView中通过initWithFrame:textContainer:方法来添加相应的NSTextContainer从而设置好对应的文字。

    // UITextView
UITextView *newTextView = [[UITextView alloc] initWithFrame:textViewRect textContainer:container];
newTextView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
newTextView.scrollEnabled = YES;
newTextView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
// newTextView.editable = NO;
newTextView.font = [UIFont fontWithName:self.textStorage.fontName size:18.0];
newTextView.dataDetectorTypes = UIDataDetectorTypeAll;
self.textView = newTextView;
[self.view addSubview:self.textView];

如果要使用UITextStorage类来改变文字的属性,分别使用

[_textStorage beginEditing];
[_textStorage endEditing];

来向UITextStorage类或其子类发送开始或完成文字编辑的消息。在两条语句之间进行相应的文字编辑,例如为文字添加letterpress style:

    [_textStorage beginEditing];
NSDictionary *attrsDic = @{NSTextEffectAttributeName: NSTextEffectLetterpressStyle};
UIKIT_EXTERN NSString *const NSTextEffectAttributeName NS_AVAILABLE_IOS(7_0); // NSString, default nil: no text effect
NSMutableAttributedString *mutableAttrString = [[NSMutableAttributedString alloc] initWithString:@"Letterpress" attributes:attrsDic];
NSAttributedString *appendAttrString = [[NSAttributedString alloc] initWithString:@" Append:Letterpress"];
[mutableAttrString appendAttributedString:appendAttrString];
[_textStorage setAttributedString:mutableAttrString];
[_textStorage endEditing];

可以看到通过attribute来改变文字的属性是非常简单的。

又如通过NSTextStorage类为文字添加颜色属性:

    [_textStorage beginEditing];
/* Dynamic Coloring Text */
self.textStorage.bookItem = [[BookItem alloc] initWithBookName:@"Dynamic Coloring.rtf"];
self.textStorage.tokens = @{@"Alice": @{NSForegroundColorAttributeName: [UIColor redColor]},
@"Rabbit": @{NSForegroundColorAttributeName: [UIColor greenColor]},
DefaultTokenName: @{NSForegroundColorAttributeName: [UIColor blackColor]}
};
[_textStorage setAttributedString:_textStorage.bookItem.content];
[_textStorage endEditing];

其处理过程要看看重写的NSTextStorage子类:

接口部分:

NSString *const DefaultTokenName;

@interface TextStorage : NSTextStorage
@property (nonatomic, strong) NSString *fontName;
@property (nonatomic, copy) NSDictionary *tokens; // a dictionary, keyed by text snippets(小片段), with attributes we want to add
@property (nonatomic, strong) BookItem *bookItem;
@end

以及.m中的匿名类别:

#import "TextStorage.h"

NSString *const DefaultTokenName = @"DefaultTokenName";

@interface TextStorage ()
{
NSMutableAttributedString *_storingText; // 存储的文字
BOOL _dynamicTextNeedsUpdate; // 文字是否需要更新
}
@end

然后是基本的初始化方法:

// get fontName Snell RoundHand
-(NSString *)fontName
{
NSArray *fontFamily = [UIFont familyNames];
NSString *str = fontFamily[2];
// NSLog(@"%@", str);
return str;
} // initial
-(id)init
{
self = [super init];
if (self) {
_storingText = [[NSMutableAttributedString alloc] init];
}
return self;
}

重点来了,重写NSTextStorage类的子类必须重载以下四个方法:

// Must override NSAttributedString primitive method
-(NSString *)string // 返回保存的文字
-(NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range // 获取指定范围内的文字属性 // Must override NSMutableAttributedString primitive method
-(void)setAttributes:(NSDictionary *)attrs range:(NSRange)range // 设置指定范围内的文字属性
-(void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str // 修改指定范围内的文字

具体实现如下:

// Must override NSAttributedString primitive method
// 返回保存的文字
-(NSString *)string
{
return [_storingText string];
} // 获取指定范围内的文字属性
-(NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
{
return [_storingText attributesAtIndex:location effectiveRange:range];
}

_storingText保存了NSTextStorage中的文字,string方法直接返回该变量的string值。

要获取文字属性时也可以直接从_storingText入手。

// Must override NSMutableAttributedString primitive method
// 设置指定范围内的文字属性
-(void)setAttributes:(NSDictionary *)attrs range:(NSRange)range
{
[self beginEditing];
[_storingText setAttributes:attrs range:range];
[self edited:NSTextStorageEditedAttributes range:range changeInLength:0]; // Notifies and records a recent change. If there are no outstanding -beginEditing calls, this method calls -processEditing to trigger post-editing processes. This method has to be called by the primitives after changes are made if subclassed and overridden. editedRange is the range in the original string (before the edit).
[self endEditing];
} // 修改指定范围内的文字
-(void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
{
[self beginEditing];
[_storingText replaceCharactersInRange:range withString:str];
[self edited:NSTextStorageEditedAttributes | NSTextStorageEditedCharacters range:range changeInLength:str.length - range.length];
_dynamicTextNeedsUpdate = YES;
[self endEditing];
}

可以看到在设置或要改变文字的属性时必须分别调用beginEditing和endEditing方法,在两者之间进行相应的动作。

如果NSTextStorge类收到endEditing的通知,则调用processEditing方法进行处理。

// Sends out -textStorage:willProcessEditing, fixes the attributes, sends out -textStorage:didProcessEditing, and notifies the layout managers of change with the -processEditingForTextStorage:edited:range:changeInLength:invalidatedRange: method.  Invoked from -edited:range:changeInLength: or -endEditing.
-(void)processEditing
{
if (_dynamicTextNeedsUpdate) {
_dynamicTextNeedsUpdate = NO;
[self performReplacementsForCharacterChangeInRange:[self editedRange]];
}
[super processEditing];
}

这是iOS7新增的用于设置文字属性和进行排版的三个主要的类,本文主要说了NSTextStorage类。另外两个类我会继续跟进学习。

对比起iOS6及以前,处理文字的排版布局和重置属性变得更加简便。

Snell Roundhand是一种我觉得非常华丽的字体,非常喜欢。最后上张程序运行结果的图:

TextKit学习(三)NSTextStorage,NSLayoutManager,NSTextContainer和UITextView的更多相关文章

  1. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

  2. HTTP学习三:HTTPS

    HTTP学习三:HTTPS 1 HTTP安全问题 HTTP1.0/1.1在网络中是明文传输的,因此会被黑客进行攻击. 1.1 窃取数据 因为HTTP1.0/1.1是明文的,黑客很容易获得用户的重要数据 ...

  3. TweenMax动画库学习(三)

    目录               TweenMax动画库学习(一)            TweenMax动画库学习(二)            TweenMax动画库学习(三)           ...

  4. Struts2框架学习(三) 数据处理

    Struts2框架学习(三) 数据处理 Struts2框架框架使用OGNL语言和值栈技术实现数据的流转处理. 值栈就相当于一个容器,用来存放数据,而OGNL是一种快速查询数据的语言. 值栈:Value ...

  5. 4.机器学习——统计学习三要素与最大似然估计、最大后验概率估计及L1、L2正则化

    1.前言 之前我一直对于“最大似然估计”犯迷糊,今天在看了陶轻松.忆臻.nebulaf91等人的博客以及李航老师的<统计学习方法>后,豁然开朗,于是在此记下一些心得体会. “最大似然估计” ...

  6. DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

    DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件   本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...

  7. [ZZ] 深度学习三巨头之一来清华演讲了,你只需要知道这7点

    深度学习三巨头之一来清华演讲了,你只需要知道这7点 http://wemedia.ifeng.com/10939074/wemedia.shtml Yann LeCun还提到了一项FAIR开发的,用于 ...

  8. SVG 学习<三>渐变

    目录 SVG 学习<一>基础图形及线段 SVG 学习<二>进阶 SVG世界,视野,视窗 stroke属性 svg分组 SVG 学习<三>渐变 SVG 学习<四 ...

  9. Android JNI学习(三)——Java与Native相互调用

    本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...

随机推荐

  1. 关于Hibernate中的Configuration

    Hibernate中,关于从 Configuration中建立一个SessionFactory常用的可以有两种方法,一种是为Configuration提供hibernate.cfg.xml配置文件,还 ...

  2. SGU 187 - Twist and whirl -- want to cheat

    原题地址:http://acm.sgu.ru/problem.php?contest=0&problem=187 太开心啦!!!!这道题从2013年开始困扰我!!今天晚上第四次下定决心把它写一 ...

  3. 关于post get ajax

    今天写程序时  出现了下面问题: 前台 $.post('ajax/GetDataAjax.ashx', { 'mode': 'DEL', 'BM_ID': bm_id }, function (res ...

  4. bzoj1296: [SCOI2009]粉刷匠

    dp. 用到俩次dp,用1和0代表俩种颜色,首先对于每块木板我们进行一次dp,g[i][j]代表前j个格子刷i次最多能涂到几个格子. 则 g[i][j]=max(g[i-1][k],max(cnt[j ...

  5. UVA 568 Just the Facts (水)

    题意: 求一个数n的阶乘,其往后数第1个不是0的数字是多少. 思路: [1,n]逐个乘,出现后缀0就过滤掉,比如12300就变成123,继续算下去.为解决爆long long问题,将其余一个数mod, ...

  6. HDU 2444 The Accomodation of Students (偶图判定,匈牙利算法)

    题意: 有一堆的学生关系,要将他们先分成两个组,同组的人都不互不认识,如果不能分2组,输出No.若能,则继续.在两组中挑两个认识的人(每组各1人)到一个双人房.输出需要多少个双人房? 思路: 先判定是 ...

  7. poj 3694 Network

    题意: 添加每条新连接后网络中桥的数目// 超时 先放着了 ,下次改//早上这代码超时了 下午改了,代码在下面#include <iostream> #include <algori ...

  8. ViewPager+Fragment替代TabHost效果的简单示例

    本示例旨在展示fragment替代tabhost的效果,具体的业务逻辑还要根据这个示例进行扩展. 效果图如下: 主Activity代码: package com.llb.view; import ja ...

  9. 【MySQL for Mac】终极解决——MySQL在Mac的字符集设置

    这个问题烦恼一天了,现在终于得以解决.分享给大家 首先贴出来,亲测不可行的博客连接: http://www.2cto.com/database/201305/215563.html http://bl ...

  10. 【JSP】弹出带输入框可 确认密码 对话框

    <body> <input type="submit" value="删除历史全部订单" onclick="deleteall()& ...