寫App有一個讓人頭痛的是App大小的問題, 而這大小有部分是由App裡面所用的圖所貢獻, 為了減少這部份消耗掉的資源, 不管是用較大壓縮率的格式來壓縮圖檔, 還是其他, 大家都想盡辦法想解決這問題, 現在由於平面化的UI設計, 使得又有不錯的方法來解決這問題, 平面化UI設計的特色是大部分的圖檔都是單色而非五顏六色, 這使得用向量圖, 甚至用字型來解決這問題變得可行

免費的圖標字型(icon font)

把所有的向量圖示變成字型檔可以節省不少空間, 以流行的Font Awesome 來說, 它包含了634個圖標, 卻只佔了153KB, 這在以往可能是不到十個圖標的檔案就會達到的大小, 相較之下節省了不少空間, 像這樣開放的圖示字型, 可以找到不少:

  • Font Awesome : 蠻流行的一個開放icon組, 提供了ttf, woff等字型檔格式
  • Google material icons : Google開放源碼的免費icon組, 它不只提供Android, iOS可使用的圖檔外, 也提供了字型檔的部分, 而且它的字型檔支援了Ligatures (後面會再提到它好用的地方),這也使得它比Font Awesome來的好用
  • Weather Icons : 顧名思義, 這提供了222個可以用於表示天氣的icons, 不過對於風向的表示的部分, 它是用同一個圖示只是在web上利用css旋轉來顯示不同方向的風, 這一點應用到App上的話, 我是還沒找到比較適合表達的方式
  • Octicons : 由GitHub開源出來的圖標字型, 圖標不多, 但自己新增應該蠻方便的(自己增加svg檔用grunt去build)

除了這些之外, 應該還可以找到不少免費的圖標字型(icon font)可以用, IconFontKit這邊就列了不少(它也整合了)可用的圖標字型

使用這些, 除了可以節省app的大小, 也可以省下不少設計圖標的時間, 但也不是沒缺點, 因為是字型的關係, 它每一個icon都是對應到一個unicode字元, 這字元大多數跟icon的形狀沒關係, 也就不是那麼好對應, 通常都要查一下對照表找出字元碼

利用現成的framework整合

要在iOS上使用這些圖標字型(icon font)的方式好幾種,寫程式去load字型是一種, 當然就有不少大德, 寫好包裝可以讓你用cocoapods或是cathage直接引入, 這邊有幾個不錯的:

用這些現成的framework的好處是, 一來減去自己手動包裝字型進app的複雜度, 二來是, 這些已經幫你定義好一些對應圖標的常數, 讓你用比較方便的方式而不是記憶unicode字元來對應這些圖標

但它也是有缺點的, 大部分這些的作法都是runtime才去載入跟註冊字型, 因此你必須是在程式內設定你的UILabel, UIButton的字型, 無法事先就在Interface Builder做預覽, 所以個人比較喜歡的方式就是自己動手來

手動在xcode上使用自訂字型

自己手動加的好處就是, Interface Builder上就可以套用, 直接就可以看到結果, 但就是稍微繁瑣了一點

Interface Builder直接看結果

首先, 要把字型檔拖入你的Project裡面:

ttf files in project

接著打開Info.plist, 加上一個新的項目叫做*“Fonts provided by application”*

這個是一個陣列(Array), 它的內容就是你要加入的字型檔檔名, 把你要加的每一個都列進去

Info.plist

接著, 在Interface Builder裡你所要使用icon font的地方, 比如說UILabel設定你的字型, 原本的字型是設定為*“System”, 把它改成“Custom”*, 並選定你所需要的字型名稱, 例如FontAwesome, 要注意的是, 字型名稱不一定等同於你字型檔的名字:

Interface builder

Interface builder

接下來在Text的部分輸入這個圖示的代表的Unicode字元就好, 不是Unicode碼, 而是那個字元本身, 這挺不方便的, 可能用copy paste的才有辦法, 這是這個方法最大的缺點

這問題還是有方法克服, 這也就是前面為何提到會比較推薦使用Google material icons而不是Font Awesome , 這原因就是Ligatures

Ligatures

Ligatures是一個字型上蠻方便的特色的, 關於Ligatures可以先看一下這篇, 這是在Google material icons提到的一篇文章:

The Era of Symbol Fonts

剛剛提到的一個很大的缺點是, 你要知道圖示對應的Unicode碼才可以在你的UI上顯示你想要的圖示, 這相當不方便, 尤其那些Unicode碼可能根本完全不代表任何意義

比較人性點的作法是當你想要一個圖示代表藍芽, 用bluetooth就可以找到對應圖示, 而Ligatures就是一個這樣的存在

我們先來看看, 如果使用沒有而Ligatures的FontAwesome, 你在Text打上**“Contacts”**會是怎樣一個情形?

Ligatures1

它會直接一字不漏的呈現**“Contacts”**,這還是因為FontAwesome有包含原本英數字字型在裡面, 有些其他的自行更慘, 根本就是一片白

讓我們再看看用Google material icons的字型,同樣的東西會有什麼結果

Ligatures2

因為這個字型有支援Ligatures, 所以在這邊contacts就會被直接代換成它對應的圖示了, 我們就不用寄那種完全看不懂的unicode碼了

但大部分的字型其實也都沒有, 所以自訂字型該怎做?那就留待之後研究了

去年為了參加WWDC, 開始練了Swift, 寫了兩個library, 不過好像一直都沒寫過完整的App, 連UI好像都沒真的去刻過(去年寫的東西跟UI比較無關), 因此最近利用了一些時間開始了個side project, 做side project就常常會把時間花在一些枝微末節的地方, 比如說, 為了做一個像Android那樣的Floating action button, 去找來一個現成的3rd party lib - yoavlt/LiquidFloatingActionButton , 那個像水珠一樣突出去的效果, 我還蠻愛的:

但缺點是, 後面缺一個擋住背景元件的, 以致於要去點伸上來的小按鈕容易誤按後面的元件, 因此就想自己改一個後面多一個overlay的版本, 當然也不想隨便貼一張白白的就交差, 起碼要像這樣:

這邊不是單純蓋一個深色半透明的背景而已, 還需要作一點模糊的部分

在iOS上(iOS8 之後), 作這樣的東西很簡單, 只要利用UIVisualEffectViewUIBlurEffect這兩個東西, 寫法很簡單:

    let blurEffect = UIBlurEffect(style: .Dark)
    let uiEffectView = UIVisualEffectView(effect: blurEffect)
    uiEffectView.frame = overlayView.bounds
    overlayView.addSubview(uiEffectView)

UIBlurEffect有三種樣式, Dark, Light, 和ExtraLight, 上面的範例是Dark, 蠻適合用在這地方的, 利用這個方法就可以不用自己擷取screenshot再算模糊化了

既然講到抓圖神器, 就先幫友人Wisely宣傳一下: https://play.google.com/store/apps/details?id=com.wisely.imagedownloader

不過在這並不是要宣傳抓圖神器的, 之前在找題目練習Swift時, 就想到要寫一個解析HTML的工具, 然後可以像是jsoup, 或是goquery一樣方便, 至少用來解析網頁內的連結或圖片連結方便的, 比如說像是下面的code:

let $ = SwSelect(html)
let imgUrls = $(”img”).attrs(”src”)
for url in imgUrls {
    print(url)
}

這個例子就這幾行就可以把網頁裡面圖片的Url給抓出來了, 又或, 如果我們只想抓某特定class的tag內包含的圖片(如:”<div class=“a1“><img src=“aa.jpg”/></div><div class=“a2″><img src=“a2.jpg”/></div>” 我們只想要包含在a1裡面卻不想要a2裡面的就可以用:

let $ = SwSelect(html)
let imgUrls = $(”.a1″).find(”img”).attrs(”src”)

本來我是想學goquery一樣, 複刻出一套jQuery出來的, 順便複習一下jQuery, 上面的範例其實也像是jQuery的API(不得不說這API還有selector設計真的是神來一筆), 因此選擇要把goquery從golang po到swift來, 其實是蠻可行的, 因為這兩個語言相似度還蠻高的, 但做到一半發現, 要複製整個jQuery其實蠻龐大的, 而且我只需要解析HTML的部分並不需要修改HTML的部分, 因此中途急轉彎, 把寫到一半的code, 重新包裝成這個SwSelect, 剛好也足夠達到上面兩個例子想要的啦

由於原本就是想從goquery po過來,  因此包裝了libxml2, 也把goquery用的selector parser的核心 - Cascadia給移植到Swift來, 整個SwSelect可以說是基於Cascadia, 這次也順便練習了Swift 2的一些新特色, 不過在整合libxml2上面也吃了些苦頭, 這以後有機會再寫, 目前其實應該可以使用了, 說明還沒補, Cathage的支援也還沒試過, 不過一些用法可以看一下Unit test來當範例:

https://github.com/julianshen/SwSelect/blob/master/SwSelectTests/SwSelectTests.swift

這週應該會找時間補上說明

如果有需要的話歡迎拿去使用:

https://github.com/julianshen/SwSelect

在Swift裡, Protocol和Protocol extension是非常強悍的東西, 比起OO世界的Class, 其實是更加犀利, 在今年(2015)的WWDC有一場個人很推薦的Protocol-Oriented Programming in Swift , 這場講的蠻精闢的, 熱門到後來加開了第二場, 今年我去WWDC, 我自己就兩場都去了, 回來又再回為了一次, 相當建議聽看看的:

https://developer.apple.com/videos/play/wwdc2015-408/

好, 這篇重點不是推薦這場演講….而是怎在Swift中去建立Array extension? 這好像沒什麼難的, 不就像下面這樣就可以了?

extension Array {
   func concat() -> String {
       var str = “”
       for i in self {
           str += String(i)
       }
       return str
   }
}

沒錯, 這是最簡單的方式, 但其實有個問題, Array內的元件(element)是泛型, 也就可能是任意的型態, 以這個例子來說, 也有可能是無法轉成String的型態, 在這邊就有可能出問題, 因為這寫法會被套用到所有的Array型態去, 不管是String array, In array或其他, 但大部分現實的例子(也就是我手上在寫的東西剛好碰到的例子), 我們只希望延伸出的東西只被套用到某些特定的型態, 這時候就得加上”where”來解決這問題:


以上面這例子, 雖然字串陣列跟整數陣列都有find這函式, 參數型態也一樣(Self.Generator.Element), 但這兩者的find的行為是不同的, 以字串陣列來說是找含有這一字串的, 而以整數陣列來說則是找一樣的, 這邊就是靠extension裡的where

這邊有個不一樣的地方是, Array被換成CollectionType了, 這是因為where裡面要判斷Element是不是我們所要的型態, 而Array是一個struct, CollectionType才是一個Protocol, 所以這邊我們用CollectionType來達到這目的

實在有點詭異的問題, 解決後又覺得實在有點蠢, 結果這問題足足花了我一個半小時才得到答案

問題是這樣的, 我拿了這一個字串 “‘x\\r\nx’” 來做解析, 一個個Character去檢查是不是”\r”, 如:

let x=“'x\\r\nx’”
for s in x.characters {
    if s == “\r” {
         print(”got you!”)
    }
}

邏輯上看起來沒啥問題, 但執行結果一直不如預期(沒印出”got you”), 把第四個字元substring出來它也不等於字串”\r”

百思不得其解, 結果最後在\r\n中間加一個空白後, 就得到預期的結果了, 原來….它是把”\r\n”整個當成一個字元!!!