iOS开发中的Markdown渲染

BearyChat的消息是全面支持Markdown语法的,所以在开发BearyChat的iOS客户端的时候需要处理Markdown的渲染。

主要是两套实现方案:

  1. 直接将Markdown文本转换成NSAttributedString。
  2. 先将Markdown文本转换成HTML,再将HTML转换成NSAttributedString。

方案1可用的第三方库有:AttributedMarkdown,这个库是基于C语言的peg-markdown的封装,经过试验发现对GitHub Flavored Markdown支持的不太好。

方案2可用的第三方库相对多一些:

将Markdown文本转换成HTML可用的第三方库有:MMMarkdown,GHMarkdownParser。其中GHMarkdownParser对GitHub Flavored Markdown支持比较好。

将HTML转换成NSAttributedString,在iOS 7之后UIKit为NSAttributedString增加了initWithData:options:documentAttributes:error:方法可以直接转换:



但是实测发现,这个方法的计算速度非常慢!google了一下,貌似因为这个方法渲染的过程是需要初始化ScriptCore的,每次渲染都要初始化一个ScriptCore肯定是不能忍的。
第三方库的替代方案:DTCoreText,NSAttributedString-DDHTML。二者之中,DTCoreText是一个比较成熟的第三方库,对样式的控制也比较灵活。

所以最终选择的方案是:首先用GHMarkdownParser讲Markdown转换成HTML,之后再用DTCoreText讲HTML转换成NSAttributedString最后交给UILabel等控件渲染。
最终的实现代码就比较简单了:

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
39
40
41
42
43
44
45
46
47
48
49
#import <GHMarkdownParser/GHMarkdownParser.h>
#import <DTCoreText/DTCoreText.h> @interface MarkdownParser
@property GHMarkdownParser *htmlParser;
@property (nonatomic, copy) NSDictionary *DTCoreText_options;
- (NSAttributedString *)DTCoreText_attributedStringFromMarkdown:(NSString *)text;
@end @implementation MarkdownParser - (instancetype)init {
self = [super init];
if (self) {
_htmlParser = [[GHMarkdownParser alloc] init];
_htmlParser.options = kGHMarkdownAutoLink;
_htmlParser.githubFlavored = YES;
}
return self;
} - (NSString *)htmlFromMarkdown:(NSString *)text {
return [self.htmlParser HTMLStringFromMarkdownString:text];
} - (NSAttributedString *)attributedStringFromMarkdown:(NSString *)text {
NSString *html = [self htmlFromMarkdown:text];
NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding];
NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithHTMLData:data options:self.DTCoreText_options documentAttributes:nil];
return attributed;
} - (NSDictionary *)DTCoreText_options {
if (!_DTCoreText_options) {
_DTCoreText_options = @{
DTUseiOS6Attributes:@YES,
DTIgnoreInlineStylesOption:@YES,
DTDefaultLinkDecoration:@NO,
DTDefaultLinkColor:[UIColor blueColor],
DTLinkHighlightColorAttribute:[UIColor redColor],
DTDefaultFontSize:@15,
DTDefaultFontFamily:@"Helvetica Neue",
DTDefaultFontName:@"HelveticaNeue-Light"
};
}
return _DTCoreText_options;
} @end

到这里,绝大部分的问题都解决了,还有一点点小问题:把解析得到的NSAttributedString丢给UILabel的attributedString渲染的时候,在options里设置的链接的颜色是无效的,貌似UILabel对链接的渲染颜色是不可改的。继续寻找替代方案:用第三方的TTTAttributedLabel代替UILabel。TTTAttributedLabel是UILabel的派生类,为UILabel提供了更多对NSAttributedString的控制。通过为TTTAttributedLabel设置超链接的样式最终解决了Markdown渲染的相关问题。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#import <UIKit/UIKit.h>
#import <TTTAttributedLabel/TTTAttributedLabel.h> @interface MarkdownLabel : TTTAttributedLabel <TTTAttributedLabelDelegate>
- (void)setDisplayedAttributedString:(id)text;
@end @implementation MarkdownLabel - (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self commonConfig];
}
return self;
} - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self commonConfig];
}
return self;
} - (void)commonConfig {
self.delegate = self;
NSDictionary *linkAttributes = @{
(id)kCTForegroundColorAttributeName:[UIColor blueColor],
NSUnderlineStyleAttributeName:@(kCTUnderlineStyleNone),
NSFontAttributeName:[UIFont fontWithName:@"HelveticaNeue-Light" size:15]
};
self.linkAttributes = linkAttributes;
self.enabledTextCheckingTypes = 0;
} - (void)setDisplayedAttributedString:(id)text {
NSMutableArray *linksAndRange = [@[] mutableCopy];
[self setText:[text string] afterInheritingLabelAttributesAndConfiguringWithBlock:^NSMutableAttributedString *(NSMutableAttributedString *mutableAttributedString) {
[text enumerateAttributesInRange:NSMakeRange(0, [text length])
options:0
usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
if (attrs[NSLinkAttributeName]) {
[linksAndRange addObject:@[attrs[NSLinkAttributeName], [NSValue valueWithRange:range]]];
} else {
[mutableAttributedString addAttributes:attrs range:range];
}
}];
return mutableAttributedString;
}]; for (NSArray *pair in linksAndRange) {
[self addLinkToURL:pair[0] withRange:[pair[1] rangeValue]];
}
} @end

http://nightfade.github.io/2015/06/26/ios-markdown-rendering/

iOS开发中的Markdown渲染的更多相关文章

  1. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  2. iOS开发中静态库之".framework静态库"的制作及使用篇

    iOS开发中静态库之".framework静态库"的制作及使用篇 .framework静态库支持OC和swift .a静态库如何制作可参照上一篇: iOS开发中静态库之" ...

  3. iOS开发中静态库制作 之.a静态库制作及使用篇

    iOS开发中静态库之".a静态库"的制作及使用篇 一.库的简介 1.什么是库? 库是程序代码的集合,是共享程序代码的一种方式 2.库的类型? 根据源代码的公开情况,库可以分为2种类 ...

  4. ios开发中的小技巧

    在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新. UITableView的Group样式下顶部空白处理 //分组列表头部空白处理 UIView *view = [[UIViewal ...

  5. IOS 开发中 Whose view is not in the window hierarchy 错误的解决办法

    在 IOS 开发当中经常碰到 whose view is not in the window hierarchy 的错误,该错误简单的说,是由于 "ViewController" ...

  6. [转]iOS开发中的火星坐标系及各种坐标系转换算法

     iOS开发中的火星坐标系及各种坐标系转换算法 源:https://my.oschina.net/u/2607703/blog/619183   其原理是这样的:保密局开发了一个系统,能将实际的坐标转 ...

  7. iOS开发中常见问题集锦

    在iOS开发中,会出现各种各样的问题.今天,就把这些常见的问题以及各位大牛的解决方案汇总下,方便以后查阅: 常见错误: 1. linker command failed with exit code ...

  8. iOS开发中获取WiFi相关信息

    iOS 开发中难免会遇到很多与网络方面的判断,这里做个汇总,大多可能是与WiFi相关的. 1.Ping域名.Ping某IP 有 时候可能会遇到ping 某个域名或者ip通不通,再做下一步操作.这里的p ...

  9. iOS开发中设置UITextField的占位文字的颜色,和光标的颜色

    在iOS开发中,对于很多初学者而言,很有可能碰到需要修改UITextField的占位文字的颜色,以及当UITextField成为第一响应者后光标的颜色,那么下面小编就介绍一下修改占位文字和光标的颜色. ...

随机推荐

  1. react-native-image-picker在IOS上总是返回”Can’t find variable:response”的错误?

    环境: react-native: 0.41.2 react-native-image-picker: 0.26.2 xcode 8.2.1 iphone 6 根据官方教程(https://githu ...

  2. Angular2-编写一个简易的组件

    Angular2组件可以这么理解:编写一个类,然后在类的上面用组件装饰器装饰一下,这个类就成组件了. 所以编写组件分两步:1)编写类:2)编写装饰器 1)编写类: export class Simpl ...

  3. mysql:名次排名 (并列与不并列)

    http://www.cnblogs.com/zengguowang/p/5541431.html sql语句查询排名 思路:有点类似循环里面的自增一样,设置一个变量并赋予初始值,循环一次自增加1,从 ...

  4. 几个常用的内存、CPU飙高 分析工具

    Process Hacker.Windbg.vs2017 调试托管内存.Microsoft.Samples.Debugging.ants memory profiler.ants performanc ...

  5. 多线程-synchronized锁

    package 多线程.synchronized锁; /*. * * * * */ public class Sale implements Runnable { ; @Override public ...

  6. distinct 用法

    参考 1.作用于单列 2.作用于多列 3.COUNT统计 4.distinct必须放在开头 5.其他 在表中,可能会包含重复值.这并不成问题,不过,有时您也许希望仅仅列出不同(distinct)的值. ...

  7. tensorboard实现tensorflow可视化

    1.工程目录 2.data.input_data.py的导入 在tensorflow更新之后可以进行直接的input_data的导入 # from tensorflow.examples.tutori ...

  8. EntityFramework(1)

    EntityFramework核心是EDM实体数据模型,该模型由三部分组成. (1) 概念模型,由概念架构定义语言文件(.csdl)来定义. (2) 映射,由映射规范语言文件(.msl)定义. (3) ...

  9. Unity3D-NGUI动态加载图片

    NGUI提供了很方便的UIAtlas,其主要作用是改进DrawCall,把众多图片整合在一张贴图上,由于UNITY3D简单易用的好处,所以只是用原生的GUI很容易忽视DrawCall的问题,所以NGU ...

  10. 个人项目-wordcount

    源代码上传到github的网址为:https://github.com/fancy-dawning/hello-world.git. wc.exe是一个常见的工具,它能统计文本文件的字符数,单词数和行 ...