先上一张图:
这是使用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 *container = [[NSTextContainer alloc] initWithSize:CGSizeMake(textViewRect.size.width, CGFLOAT_MAX)];
- container.widthTracksTextView = YES;
-
-
-
- NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
- [layoutManager addTextContainer:container];
-
-
-
- self.textStorage = [[TextStorage alloc] init];
- [self.textStorage addLayoutManager:layoutManager];
首先是初始化类对象,然后通过add方法来建立三者之间的关系。
最后必须注意要在UITextView中通过initWithFrame:textContainer:方法来添加相应的NSTextContainer从而设置好对应的文字。
-
- UITextView *newTextView = [[UITextView alloc] initWithFrame:textViewRect textContainer:container];
- newTextView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
- newTextView.scrollEnabled = YES;
- newTextView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
-
- newTextView.font = [UIFont fontWithName:self.textStorage.fontName size:18.0];
- newTextView.dataDetectorTypes = UIDataDetectorTypeAll;
- self.textView = newTextView;
- [self.view addSubview:self.textView];
如果要使用NSTextStorage类来改变文字的属性,分别使用
- [_textStorage beginEditing];
- [_textStorage endEditing];
来向UITextStorage类或其子类发送开始或完成文字编辑的消息。在两条语句之间进行相应的文字编辑,例如为文字添加letterpress style:
- [_textStorage beginEditing];
- NSDictionary *attrsDic = @{NSTextEffectAttributeName: NSTextEffectLetterpressStyle};
- UIKIT_EXTERN NSString *const NSTextEffectAttributeName NS_AVAILABLE_IOS(7_0);
- 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];
-
- 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;
- @property (nonatomic, strong) BookItem *bookItem;
- @end
以及.m中的匿名类别:
- #import "TextStorage.h"
-
- NSString *const DefaultTokenName = @"DefaultTokenName";
-
- @interface TextStorage ()
- {
- NSMutableAttributedString *_storingText;
- BOOL _dynamicTextNeedsUpdate;
- }
- @end
然后是基本的初始化方法:
-
- -(NSString *)fontName
- {
- NSArray *fontFamily = [UIFont familyNames];
- NSString *str = fontFamily[2];
-
- return str;
- }
-
-
- -(id)init
- {
- self = [super init];
- if (self) {
- _storingText = [[NSMutableAttributedString alloc] init];
- }
- return self;
- }
重点来了,重写NSTextStorage类的子类必须重载以下四个方法:
-
- -(NSString *)string
- -(NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
-
-
- -(void)setAttributes:(NSDictionary *)attrs range:(NSRange)range
- -(void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
具体实现如下:
-
-
- -(NSString *)string
- {
- return [_storingText string];
- }
-
-
- -(NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
- {
- return [_storingText attributesAtIndex:location effectiveRange:range];
- }
_storingText保存了NSTextStorage中的文字,string方法直接返回该变量的string值。
要获取文字属性时也可以直接从_storingText入手。
-
-
- -(void)setAttributes:(NSDictionary *)attrs range:(NSRange)range
- {
- [self beginEditing];
- [_storingText setAttributes:attrs range:range];
- [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
- [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方法进行处理。
-
- -(void)processEditing
- {
- if (_dynamicTextNeedsUpdate) {
- _dynamicTextNeedsUpdate = NO;
- [self performReplacementsForCharacterChangeInRange:[self editedRange]];
- }
- [super processEditing];
- }
这是iOS7新增的用于设置文字属性和进行排版的三个主要的类,本文主要说了NSTextStorage类。另外两个类我会继续跟进学习。
对比起iOS6及以前,处理文字的排版布局和重置属性变得更加简便。
Demo已经上传了,可以下载看看。
Snell Roundhand是一种我觉得非常华丽的字体,非常喜欢。最后上张程序运行结果的图: