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

NSTextStorage

NSTextStorageNSMutableAttributedString的子类,根据苹果官方文档描述是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

上面实现的方法里代码都加上了beginEditingedited: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的方法前



下面重写NSLayoutManagerdrawGlyphsForGlyphRange方法

-(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下载


iOS富文本(三)深入使用Text Kit的更多相关文章

  1. iOS富文本组件的实现—DTCoreText源码解析 数据篇

    本文转载 http://blog.cnbang.net/tech/2630/ DTCoreText是个开源的iOS富文本组件,它可以解析HTML与CSS最终用CoreText绘制出来,通常用于在一些需 ...

  2. iOS富文本(二)初识Text Kit

    概述 Text Kit 是建立在Core Text上的文本布局系统,虽然没有Core Text那么强大的文本处理功能,但是对于大多数常见的文本布局用Text Kit能够很简单的实现,而不是用Core ...

  3. iOS富文本

    背景:前些天突然想做一个笔记本功能,一开始,觉得挺简单的呀,一个UITextView,网络缓存也不干了,直接本地NSUserDefault存储,然后完事了,美工,弄几张好看的图片,加几个动画,也就这样 ...

  4. iOS - 富文本

    iOS--NSAttributedString超全属性详解及应用(富文本.图文混排)   ios项目中经常需要显示一些带有特殊样式的文本,比如说带有下划线.删除线.斜体.空心字体.背景色.阴影以及图文 ...

  5. iOS富文本(一)属性化字符串

    概述 iOS一些复杂的文本布局一般都是由底层的Core Text来实现的,直到iOS7苹果发布了Text Kit框架,Text Kit能够很简单实现一些复杂的文本样式以及布局,而Text Kit富文本 ...

  6. OS开发小记:iOS富文本框架DTCoreText在UITableView上的使用

    要在页面中显示自己的布局,比如文字的字体和颜色.图文并排的样式,我们要用iOS SDK的原生UI在app本地搭建,如果一个页面需要在服务器端获取数据的话,我们也要在本地搭建好固定的布局,解析服务器传回 ...

  7. iOS 富文本类库RTLabel

      本文转载至 http://blog.csdn.net/duxinfeng2010/article/details/9004749  本节关于RTLable基本介绍,原文来自 https://git ...

  8. iOS - 富文本AttributedString

    最近项目中用到了图文混排,所以就研究了一下iOS中的富文本,打算把研究的结果分享一下,也是对自己学习的一个总结. 在iOS中或者Mac OS X中怎样才能将一个字符串绘制到屏幕上呢?         ...

  9. iOS - 富文本直接设置文字的字体大小和颜色

    富文本效果图: 富文本实现代码: UILabel *orderSureLabel = [Common lableFrame:CGRectZero title:] textColor:[UIColor ...

随机推荐

  1. Sqli-labs less 56

    Less-56 与less54.55形式是一致的,我们关注sql语句, $sql="SELECT * FROM security.users WHERE id=('$id') LIMIT 0 ...

  2. NumPy的array

    1.numpy包中的array数组,用于弥补列表可以存储任意的数据类型的不足,因为有时候我们需要存储某种类型的数据在数组中,这才是数组的本来内涵.我们通过向numpy.array()函数中传递pyth ...

  3. javascript笔记 面向对象

    Javascript是一种面向对象的弱语言,既然有面向对象,就有继承 继承: 1.call函数和apply函数:区别在于它们参数上的不同,固定参数的用call,可变参数的用apply.换句话说,就是a ...

  4. 一个Form中2个按钮,PHP后台如何判断提交的是哪一个按钮

    方法一: <div class="container theme-showcase" role="main"> <form class=&qu ...

  5. HDU 1392 Surround the Trees (Graham求凸包周长)

    题目链接 题意 : 让你找出最小的凸包周长 . 思路 : 用Graham求出凸包,然后对每条边求长即可. Graham详解 #include <stdio.h> #include < ...

  6. POJ 3468 A Simple Problem with Integers(线段树)

    题目链接 题意 : 给你n个数,进行两种操作,第一种是将a到b上所有元素都加上c,第二种是查询a到b上所有元素之和输出. 思路 : 线段树,以前写过博客,但是现在在重刷,风格改变,,所以重新写一篇.. ...

  7. hdu 4345 Permutation 记忆化搜索

    思路:实际上求的是和小于等于n的质数的种类数!!! 代码如下: #include<iostream> #include<stdio.h> #include<algorit ...

  8. BZOJ 1296: [SCOI2009]粉刷匠 分组DP

    1296: [SCOI2009]粉刷匠 Description windy有 N 条木板需要被粉刷. 每条木板被分为 M 个格子. 每个格子要被刷成红色或蓝色. windy每次粉刷,只能选择一条木板上 ...

  9. C语言:指针的几种形式二

    一.const指针 1.const int* p和int const* p:两者意义是相同的.指向的内容是只读数据,不可以q改变:但是指向的地址可以改变. 2.int* const p:必须先对指针初 ...

  10. python流程控制语句 for循环 - 1

    Python中for循环语句是通过遍历某一序列对象(元组.列表.字典等)来构建循环,循环结束的条件就是遍历对象完成. 语法形式: for <循环变量> in <遍历对象>: & ...