青芒 for Mac客戶端開發筆記

zhihaozhang發表於2018-03-12

關於輕芒和青芒

輕芒閱讀是我每天都會開啟的app,在它提供的兩百多個channel中,我訂閱了其中十幾個channel,非常喜歡這個產品。這個產品的創始人是王俊煜,他曾經創辦了一個更有名的公司——豌豆莢。關於輕芒和俊煜的更多介紹,請參照: 前沿科技的報導。雖然報導里強調了輕芒不是青芒,但是參照知了和知乎的關係,我還是決定把我的客戶端取名為青芒。而且取名青芒的一大好處是logo好設計。做完原型的第二天,我就找了非著名設計師Joseph幫我設計了一枚logo,個人覺得還是很讚的。

非著名設計師Joseph設計的logo
非著名設計師Joseph設計的logo

非著名設計師Joseph設計的logo

為什麼要做青芒

8月18日,我在github閒逛的時候發現了輕芒團隊仍處於內測階段的API文件,抱著試一試的態度,我給輕芒團隊傳送了內測申請的郵件,兩天後得到了對方產品經理振輝的回覆,同意了我參加內測的申請,在這表示感謝。收到郵件之後我興奮異常,雖然之前也做了像微博、twitter、Instagram的客戶端,但是這些產品早已有比我做的更好的產品上架到Apple Store了,估計除了我以外,沒有人會用我開發的簡陋版客戶端。而輕芒則不同,目前還真沒有mac和windows的客戶端。

輕芒的使用者通常對精神生活有豐富的追求,在macOS和windows中,我估計macOS使用者會非常多,因此我選擇了macOS平臺,我認為這對我是一個機會,可能會有不少人試用這款軟體。畢竟看到別人電腦或手機上執行著你開發的軟體,還是會給人帶來很大的成就感的。(偷笑)

據不完全統計,自從開源以來,已經有100多人下載了該軟體。

青芒是怎麼做的

知了是知乎日報的mac客戶端,青芒跟知了有著相同的需求,即展示channel list、article list、article content三個部分,於是萌生了借鑑知了的念頭,搜了一圈之後發現github上並沒有類似的mac軟體開源出來,於是做了決定:自己從頭開始寫,並且寫完的第一件事就是將專案開源。我相信三段式佈局的應用會適用很多場景,並且會給初學者們帶來一定的啟發。專案開源在我的github上,歡迎fork和star,特別感謝github開發者@jydemo、@immress對本專案提出的改進與code實現,效果圖如下。

客戶端檢視(模仿的知了)
客戶端檢視(模仿的知了)

客戶端檢視(模仿的知了)

Notification Center檢視(略醜)
Notification Center檢視(略醜)

Notification Center檢視(略醜)

TouchBar檢視
TouchBar檢視

TouchBar檢視

輕芒的API遵循RPC風格,形如 域名/主體.操作,主體和操作都使用小寫開頭的駝峰命名法,總體上還是比較容易呼叫的。青芒目前用到了其中的category.list、category.get、article.list、article.get。經測試,發現內測版裡提供的channel還非常有限,只有11個,所以目前選擇將所有channel展示出來。

開發青芒過程中,使用到了如下技術:HTTP請求、json解析、NSSplitView、WebKit、NSTableView、多執行緒(GCD)、imageView、自定義NSTextField、NSScrollView、自定義Window、NSTouchBar、NCWidgetProviding等技術,下面我來詳細介紹一下青芒實現的過程。

介面部分

macOS/ios介面的構建一直是值得爭論的話題,大體上有三種可選方式:

  1. 純程式碼手寫
  2. Xib檔案
  3. Stroyboard

純手寫程式碼

純手寫程式碼是極客的不二選擇,對於多人協作工作是很好的選擇,但是缺點主要是不能直觀的看到效果、編碼速度很慢,例如初始化一個自定義的Button可能就需要二十行程式碼,非常不利於閱讀。

Xib檔案

Xib解決了上面的兩個問題,提升了開發效率。其實Xib就是XML格式的檔案,在編譯過程中,被編譯成Nib檔案,每個Nib檔案跟對應的ViewController關聯。Xib的缺點是:程式碼可能回覆蓋UI的設計,而且每個檢視都需要單個的Xib,檢視間的跳轉依然需要程式碼控制。

Stroyboard

為了解決了Xib的問題,Apple提供了故事板功能。StoryBoard可以看成將很多Xib集中到了一起,像講述一個故事一樣,清晰的看到每個ViewController之間的跳轉關係,跳轉可以不用寫程式碼了。因此我選用了StoryBoard來構建青芒。

青芒的StoryBoard檔案截圖
青芒的StoryBoard檔案截圖

青芒的StoryBoard檔案截圖

從StroyBoard截圖可以清晰的看到整個介面的佈局,整個介面的佈局是模仿的知乎日報Mac客戶端(知了)做的,經典的三段式佈局,NSSplitView可以將介面分成左右(或上下)的兩部分。考慮到主題一列比較窄,而且主題和相應主題下的文章有強關聯關係,因此借用SplitView將Overview Controller一分為二,這樣三段式佈局就算是完成了。

先從左邊的OverView檢視說起,這個檢視裡由兩部分組成,第一個部分是一系列的主題按鈕,點選之後,在右邊的NSTableview裡展示對應主題下的最新文章list。最新文章list點選之後,在右邊的DetailView中用一個WebView展現文章的詳情。

黑色的部分是TouchBar的檢視,TouchBar是蘋果在MacBook Pro 2016機型上加入的一個新的互動裝置,圍繞他的討論有很多,我們有空可以單獨聊一下,但是為了尊重這條價值4K的bar,我還是決定做了相應的適配,將主題通過按鈕的形式放到了Bar上,Bar上的按鈕和OverView的按鈕是需要做聯動功能的,在下文中會提到。

耐心地拖入相關控制元件、控制控制元件之間的相對位置,最初始的原型就算是完成了。當然,經過試用,還是可以發現優化的部分,比如Overview裡的tableview並不是頂到window title的,這樣使用者拖拽那部分的時候,視窗依然可以移動,同樣,WebView上方也是需要留白的。雖然只是個很小的細節,但真的很討好使用者。

自定義取代系統預設

介面完成後,就需要做功能了,但是等等,好像看起來不大對勁,為什麼效果相比於知了差很多呢?下面的截圖展現了青芒的第二版,相比於第一版,已經是把window的標題去掉了,但還是給人一種寨寨的、不夠簡潔的感覺。TableView中選中的顏色跟整體介面很不符。

第二版的青芒原型
第二版的青芒原型

第二版的青芒原型

為什麼會出現這樣的情況呢?因為一直到現在,我們都是採用的系統預設選項,沒有設計人員的審美在裡面。如何給使用者帶來私人訂製的感覺呢?這就需要我們覆蓋系統的預設行為和屬性,具體來說,就是自定義子類,繼承並覆蓋父類中不符合開發者預期的部分。

從最後的結果來看,我們需要整個軟體看起來背景是白色的,因此我們在每一個view載入的函式中,指定背景色為白色,使用

view.wantsLayer =true
self.view.layer?.backgroundColor=NSColor.white.cgColor
複製程式碼

雖然所有檢視背景色全部設為了白色,但是關閉、最大化、最小化按鈕依然title上,而不是overview那部分,設定title為影藏,它們又不見了,讓他們正確顯示在overview中的做法是在window載入函式中加入:

self.window?.titleVisibility = .hidden
self.window?.titlebarAppearsTransparent =true
self.window?.styleMask.insert(.fullSizeContentView)
複製程式碼

關於TableView中選中狀態的背景色,可行的方法有兩種。第一種是自定義Cell覆蓋NSTableCellView,覆蓋父類中的override var backgroundStyle:NSBackgroundStyle{}屬性。第二種方法是github上的使用者@jydemofork我的專案之後給我提的issue,自定義NSTableRowView,覆蓋父類方法:

override func drawSelection(in dirtyRect:NSRect) {
    super.drawSelection(in: dirtyRect)
    var slectorRect =NSInsetRect(self.bounds,0,0)
    NSColor(calibratedWhite:0.92, alpha:1.0).setStroke()
    NSColor(calibratedWhite:0.92, alpha:1.0).setFill()
    var slectorPath =NSBezierPath(roundedRect: slectorRect, xRadius:0, yRadius:0)
    slectorPath.fill()
    slectorPath.stroke()
}
複製程式碼

然後實現tableview的代理方法,

func tableView(_tableView:NSTableView, rowViewForRow row:Int) ->NSTableRowView? {
    let rowview = MyTableRowView(frame: .zero)
    return rowview
}
複製程式碼

兩個主檢視之間的分隔條比較粗,總讓人覺得不美,解決方法還是自定義。覆蓋NSSplitView,覆蓋屬性

override vardividerThickness:CGFloat{
    get {return0.5}
}
複製程式碼

如果使用者願意分隔條還是可以左右動的,想禁掉左右動的功能,實現NSSplitView的一個代理方法:

override func splitView(_ofDividerAtsplitView:NSSplitView, effectiveRect proposedEffectiveRect:NSRect, forDrawnRect drawnRect:NSRect, ofDividerAt dividerIndex:Int) ->NSRect{
    return NSRect.zero
}
複製程式碼

經過這一章節,不難發現還是程式碼靠譜(?)。經過上面的調整,介面看起來簡潔、清爽了不少,可以以假亂真了。

希望這一章節讓大家明白,想要做出看起來美的東西,一定要去大膽地替代系統的預設選項。而具體的做法通常是覆蓋父類中的屬性和方法,記得要將元件和自定義的類關聯起來。

美化後的介面
美化後的介面

美化後的介面

一個坑

自定義TableView的Cell過程中,由於文章的標題通常是比較長的,因此用NSTextField無法放下,必須使用NSTextView,而NSTextView預設是可以上下左右滑動的,所以在文章列表中上下滑動的時候,每當滑動到TextView裡,滑動事件就會白TextView捕獲,TableView中的Scroll view沒有機會捕獲了。

解決的方法和上一節一樣,通過覆蓋cell裡(請注意是Cell,不是tableview)的scroll view,重寫hitTest方法

override func hitTest(_point:NSPoint) ->NSView? {
    return nil
}
複製程式碼

告訴cell,這個滑動事件我不處理了,請交給別人處理吧。

NSTouchBar

為了趕時髦,應用內做了TouchBar和通知中心的內容。TouchBar需要注意的是NSWindow和NSViewController之間的聯動。從NSWindow到NSViewController:

let myViewcontroller =self.window?.contentViewControlleras!mainViewController
複製程式碼

相反的過程:

let mywindowController=NSApplication.shared().windows[0].windowControlleras?windowController
複製程式碼

這樣就可以做到在TouchBar中按了某個按鈕,在主介面裡也可以看到按鈕被選中的效果,滿足了一致性。

Notification Center檢視

通知中心做的蠻醜的,真的是為了嘗試一下TodayExtension的功能而已。模仿知了,目前功能只是展示了首頁的文章列表,點選文章可以用系統預設瀏覽器開啟原文。其實這也就夠了,畢竟通知中心就是為了看個大概用的,誰也不會經常點開看。通知中心需要注意的是要自定義檢視的高度,通過 self.preferredContentSize=CGSize(width:self.view.frame.size.width, height:xxx) 完成。 最後要注意的是用URLSession請求資料,可以防止UI卡頓,UI卡頓給使用者帶來的感覺非常糟糕。

func getData(with urlString:String,success:@escaping(Data?)->Void, failure: ((Error)->Void)? =nil) {

         guard let url =URL(string: urlString)else{

         return

}
    let task =URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?)in
    DispatchQueue.main.async{
    if let error = error {
                     failure?(error)
    }else{
                      success(data)
                 }
          }
      }
     task.resume()
}
複製程式碼

文章具體內容檢視

青芒團隊提供的API裡有web-content這一項,只要通過webview包裝然後load一下就會將文字和圖片展示出來,但是還是有一些細節需要考慮到的。比如有些圖片是很大的,雖然限制了視窗不得小於一個值,但有些圖片還是遠超了這個大小。web-content是沒有標題的,看起來會比較突兀,標題和標題需要你手動加上。圖片過大時,視窗是可以左右滑動的,怎麼把他限制不能滑動。字型如何跟其他部分檢視的字型做到沒有違和感,都是需要考慮的問題,這些用前端裡的CSS樣式可以控制,需要開發者有一定的前端開經驗。由於本公眾號重心在果教,因此這裡就不具體展開敘述了,感興趣的朋友可以參考我的github

青芒的宣傳

做完青芒之後,考慮到整個專案是用最新的swift4寫成,於是我在微博上@了swiftLanguage,博主是一個不大不小的V,關注者多是對swift感興趣的或從業人員。短短兩天,該微博獲得了1萬多次閱讀,最後我欣喜的發現,王俊煜也給該微博點了贊,感謝俊昱的鼓勵與肯定。

微博截圖
微博截圖

微博截圖

相關文章