您的位置:首页 > 移动开发 > IOS开发

iOS富文本(三)深入使用Text Kit

2015-12-02 23:23 691 查看
上一篇中介绍了
Text Kit
的三种基本组件的关系并且简单的实现了怎么使用这三种基本组件,本片将深入的去使用这三种基本组件。

NSTextStorage

NSTextStorage
NSMutableAttributedString
的子类,根据
苹果官方文档
描述是
semiconcrete
子类,因为
NSTextStorage
没有实现
NSMutableAttributedString
中的方法,所以说
NSTextStorage
应该是
NSMutableAttributedString
的类簇。

所要我们深入使用
NSTextStorage
不仅要继承
NSTextStorage
类还要实现
NSMutableAttributedString
的下面方法

- (NSString *)string
- (void)replaceCharactersInRange:(NSRange)range    withString:(NSString *)str
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range


因为这些方法实际上
NSTextStorage
并没有实现然而我们断然不知道
NSMutableAttributedString
是如何实现这些方法,所以我们继承
NSTextStorage
并实现这些方法最简单的莫过于在
NSTextStorage
类中实例化一个
NSMutableAttributedString
对象然后调用
NSMutableAttributedString
对象的这些方法来实现
NSTextStorage
类中的这些方法

@interface LSYTextStorage : NSTextStorage
@property (nonatomic,strong) NSMutableAttributedString *attributedString;
@end


继承
NSTextStorage
后都会实现下面的代码,如果要做一些特殊的处理知道在下面的代码里添加就可以了

#import "LSYTextStorage.h"
@interface LSYTextStorage ()
@property (nonatomic,strong) NSMutableAttributedString *attributedString;
@end
@implementation LSYTextStorage
- (instancetype)init
{
self = [super init];
if (self) {
_attributedString = [[NSMutableAttributedString alloc] init];
}
return self;
}
-(NSString *)string{
return [_attributedString string];
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)range
{
return [_attributedString attributesAtIndex:location effectiveRange:range];
}
-(void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
{
[self beginEditing];
[_attributedString replaceCharactersInRange:range withString:str];
[self edited:NSTextStorageEditedAttributes|NSTextStorageEditedCharacters range:range changeInLength:str.length-range.length];
[self endEditing];
}
-(void)setAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range
{
[self beginEditing];
[_attributedString setAttributes:attrs range:range];
[self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
[self endEditing];
}
@end


上面实现的方法里代码都加上了
beginEditing
edited:range:changeInLength:
endEditing
的方法,这样做主要是通知它的 Layout Manager 发生了变化来计时调整布局

根据上面提供的模版添加特殊处理的代码

#import "LSYTextStorage.h"
@interface LSYTextStorage ()
@property (nonatomic,strong) NSMutableAttributedString *attributedString;
@end
@implementation LSYTextStorage
- (instancetype)init
{
self = [super init];
if (self) {
_attributedString = [[NSMutableAttributedString alloc] init];
}
return self;
}
-(NSString *)string{
return [_attributedString string];
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)range
{
return [_attributedString attributesAtIndex:location effectiveRange:range];
}
-(void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
{
[self beginEditing];
[_attributedString replaceCharactersInRange:range withString:str];
[self edited:NSTextStorageEditedAttributes|NSTextStorageEditedCharacters range:range changeInLength:str.length-range.length];
[self endEditing];
}
-(void)setAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range
{
[self beginEditing];
[_attributedString setAttributes:attrs range:range];
[self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
[self endEditing];
}
-(void)processEditing
{

NSRange lineRange = NSUnionRange(self.editedRange, [self.string lineRangeForRange:self.editedRange]);  //正在编辑的整个段落范围
[self.attributedString.string enumerateSubstringsInRange:lineRange options:NSStringEnumerationByWords usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
if ([substring isEqualToString:@"GGGHub"]) {
[self setAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]} range:substringRange];  //当出现GGGHub单词时字体变红
}
else{
[self setAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor]} range:substringRange]; //默认字体是黑色
}
}];
[super processEditing];
}
@end


每次编辑都会调用
-(void)processEditing
的方法,然后遍历整段修改的文字当出现
GGGHub
的单词时显示红色字体。

#import "ViewController.h"
#import "LSYTextStorage.h"
@interface ViewController ()
{
LSYTextStorage *textStroage;  //需要声明为全局变量,否则出了作用域后就释放掉了
}
@property (weak, nonatomic) IBOutlet UITextView *textView;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = _textView.text;
textStroage = [[LSYTextStorage alloc] init];
[textStroage replaceCharactersInRange:NSMakeRange(0, 0) withString:str];
[textStroage addLayoutManager:self.textView.layoutManager];
//替换textView的textStroage属性
}


只要文本中出现指定的关键字字体就会变红,输入指定的关键字字体也会变红。

实现效果



代码在github
NSTextStorage
Tag下载

NSLayoutManager

布局管理器主要用来绘制字体的。
NSTextStorage
虽然能够改变字体的样式但是更改不了字体绘制的方式。我们可以继承
NSLayoutManager
来更改字体绘制。对于某些特定的字段可能不需要显示比如加密文本,或者用图片替换这些字段,或者給这些字段添加一些背景,这时只要重写
NSLayoutManager
中的某些方法可以很简单的实现。

更改字体绘制与字体背景颜色只需要重写下面的两个方法

- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin;
//绘制字形背景
- (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin;
//绘制字形


下面例子会实现这样的功能,只要文本中有纯数字或者输入纯数字那么这段数字不显示出来,用黑色的遮罩挡住。

首先在
LSYTextStorage.m
文件中更改
processEditing
函数

-(void)processEditing
{

NSRange lineRange = NSUnionRange(self.editedRange, [self.string lineRangeForRange:self.editedRange]);
NSString *regexNumber = @"^-?[0-9]\\d*$";
NSPredicate *predicateNumber = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regexNumber];
//正则表达式,判断是否为纯数字
[self.attributedString.string enumerateSubstringsInRange:lineRange options:NSStringEnumerationByWords usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
NSLog(@"%@",substring);
if ([substring isEqualToString:@"GGGHub"]) {
[self setAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]} range:substringRange];
}
/**
*  如果是纯数字給这段字符串添加LSYSecretAttribute属性为了绘制字形时查找
*/
else if ([predicateNumber evaluateWithObject:substring]){
[self setAttributes:@{@"LSYSecretAttribute":@"secretAttribute"} range:substringRange];
}
else{
[self setAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor]} range:substringRange];
}
}];
[super processEditing];

}


上面的代码主要为纯数字的字符串添加一个
LSYSecretAttribute
的属性,当
NSLayoutManager
开始绘制字形时可以方便找到这段字符串然后在这段字符串的范围绘制黑色遮罩

再重写
NSLayoutManager
的方法前



下面重写
NSLayoutManager
drawGlyphsForGlyphRange
方法

-(void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin
{

NSRange range = [self characterRangeForGlyphRange:glyphsToShow
actualGlyphRange:NULL];
[self.textStorage enumerateAttribute:@"LSYSecretAttribute" inRange:range options:0 usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {
//找到在LSYTextStorage中自定的LSYSecretAttribute属性
if ([value isEqualToString:@"secretAttribute"]) {
NSRange glyphRange = [self glyphRangeForCharacterRange:range
actualCharacterRange:NULL];
NSTextContainer *
container = [self textContainerForGlyphAtIndex:glyphRange.location
effectiveRange:NULL];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);   //保存当前的绘图配置信息
CGContextTranslateCTM(context, origin.x, origin.y); //转换初始坐标系到绘制字形的位置
[[UIColor blackColor] setFill];
CGRect rect = [self boundingRectForGlyphRange:glyphRange inTextContainer:container];
[self drawSecret:rect]; //开始绘制
CGContextRestoreGState(context); //恢复绘图配置信息
}
else
{
[super drawGlyphsForGlyphRange:range atPoint:origin];
}

}];

}


实现效果



这种遮罩是动态的,只要输入是纯数字那么
NSLayoutManager
的对象就不会对其进行绘制,而用黑色的遮罩挡住。

代码在github
NSLayoutManager
Tag下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: