运用TextKit实现Markdown语法

先详细说一下 加粗 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-(void)processEditing
{
[self performReplacementsForRange:[self editedRange]];
[super processEditing];
}

- (void)performReplacementsForRange:(NSRange)changedRange
{
NSRange extendedRange = NSUnionRange(changedRange, [[_backingStore string]
lineRangeForRange:NSMakeRange(changedRange.location, 0)]);
extendedRange = NSUnionRange(changedRange, [[_backingStore string]
lineRangeForRange:NSMakeRange(NSMaxRange(changedRange), 0)]);
[self applyStylesToRange:extendedRange];
}

上面的 Code 将检查配合我们的粗体格式图案的区间。changedRange 通常表示一个字符,lineRangeForRange 扩展范围文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (void)applyStylesToRange:(NSRange)searchRange
{
// 1. 创建字体样式
UIFontDescriptor* fontDescriptor = [UIFontDescriptor
preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
UIFontDescriptor* boldFontDescriptor = [fontDescriptor
fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
UIFont* boldFont = [UIFont fontWithDescriptor:boldFontDescriptor size: 0.0];
UIFont* normalFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

// 2. 星号匹配
NSString* regexStr = @"(\\*\\w+(\\s\\w+)*\\*)\\s";
NSRegularExpression* regex = [NSRegularExpression
regularExpressionWithPattern:regexStr
options:0
error:nil];

NSDictionary* boldAttributes = @{ NSFontAttributeName : boldFont };
NSDictionary* normalAttributes = @{ NSFontAttributeName : normalFont };

// 3. 遍历每个匹配,使文本加粗
[regex enumerateMatchesInString:[_backingStore string]
options:0
range:searchRange
usingBlock:^(NSTextCheckingResult *match,
NSMatchingFlags flags,
BOOL *stop){

NSRange matchRange = [match rangeAtIndex:1];
[self addAttributes:boldAttributes range:matchRange];

// 4. reset the style to the original
if (NSMaxRange(matchRange)+1 < self.length) {
[self addAttributes:normalAttributes
range:NSMakeRange(NSMaxRange(matchRange)+1, 1)];
}
}];
}

上面的代码执行以下操作:

  • 创建一个文本字体描述。字体描述符帮你避免使用硬编码的字体字符串设置字体类型和风格。
  • 创建一个正则表达式(或正则表达式),其定位由星号包围的任何文本。
  • 枚举的正则表达式匹配每一个粗体属性。
  • 重置字体样式,跟踪匹配的字符串为星号字符的文本样式。

进一步增加样式

应用样式分隔文本的基本原理相当简单:使用正则表达式查找和替换 applyStylesToRange 分隔的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (void) createHighlightPatterns {

UIFontDescriptor *scriptFontDescriptor =
[UIFontDescriptor fontDescriptorWithFontAttributes:
@{UIFontDescriptorFamilyAttribute: @"Zapfino"}];

// 1.字体风格描述
UIFontDescriptor* bodyFontDescriptor = [UIFontDescriptor
preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
NSNumber* bodyFontSize = bodyFontDescriptor.
fontAttributes[UIFontDescriptorSizeAttribute];
UIFont* scriptFont = [UIFont
fontWithDescriptor:scriptFontDescriptor size:[bodyFontSize floatValue]];

// 2.创建属性
NSDictionary* boldAttributes = [self
createAttributesForFontStyle:UIFontTextStyleBody
withTrait:UIFontDescriptorTraitBold];
NSDictionary* italicAttributes = [self
createAttributesForFontStyle:UIFontTextStyleBody
withTrait:UIFontDescriptorTraitItalic];
NSDictionary* strikeThroughAttributes = @{ NSStrikethroughStyleAttributeName : @1};
NSDictionary* scriptAttributes = @{ NSFontAttributeName : scriptFont};
NSDictionary* redTextAttributes =
@{ NSForegroundColorAttributeName : [UIColor redColor]};

// 3.正则表达式
_replacements = @{
@"(\\*\\w+(\\s\\w+)*\\*)\\s" : boldAttributes,
@"(_\\w+(\\s\\w+)*_)\\s" : italicAttributes,
@"([0-9]+\\.)\\s" : boldAttributes,
@"(-\\w+(\\s\\w+)*-)\\s" : strikeThroughAttributes,
@"(~\\w+(\\s\\w+)*~)\\s" : scriptAttributes,
@"\\s([A-Z]{2,})\\s" : redTextAttributes};
}

就拿上面的匹配由星号包围的话实现了第一个正则表达式:
(\\*\\w+(\\s\\w+)*\\*)\\s

双斜杠是正则表达式的特殊字符,即反斜杠。就是这样:
(\*\w+(\s\w+)*\*)\s

现在,解构一步正则表达式的步骤:

  • (\* -匹配星号
  • \w+ -后面跟着一个或多个 word
  • (\s\w+* -后跟零个或多个基团的空间后跟“字”的字符
  • \*) -后跟星号
  • \s -一个空格结束。

调用createHighlightPatterns:

1
2
3
4
5
6
7
8
- (id)init
{
if (self = [super init]) {
_backingStore = [NSMutableAttributedString new];
[self createHighlightPatterns];
}
return self;
}

下面的方法适用于所提供的字体样式的正文字体。它提供了一个零大小fontWithDescriptor:size:迫使UIFont返回的尺寸相匹配的用户的当前字体大小偏好。

1
2
3
4
5
6
7
8
9
10
11
- (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 };
}

替换现有applyStylesToRange方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void)applyStylesToRange:(NSRange)searchRange
{
NSDictionary* normalAttrs = @{NSFontAttributeName:
[UIFont preferredFontForTextStyle:UIFontTextStyleBody]};

// 迭代每次更换
for (NSString* key in _replacements) {
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:key
options:0
error:nil];

NSDictionary* attributes = _replacements[key];

[regex enumerateMatchesInString:[_backingStore string]
options:0
range:searchRange
usingBlock:^(NSTextCheckingResult *match,
NSMatchingFlags flags,
BOOL *stop){
// 样式
NSRange matchRange = [match rangeAtIndex:1];
[self addAttributes:attributes range:matchRange];

// 重新设置到最初的样式
if (NSMaxRange(matchRange)+1 < self.length) {
[self addAttributes:normalAttrs
range:NSMakeRange(NSMaxRange(matchRange)+1, 1)];
}
}];
}
}

展示效果如下图:


以上