Sources 开发日记五(代码展示页面)

转自我自己的 blog:Sources 开发日记五(代码展示页面)

好久没写 blog 了,上一篇距离现在居然有一个半月了,而上一篇关于 Sources 的开发日记竟然相隔快两个月了。得赶快把 v1.0 的最后两篇写完,然后全身心进入下一个版本的开发和记录。

这一篇讲的部分应该是整个 App 最出彩的地方(颜色很多),展示代码,而且是带有语法高亮的展示。

语法高亮的实现方案

如何给代码在 iOS 上进行语法着色显示,这是这个 app 开发前我能想到的最大的一个难题。我看过几篇关于利用 Core Text 来给文本中不同的部分来设置不同颜色的 blog,但是前提是要知道哪些部分是哪些类型。关键字,字符串,数字,自建类型,注释……要把这些东西从一个代码文件中分析出来,实现一个语法分析器,对于现在的我来说简直不可能。

于是在 Google 里搜索「code highlight」,找到了我的解决方案,https://highlightjs.org。这个网站就是在 Web 端给各种语言(目前是166种)提供语法高亮,而且包含了多种主题(目前是77种)。只需要在网页中使用这个网站提供的 js,并指定代码段的 class 就可以实现梦想中的语法高亮!

所以在 iOS 端如何实现也就确定了,web view。

Syntax-highlighten Code

加载代码

HTML Template

既然是 web view,就需要跟 HTML 打交道了。根据 Github API 下载得到的代码是 plain text,想要语法高亮就要按照 highlightjs 的教程加载 js 到 HTML,所以要自定义一个 HTML 模板用来加载 js 和 代码。

<!DOCTYPE html>
<html lang="en">
<head>
    <title>#title#</title>
    <link rel="stylesheet" href="#theme#.css">
    <meta name='viewport' content='initial-scale=1.0; maximum-scale=2.0;'>
    <script src="highlight.pack.js"></script>
    <script>hljs.initHighlightingOnLoad();</script>

</head>
<body>
<pre><code class="hljs">

#code#

</code></pre>
</body>
</html>

以上代码中的 #title #theme #code 都是 placeholder,在获取代码后用文件名、选择的主题和代码文本来替换。

CodeViewController 中下载代码之前需要获得这个模板的字符串:

private func htmlTemplateString() -> String? {
    let path = NSBundle.mainBundle().URLForResource("template", withExtension: "html")!
    let str: String?
    do {
        str = try String(contentsOfURL: path)
    } catch {
        str = nil
    }
    return str
}

下载代码

我用的是 WKWebView。因为 WKWebView 目前还无法在 Storyboard 中使用,所以只能在 code 中进行设置。


override func viewDidLoad() {
    super.viewDidLoad()

    navigationItem.title = file.name

    let config = WKWebViewConfiguration()
    config.preferences.javaScriptEnabled = true
    webView = WKWebView(frame: view.bounds, configuration: config)
    view.insertSubview(webView, belowSubview: favoriteButton)
    self.theme = NSUserDefaults.standardUserDefaults().stringForKey("default_theme") ?? "default"
    downloadSourceCode()
}

{% endcodeblock%}

语法高亮的主题默认是 default,如果用户有选择其它主题就会存入 User Defaults 中,这样 `CodeViewController` 在每次加载后都会设置为上一次选择的主题。

下载代码的逻辑是这样的:
1. 先获取 HTML 模板和下载API
2. 根据 API 去下载代码
3. 如果 API 对应的文件是代码文件(文本文件)就将代码字符串进行转义、placeholder 替换,用 web view 加载
4. 如果不是代码文件,就提示用户,并直接返回文件列表

{% codeblock Download Code lang:swift %}
private func downloadSourceCode() {
    if let template = htmlTemplateString(), downloadURLString = file.downloadURLString {

        let url = NSURL(string: downloadURLString)!

        EZLoadingActivity.show("loading source", disableUI: true)

        Alamofire.request(.GET, url)
            .responseData(completionHandler: { (response) in
                EZLoadingActivity.hide()
                self.setFavoriteButton()
                if let htmlData = response.data {
                    if let dataString = String(data: htmlData, encoding: NSUTF8StringEncoding) {
                        let escapeString = dataString.stringByReplacingOccurrencesOfString("<", withString: "<")
                            .stringByReplacingOccurrencesOfString(">", withString: ">")
                        self.contentString = escapeString
                        let htmlString = template.stringByReplacingOccurrencesOfString("#code#", withString: escapeString)
                            .stringByReplacingOccurrencesOfString("#title#", withString: self.file.name ?? "")
                            .stringByReplacingOccurrencesOfString("#theme#", withString: self.theme)
                        dispatch_async(dispatch_get_main_queue(), {
                            self.webView.loadHTMLString(htmlString, baseURL: NSBundle.mainBundle().bundleURL)
                        })
                    } else {
                        // not a text file, show alert and then pop back

                        let alertController = UIAlertController(title: "", message: "This file is not a source code file", preferredStyle: .Alert)
                        let alertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { ( _ ) in
                            self.navigationController?.popViewControllerAnimated(true)
                        })
                        alertController.addAction(alertAction)
                        RecentsManager.sharedManager.recents.removeFirst()
                        self.presentViewController(alertController, animated: true, completion: nil)
                    }
                }
            })
    }
}

语法高亮主题列表

其它的代码查看 App 关于语法高亮主题的选择上都只是列出个名称列表而已,通过名称并不能直观的知道该主题是个什么样子的,必须设置后才能一窥究竟。
我在这部分做了一点微小的工作:将主题的整体配色加入到列表中。

Theme list

用户点击一个 theme 后,会返回到 CodeViewController 并重新加载上面的 HTML string,具体代码就不贴了,可以到我的 github 中查看。
弄这个 list 纯是个手工活,因为这个 list 并不是动态加载的,而是我将各个主题的配色都手工提取出来做成了一个数组:

Theme array

要问我为什么要手工搞这个数组,下面就是原因。

Theme CSS

highlightjs 中每一个 theme 其实是一个 css,就是定义了 HTML 中各个 class 的颜色,以 default 为例:

...

.hljs {
  display: block;
  overflow-x: auto;
  padding: 0.5em;
  background: #F0F0F0;
}


/* Base color: saturation 0; */

.hljs,
.hljs-subst {
  color: #444;
}

.hljs-comment {
  color: #888888;
}

.hljs-keyword,
.hljs-attribute,
.hljs-selector-tag,
.hljs-meta-keyword,
.hljs-doctag,
.hljs-name {
  font-weight: bold;
}


/* User color: hue: 0 */

.hljs-type,
.hljs-string,
.hljs-number,
.hljs-selector-id,
.hljs-selector-class,
.hljs-quote,
.hljs-template-tag,
.hljs-deletion {
  color: #880000;
}

.hljs-title,
.hljs-section {
  color: #880000;
  font-weight: bold;
}

.hljs-regexp,
.hljs-symbol,
.hljs-variable,
.hljs-template-variable,
.hljs-link,
.hljs-selector-attr,
.hljs-selector-pseudo {
  color: #BC6060;
}


/* Language color: hue: 90; */

.hljs-literal {
  color: #78A960;
}

.hljs-built_in,
.hljs-bullet,
.hljs-code,
.hljs-addition {
  color: #397300;
}

...

每一个 css 中的 class 都不尽一样,不过大部分还都是定义了四五个颜色,分别对应背景、关键字、自定义类型、字符串、数字和注释等等。因为 css 没有一个统一的格式标准,所以想靠动态读取来获得这些颜色还是要比搞个固定的数组麻烦许多,毕竟这个数组也不是太大,才70多个元素。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,857评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 人闲心寂寂,天高云悠悠。红尘纷扰事,皆随烟波流。曾经多固执,今日觉荒谬。不如归园田,耕读到白头。
    成都独行侠阅读 229评论 2 4
  • 天气转凉了,秋天真的来了,北京的秋天是一年当中最好的季节,可惜的是时间比较短,只有短短的两个月左右。 在北京多年,...
    一箭阅读 324评论 0 3
  • 夜里的样子 好孤单 谁在拼命失眠 走路的样子 好心酸 谁在追赶时光 如果你爱上了ta 不能陪ta流浪 那就把赞美带给ta
    一凡SU阅读 176评论 0 0