iOS開發中的Markdown渲染
BearyChat的消息是全面支持Markdown語法的,所以在開發BearyChat的iOS客戶端的時候需要處理Markdown的渲染。
主要是兩套實現方案:
- 直接將Markdown文本轉換成
NSAttributedString
。
- 先將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
等控件渲染。
最終的實現代碼就比較簡單了:
|
#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渲染的相關問題。
|
#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/