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閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,630評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 人閑心寂寂,天高云悠悠。紅塵紛擾事,皆隨煙波流。曾經多固執,今日覺荒謬。不如歸園田,耕讀到白頭。
    成都獨行俠閱讀 241評論 2 4
  • 天氣轉涼了,秋天真的來了,北京的秋天是一年當中最好的季節,可惜的是時間比較短,只有短短的兩個月左右。 在北京多年,...
    一箭閱讀 332評論 0 3
  • 夜里的樣子 好孤單 誰在拼命失眠 走路的樣子 好心酸 誰在追趕時光 如果你愛上了ta 不能陪ta流浪 那就把贊美帶給ta
    一凡SU閱讀 182評論 0 0