轉(zhuǎn)自我自己的 blog:Sources 開發(fā)日記五(代碼展示頁面)
好久沒寫 blog 了,上一篇距離現(xiàn)在居然有一個(gè)半月了外永,而上一篇關(guān)于 Sources 的開發(fā)日記竟然相隔快兩個(gè)月了寂诱。得趕快把 v1.0 的最后兩篇寫完歉摧,然后全身心進(jìn)入下一個(gè)版本的開發(fā)和記錄屈尼。
這一篇講的部分應(yīng)該是整個(gè) App 最出彩的地方(顏色很多),展示代碼躬充,而且是帶有語法高亮的展示逃顶。
語法高亮的實(shí)現(xiàn)方案
如何給代碼在 iOS 上進(jìn)行語法著色顯示雁社,這是這個(gè) app 開發(fā)前我能想到的最大的一個(gè)難題差牛。我看過幾篇關(guān)于利用 Core Text 來給文本中不同的部分來設(shè)置不同顏色的 blog,但是前提是要知道哪些部分是哪些類型司顿。關(guān)鍵字伴找,字符串盈蛮,數(shù)字,自建類型技矮,注釋……要把這些東西從一個(gè)代碼文件中分析出來抖誉,實(shí)現(xiàn)一個(gè)語法分析器,對(duì)于現(xiàn)在的我來說簡直不可能衰倦。
于是在 Google 里搜索「code highlight」袒炉,找到了我的解決方案,https://highlightjs.org樊零。這個(gè)網(wǎng)站就是在 Web 端給各種語言(目前是166種)提供語法高亮我磁,而且包含了多種主題(目前是77種)。只需要在網(wǎng)頁中使用這個(gè)網(wǎng)站提供的 js驻襟,并指定代碼段的 class 就可以實(shí)現(xiàn)夢(mèng)想中的語法高亮夺艰!
所以在 iOS 端如何實(shí)現(xiàn)也就確定了,web view沉衣。
加載代碼
HTML Template
既然是 web view郁副,就需要跟 HTML 打交道了。根據(jù) Github API 下載得到的代碼是 plain text豌习,想要語法高亮就要按照 highlightjs 的教程加載 js 到 HTML存谎,所以要自定義一個(gè) 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
中下載代碼之前需要獲得這個(gè)模板的字符串:
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
巷屿。因?yàn)?WKWebView
目前還無法在 Storyboard 中使用固以,所以只能在 code 中進(jìn)行設(shè)置。
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%}
語法高亮的主題默認(rèn)是 default嘱巾,如果用戶有選擇其它主題就會(huì)存入 User Defaults 中憨琳,這樣 `CodeViewController` 在每次加載后都會(huì)設(shè)置為上一次選擇的主題。
下載代碼的邏輯是這樣的:
1. 先獲取 HTML 模板和下載API
2. 根據(jù) API 去下載代碼
3. 如果 API 對(duì)應(yīng)的文件是代碼文件(文本文件)就將代碼字符串進(jìn)行轉(zhuǎn)義旬昭、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 關(guān)于語法高亮主題的選擇上都只是列出個(gè)名稱列表而已遍略,通過名稱并不能直觀的知道該主題是個(gè)什么樣子的惧所,必須設(shè)置后才能一窺究竟。
我在這部分做了一點(diǎn)微小的工作:將主題的整體配色加入到列表中绪杏。
用戶點(diǎn)擊一個(gè) theme 后下愈,會(huì)返回到 CodeViewController
并重新加載上面的 HTML string,具體代碼就不貼了蕾久,可以到我的 github 中查看势似。
弄這個(gè) list 純是個(gè)手工活,因?yàn)檫@個(gè) list 并不是動(dòng)態(tài)加載的僧著,而是我將各個(gè)主題的配色都手工提取出來做成了一個(gè)數(shù)組:
要問我為什么要手工搞這個(gè)數(shù)組履因,下面就是原因。
Theme CSS
highlightjs 中每一個(gè) theme 其實(shí)是一個(gè) css盹愚,就是定義了 HTML 中各個(gè) 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;
}
...
每一個(gè) css 中的 class 都不盡一樣,不過大部分還都是定義了四五個(gè)顏色皆怕,分別對(duì)應(yīng)背景霞篡、關(guān)鍵字、自定義類型端逼、字符串朗兵、數(shù)字和注釋等等。因?yàn)?css 沒有一個(gè)統(tǒng)一的格式標(biāo)準(zhǔn)顶滩,所以想靠動(dòng)態(tài)讀取來獲得這些顏色還是要比搞個(gè)固定的數(shù)組麻煩許多余掖,畢竟這個(gè)數(shù)組也不是太大,才70多個(gè)元素礁鲁。