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. [转]Porting to Oracle with Entity Framework NLog

    本文转自:http://izzydev.net/.net/oracle/entityframework/2017/02/01/Porting-to-Oracle-with-Entity-Framewo ...

  2. js控制div样式显示与隐藏,JS通过点击超链接右边(指定位置)显示一个图标

    原文出自:https://blog.csdn.net/seesun2012 javascript基础篇,老土的方法解决js控制div样式,便于新手理解,粗暴的不能再粗暴,如果你是高手,请忽略! 思路: ...

  3. 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(二)——使用蓝图功能进行模块化

    使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(一)——创建应用 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(二)——使用蓝图功能进行模块化 使用 Flask 框架写用 ...

  4. Timer控件

    Timer控件是定期引发事件的控件,时间间隔的长度由interval属性定义,其值以毫秒为单位吗,若启用了该组件,则每个事件间隔引发一个Tick事件,Timer组件的主要方法包括start和stop, ...

  5. layer子窗口与父窗口传值

    layer作为优秀的jquery框架,可以用作弹出组件.日历.分页等,而且实现简单,只有几十k的大小. 此处给出弹出窗口时子窗口与父窗口的传值.js和css这里不展示引入(以下给出目录结构的图片),仅 ...

  6. 十九、异步任务编排CompletableFuture

    一.简介 并发编程中我们经常创建异步线程来执行任务.但是,当异步任务之间存在依赖关系时,使得我们开发过程变得更加复杂.比如: 1.线程2依赖于线程1的执行结果 2.线程3依赖于线程1和线程2执行结果的 ...

  7. PHP高级工程师面试 - 笔试题

    Part1:HTTP协议 1.状态码的含义 1xx(临时响应) 表示临时响应并需要请求者继续执行操作的状态代码. 代码 说明 100 (继续) 请求者应当继续提出请求. 服务器返回此代码表示已收到请求 ...

  8. Java Swing实战(四)按钮组件JButton及其事件监听

    接下来为面板添加保存按钮,并为按钮绑定事件监听. /** * @author: lishuai * @date: 2018/11/26 13:51 */ public class WeimingSyn ...

  9. SSM框架文件远程服务器下载

    1.首先你必须要建立连接 获取URL的输入流 2.之后就是文件读取和写入了 3.还有就是设置响应头,响应码等 代码 @RequestMapping("/fileDownLoad") ...

  10. hdu 3474 Necklace 单调队列

    Necklace Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Su ...