iOS7.0之后的類,同樣是對富文本的操作,但是再iOS9.0之后廢棄了一些屬性,所以建議有需求的人有時間的人好好搞一下
這里還是貼上代碼及原帖地址和demo鏈接
這是使用UITextView時用到的iOS7新增加的類:NSTextContainer、NSLayoutManager、NSTextStorage及其相互關系:
這三個新出的類還沒有在官方出獨立的class reference,但是在新出的UIKit_Framework上已經(jīng)有這些類的相關說明及使用方法,當然官方還會更新。
以下是摘自文檔的部分語句:
首先是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則定義了一個矩形區(qū)域用于存放已經(jīng)進行了排版并設置好屬性的文字。
以上三者是相互包含相互作用的層次關系。
接下來是三種類的使用:
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];
如果要使用NSTextStorage類來改變文字的屬性,分別使用
[_textStorage beginEditing];
[_textStorage endEditing];
來向UITextStorage類或其子類發(fā)送開始或完成文字編輯的消息。在兩條語句之間進行相應的文字編輯,例如為文字添加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 // 修改指定范圍內的文字
具體實現(xiàn)如下:
// 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];
}
- (void)performReplacementsForCharacterChangeInRange:(NSRange)changedRange
{
NSRange extendedRange = NSUnionRange(changedRange, [[_storingText string]
lineRangeForRange:NSMakeRange(changedRange.location, 0)]);
extendedRange = NSUnionRange(changedRange, [[_storingText string]
lineRangeForRange:NSMakeRange(NSMaxRange(changedRange), 0)]);
[self applyStylesToRange:extendedRange];
}
- (NSDictionary*)createAttributesForFontStyle:(NSString*)style
withTrait:(uint32_t)trait {
UIFontDescriptor *fontDescriptor = [UIFontDescriptor
preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
UIFontDescriptor *descriptorWithTrait = [fontDescriptor
fontDescriptorWithSymbolicTraits:trait];
UIFont* font = [UIFont fontWithDescriptor:descriptorWithTrait size: 0.0];
return @{ NSFontAttributeName : font };
}
- (void)createMarkupStyledPatterns
{
UIFontDescriptor *scriptFontDescriptor =
[UIFontDescriptor fontDescriptorWithFontAttributes:
@{UIFontDescriptorFamilyAttribute: @"Bradley Hand"}];
// 1. base our script font on the preferred body font size
UIFontDescriptor* bodyFontDescriptor = [UIFontDescriptor
preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
NSNumber* bodyFontSize = bodyFontDescriptor.
fontAttributes[UIFontDescriptorSizeAttribute];
UIFont* scriptFont = [UIFont
fontWithDescriptor:scriptFontDescriptor size:[bodyFontSize floatValue]];
// 2. create the attributes
NSDictionary* boldAttributes = [self
createAttributesForFontStyle:UIFontTextStyleBody
withTrait:UIFontDescriptorTraitBold];
NSDictionary* italicAttributes = [self
createAttributesForFontStyle:UIFontTextStyleBody
withTrait:UIFontDescriptorTraitItalic];
NSDictionary* strikeThroughAttributes = @{ NSStrikethroughStyleAttributeName : @1,
NSForegroundColorAttributeName: [UIColor redColor]};
NSDictionary* scriptAttributes = @{ NSFontAttributeName : scriptFont,
NSForegroundColorAttributeName: [UIColor blueColor]
};
NSDictionary* redTextAttributes =
@{ NSForegroundColorAttributeName : [UIColor redColor]};
_replacements = @{
@"(\\*\\*\\w+(\\s\\w+)*\\*\\*)" : boldAttributes,
@"(_\\w+(\\s\\w+)*_)" : italicAttributes,
@"(~~\\w+(\\s\\w+)*~~)" : strikeThroughAttributes,
@"(`\\w+(\\s\\w+)*`)" : scriptAttributes,
@"\\s([A-Z]{2,})\\s" : redTextAttributes
};
}
- (void)applyStylesToRange:(NSRange)searchRange
{
NSDictionary* normalAttrs = @{NSFontAttributeName:
[UIFont preferredFontForTextStyle:UIFontTextStyleBody]};
// iterate over each replacement
for (NSString* key in _replacements) {
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:key
options:0
error:nil];
NSDictionary* attributes = _replacements[key];
[regex enumerateMatchesInString:[_storingText string]
options:0
range:searchRange
usingBlock:^(NSTextCheckingResult *match,
NSMatchingFlags flags,
BOOL *stop){
// apply the style
NSRange matchRange = [match rangeAtIndex:1];
[self addAttributes:attributes range:matchRange];
// reset the style to the original
if (NSMaxRange(matchRange)+1 < self.length) {
[self addAttributes:normalAttrs
range:NSMakeRange(NSMaxRange(matchRange)+1, 1)];
}
}];
}
}
這是iOS7新增的用于設置文字屬性和進行排版的三個主要的類,本文主要說了NSTextStorage類。另外兩個類我會繼續(xù)跟進學習。
對比起iOS6及以前,處理文字的排版布局和重置屬性變得更加簡便。
Snell Roundhand是一種我覺得非常華麗的字體,非常喜歡。最后上張程序運行結果的圖: