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可用的第三方库有:MMMarkdownGHMarkdownParser。其中GHMarkdownParserGitHub Flavored Markdown支持比较好。

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



但是实测发现,这个方法的计算速度非常慢!google了一下,貌似因为这个方法渲染的过程是需要初始化ScriptCore的,每次渲染都要初始化一个ScriptCore肯定是不能忍的。
第三方库的替代方案:DTCoreTextNSAttributedString-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丢给UILabelattributedString渲染的时候,在options里设置的链接的颜色是无效的,貌似UILabel对链接的渲染颜色是不可改的。继续寻找替代方案:用第三方的TTTAttributedLabel代替UILabel。TTTAttributedLabelUILabel的派生类,为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. 解读MySQL的慢日志

    完整的慢日志格式一般如下: # Time: :: # User@Host: db_user[db_database] @ localhost [] # Query_time: Rows_examine ...

  2. JavaScript ES6 promiss的理解。

    本着互联网的分享精神,我将我对promise的理解分享给大家. JavaScript ES6的promise方法主要应用在处理异步函数返回的结果,注意他不是将异步函数转换为同步函数,而是等异步函数有结 ...

  3. [PHP] 试题系统研究

    考试科目: 添加考试科目,填写科目名称,选择科目题型(复选框/单选题,多选题,判断题,问答题,填空题) 添加科目章节,填写章节名称,添加章节知识点,填写知识点以英文逗号分隔,直接插入多条记录 开通考场 ...

  4. g2o error2

    ./pose_estimation_3d2d: error while loading shared libraries: libg2o_core.so: cannot open shared obj ...

  5. 撩课-Web大前端每天5道面试题-Day4

    1. 如何实现瀑布流? 瀑布流布局的原理: ) 瀑布流布局要求要进行布置的元素等宽, 然后计算元素的宽度, 与浏览器宽度之比,得到需要布置的列数; ) 创建一个数组,长度为列数, 里面的值为已布置元素 ...

  6. IniHelper——INI操作辅助类

    使用INI配置文件,简单便捷. 该辅助工具类为C#操作INI文件的辅助类,源码在某位师傅的基础上完善的来,因为忘记最初的来源了,因此不能提及引用,在此深感遗憾,并对贡献者表示感谢. using Sys ...

  7. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  8. sql 模糊搜素拼接

    if($irb_order!=''){ $condition .= " AND d.irb_order like '%".$irb_order."%'"; } ...

  9. async await的使用

    var sleep = function (time) { return new Promise(function (resolve, reject) { setTimeout(function () ...

  10. css3怎么分清伪类和伪元素

    伪类用于向某些选择器添加特殊的效果. 伪元素用于将特殊的效果添加到某些选择器. 伪类有::first-child ,:link:,vistited,:hover,:active,:focus,:lan ...