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. API ,批量添加

    添加引用:cors using   system.web.http.cors API添加这句话:       [EnableCors("*", "*", &qu ...

  2. 一:XML知识整理

    一:xml 1. xml简介 a) xml, eXtensible Markup Language, 可扩展标记语言.是一种标记语言. b) xml 是一种非常灵活的语言, 没有固定的标签, 所有的标 ...

  3. Spring学习笔记:spring与mybatis四种整合方法

    1.采用数据映射器(MapperFactoryBean)的方式,不用写mybatis映射文件,采用注解方式提供相应的sql语句和输入参数.  (1)Spring配置文件: <!-- 引入jdbc ...

  4. groovy对枚举的支持

    /** * Created by Jxy on 2019/1/3 15:42 * groovy对枚举的支持 */ enum CoffeeSize{ SHORT,SMALL,BIG,MUG } def ...

  5. LinkedList实现队列存储结构

    package com.tercher.demo; import java.util.LinkedList; public class Queue { //用LinkedList 实现队列的数据存储结 ...

  6. csharp: read excel using Aspose.Cells

    /// <summary> /// /// </summary> /// <param name="strFileName"></para ...

  7. Luogu3307:[SDOI2013]项链

    传送门 求每个珠子的方案数 即有序的求三元组 \((x,y,z),x,y,z\le a\) 满足 \(gcd(x,y,z)=1\) 设 \(G_i\) 表示 \(i\) 个小于等于 \(a\) 的有序 ...

  8. reac——父组件向子组件传递值,子组件何时能同步获得父组件改变后的值

    //这里是父组件的代码:export default class HeaderCom_son extends React.Component { constructor(props) { super( ...

  9. 微信小程序-view组件

    <view class="section"> <view class="section__title">flex-direction: ...

  10. Angular面试题三

    十六.一个 angular 应用应当如何良好地分层? 目录结构的划分 1.对于小型项目,可以按照文件类型组织,比如: css Js  {  controllers  models  services ...