這篇教學解釋了如何由Parse來建立後台的方法。我們會建立一個像Instagram 一樣的App,其中包含了這些功能:
- 從Parse 載入資料,將它存在local 端。
- 儲存資料至Parse,並將其寫入至雲端。
- 投票或者是對喜愛的貓咪圖片按讚。
這個App完全由Swift所建構,Swift是Apple作為iOS App的新程式語言。Parse還沒有以Swift來編寫,所以我們會建立一個Bridging Header來處理它。
以下將是你會學到的部分:
- 利用Parse 雲來做資料的存取
- 在Swift 專案中以Cocoapods整合Objective-C 框架(framework)
- 以介面建構器(Interface Builder)設定視圖以及一個自訂的表格視圖cell。
- 從頭開始以Swift來編寫整個App。
- 實作Auto Layout 與約束條件。
- 使用手勢辨識器(gesture recognizers),optionals、條件式(condition)、屬性(property)、outlet與動作(action)。
我們開始吧
首先,確定你已經有了Parse帳號。你可以至Parse.com 以Facebook、Google+或者 GitHub帳號,或者輸入你的email地址來登入。
接著登入Parse並至https://www.parse.com/apps進到你的App Dashboard。
點擊上方處的「Create a new App」按鈕建立一個新的App。並輸入「Paws」作為我們App的名稱。然後,打開新的App並確定你有見到Core標籤,看起來如下圖所示。
建立 Datastore
Parse技術上只是一個線上資料庫。資料以帶有名稱以及幾個欄位的物件來儲存,跟試算表很像。這樣的物件稱作一個類別,而它的功能如同資料結構的藍圖,我們要處理的類別稱作Cat。
在Core 標籤,點擊「Add a class」按鈕。確認下拉框是寫Custom,並輸入類別名稱為Cat,然後點擊「Create class」。
Parse幫我們建立了新類別並加入一堆標準的欄位,像是objectId、createdAt、updatedAt 以及 ACL,我們加入更多欄位吧!
按下上方的 +Col 按鈕,以名稱搭配型態加入以下的欄位:
- Name: name, type: String.
- votes, type Number.
- url, type String.
- cc_by, type String.
這些欄位用來儲存我們cat資料庫的基本資訊。
資料導入
現在我們已經設定好架構,我們可以導入資料。儲存這個檔案至你的電腦:Cat.json
然後,回到Core標籤與我們的資料庫,點擊左上方的Import 按鈕。選取你剛儲存以及上傳的檔案。確認 Collection Type 「Custom」被選取,並重新將collection 命名為Cat (不是 rs1_Cat),然後點擊Finish Import 。 Parse會通知你的導入已經完成,通常會很快,然後點擊Got it! 。然後重載網頁。
如果一切沒有問題,你應該會見到資料庫中有填入10隻貓咪。它們全部都有名字、一個URL、一些票選數(votes)、以及標註圖片原始作者的欄位。
這是我們在Parse中所需要的。現在,我們來設定Swift Xcode專案。
設定Xcode專案
打開Xcode並建立一個新專案,從開始的畫面,或使用選單File -> New -> Project 。
從 iOS->Application挑選「Single View Application」模板,在下一個畫面,輸入以下的欄位:
- Product Name: Paws
- Organization Name: 這裏輸入任何你想輸入的名稱
- Organization Identifier: 任何你想輸入的,例如 com.appcoda.tw
- Language: Swift
- Devices: Universal
- 不要勾選Use Core Data
點擊Next 並幫專案選擇檔案夾,然後點擊建立。
我們不準備使用Stoyboard,所以點擊左上角名稱為「Paws、 2 targets、 iOS SDK」,來打開Project 設定。在左側列表,點擊Targets下方的Paws,並在畫面主區域的 Main Interface setting,從輸入框中移除Main 文字。
以Cocoapods 來加入Parse函式庫
我們在App的程式中使用Parse 之前,我們必須將它加入作為依賴件(dependency)。我們使用的Cocoapods是一個套件管理器。許多App專案依賴第三方函式庫,像是Parse,而Cocoapods是可以很容易將函式庫導入並確保能即時更新的工具。
在終端機執行以下的指令來安裝Cocoapods。它會詢問你的Mac使用者密碼。不要加入$ 符號。這個符號只是指出一個shell指令。
$ sudo gem install cocoapods
如果幾分鐘內沒有動作不要擔心, 表示正在安裝Cocoapods。完成後,你會見到好幾行指令,最後會見到gems installed。
接下來,在Xcode App專案的根目錄下建立一個空的檔案,將檔案命名為Podfile。打開你偏好的文字編輯器,並貼上以下幾行:
pod 'Parse', '~> 1.7.1'
pod 'ParseUI', '~> 1.1.3'
這個Podfile是告訴Cocoapods,要使用哪一個函式庫,在這個專案中,Parse版本是 1.7.1而ParseUI版本是 1.1.3
現在,關掉Xcode並使用終端機切換至App專案的根目錄。在終端機輸入cd,並在Finder中查詢一下Paws的目錄,然後將目錄拖曳至終端機中。
接下來輸入以下的指令:
$ pod install
Cocoapods 會尋找Podfile並嘗試安裝我們所設定的依賴件。這過程可能需要幾分鐘,結果應該如下所示:
CocoaPods已經下載並編譯Parse以及其依賴件,並將其加入新的Workspace。從現在開始,我們不再使用原來的App專案,我們將使用由CocoaPods 所建立的Workspace,它裡面包含了原來的專案與CocoaPods專案。
在我們編寫App程式前,我們需要先建立Parse與我們專案間的連結。Parse是以Objective-C所撰寫的,而我們的專案是以Swift來編寫,這兩個如果沒有做好設定是無法混在一起的。
在Swift專案中使用Objective-C
任何Objective-C函式庫、專案或類別可以透過Bridging Header來跟Swift一起使用。技術上,這樣的介接會將標頭檔(header file)從Objective-C轉譯為Swift。
執行以下步驟來建立Bridging Header:
- 在Paws專案的Paws目錄上按右鍵,並加入一個新的檔案至Paws目錄,然後點擊New File… 。
- 從iOS -> Source 分類,挑選Objective-C檔案模板,並點擊Next。
- 將類別命名為「Paws(或者任何你偏好的名稱)」然後按下continue繼續並儲存檔案。在出現的提示中點擊「Yes」來設置一個Objective-C Bridging Header。
Xcode建立了兩個新檔案:Paws.m and Paws-Bridging-Header.h。我們不需要Paws.m檔,所以只要將它刪除即可,在Paws-Bridging-Header.h檔撰寫以下的程式:
#import
#import
#import
設定這麼多工作只是為了編寫一個專案的程式,對嗎?別擔心,我們稍後就會有些有趣的東西了。記得Parse提供了一個可以馬上使用的後台,已經幫我們省下不少工作天了。
確認Parse的運作
回到https://parse.com/apps,在Parse的Dashboard。將指標移到右上角你的帳戶上,然後點擊Account,接著點擊上方的App keys標籤,你也可以直接以這個網址進去 https://parse.com/account/keys。
這個頁面列出了你的App Keys,稍後需要它來辨識這個App與Parse間的網路服務。這樣的Key是由一串字母與數字的組合而成,應該要保密才是,基本上就是App的密碼。
接著打開在Xcode的AppDelegate.swift檔,在application didFinishLaunchingWithOptions
方法處。加入以下的程式至方法中,確認它是方法的第一行,整個方法看起來應該像這樣:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
Parse.setApplicationId("...", clientKey: "...")
return true
}
將「…」以Parse上的App keys來取代,取代的部分是:
- 第一,Application ID。
- 第二,Client Key。
然後,試著去執行這個程式:
- 確定有選取正確的裝置,檢查Play按鈕的右上角的裝置有被選取,例如你的iPhone或者iPhone 6。
- 然後點擊Play按鈕或按下Command-R。
這個專案應該可以順利執行沒有錯誤。在執行期間,當你見到iPhone,它會有一個黑色畫面然後抱怨了一下視窗是空的,這是因為我們移除了MainInterface storyboard,然後還沒有取代它。
好消息是,你已經將你的專案與 Parse以及CocoaPods設定成功了!
建立表格視圖控制器
要顯示Paws App的貓咪們,我們將使用一個表格視圖控制器(table view controller)。這是iOS很常見的介面元件,以垂直列表的方式將資料顯示在每列上。一個很棒的例子是iPhone上的Contacts App:一個顯示聯絡人與電話的垂直列表。在Objective-C 與Swift ,表格視圖控制以UITableViewController
類別為代表。
Parse有一個非常相似的部分稱作ParseUI,這是一個UI元件的集合,跟Parse產品緊緊的整合在一起。我們將會使用PFQueryTableViewController
類別,它延伸了UITableViewController
的功能加上Parse上的資料,兩者配合完美。
我們開始建立一個新的Swift類別稱作CatsTableViewController
,在專案導覽器,在Paws目錄上按右鍵並挑選New file。從iOS -> Source挑選Cocoa Touch Class 模板。輸入以下的設定:
- Class:
CatsTableViewController
- Subclass of:
PFQueryTableViewController
- Language: Swift
- 確認 Also create XIB file 沒有勾選
將檔案儲存在Paws目錄。在選擇目錄的時候,確定Paws被勾選作為Target(下面的地方)。
打開新類別檔案,你可以見到基本的結構: 一個viewDidLoad
方法,一個didReceiveMemoryWarning
方法。注意 CatsTableViewController
是PFQueryTableViewController
的擴展,它是子類別。而PFQueryTableViewController
又是UITableViewController
的擴展。所以在我們從ParseUI加入程式以及功能時,我們的CatsTableViewController
會繼承表格視圖功能。
表格視圖控制器的程式撰寫
我們來撰寫表格視圖控制器。首先,我們必須覆寫我們類別的建構方法來設定幾個基本設定。
加入以下兩個方法到類別的上方,就在檔案的第一個大括弧之後。
override init(style: UITableViewStyle, className: String!)
{
super.init(style: style, className: className)
self.pullToRefreshEnabled = true
self.paginationEnabled = false
self.objectsPerPage = 25
self.parseClassName = className
}
required init(coder aDecoder:NSCoder)
{
fatalError("NSCoding not supported")
}
你剛加入兩個方法:
- 一個指定的初始化設定,init,有兩個參數:一個是表格視圖的樣式,一個是我們從Parse上取得會用到的className(也就是Cat)。
- 一個必要的初始化設定,required init,有一個參數:一個
NSCoder
的實體。它的內容跟現在無關,我們只要把它固定作為必要實做的方法,但結構上不是重要的方法。
在第一個init
,發生了這幾件事:
- 經由
super.init()
呼叫,透過其父類別(super class)PFQueryTableViewController
來初始化自己。 - 然後將
pullToRefreshEnabled
設為true。這是繼承了PFQueryTableViewController
屬性。而self 這個特別的變數是參照目前的範圍,也就是這類別的實體。 - 然後,我們關掉了分頁,並設定我們表格物件的最大數為25。
- 最後,我們儲存了className參數在parseClassName這一個實體屬性中。
而稍後,我們建立了CatsTableViewController
類別的實體,這個建構器(constructor),或者稱指定初始器(designated initializer),會被呼叫作為設定表格視圖控制器的基礎。
編寫以queryForTable 來取得資料的程式
以Parse 來實作表格視圖的核心是使用PFQueryTableViewController
的queryForTable方法。當我們將類別作為PFQueryTableViewController
的子類別時,已經繼承這個方法,而我們必須覆寫它:PFQueryTableViewController
在它要連上表格視圖控制器至Parse 的datastore時會呼叫它。它幫表格查詢文字資料,因此這個方法稱作queryForTable
。在這個方法內,我們可以客製化取得方式。
將這個方法加至CatsTableViewController
類別,在 viewDidLoad
方法下方,注意一下大括號!
override func queryForTable() -> PFQuery {
var query:PFQuery = PFQuery(className:self.parseClassName!)
if(objects?.count == 0)
{
query.cachePolicy = PFCachePolicy.CacheThenNetwork
}
query.orderByAscending("name")
return query
}
我們來看一下這個新方法的所用到的signature(譯註:signature指的是其函數名稱、參數型別等):
override func queryForTable() -> PFQuery
它內容是什麼?它設定了queryForTable
的新方法,告訴了編譯器幾件事:
- 以相同名稱(或signature)覆寫了父類別方法,以及陳述的覆寫。
- 以func來宣告方法,以及其方法名稱,
queryForTable
。 - 以及宣告方法間的參數,在我們的例子是沒有參數。
- 最後這個 ->
PFQuery
,是方法回傳型態。
方法內,發生了什麼事呢:
- 宣告一個新參數,稱作query,並以一個帶有名稱參數為classname(以self.parseClassName 來取得其值)的建構方法(constructor method)來實體化它,換句話說,當我們的表格類別名稱是Cat,Cat實體的查詢(query)在這個地方便會建立。
- 然後,倘若查詢結果是空的話,我們在query設定cachePolicy屬性。它的值是
PFCachePolicy.CacheThenNetwork
常數,表示查詢會先針對物件做離線快取(cache)查詢,如果不存在的話,它會從線上的Parse datastore下載物件。當表格視圖第一次放置在我們的App的畫面上,if陳述幾乎至少會執行一次。 - 然後,我們以「name」欄來排序查詢物件。
- 最後,我們將查詢回傳。
將資料放到畫面上
幾乎快完成了!讓我們撰寫這個類別的最後方法。它會自己將資料放到表格視圖上:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cellIdentifier:String = "Cell"
var cell:PFTableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? PFTableViewCell
if(cell == nil) {
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellIdentifier)
}
if let pfObject = object {
cell?.textLabel?.text = pfObject["name"] as? String
}
return cell;
}
這個方法的signature 是這樣:以tableView、indexPath、object參數覆寫tableView cellForRowAtIndexPath方法,回傳一個很明確地,解開(unwarp)的PFTableViewCell
實體。說白話點:這裏有一個表格視圖、有一個物件,這是它的索引(列數),現在請回傳可用的cell 視圖。
然後,我們先宣告一個文字型態的識別碼(identifier)給我們的cell,每一個cell型態有不同的識別碼。我們只會使用一種cell型態,所以我們指定其型態為String,其值為「Cell」。「let」這個陳述則是宣告一個常數,跟變數相反。
然後,我們宣告一個optional變數 cell,型態是PFTableViewCell
? ,而我們嘗試要從tableView 參數 dequeue它,這個dequeue是一種重新使用舊表格cell,以增快表格視圖顯示的機制。實體方法dequeueReusableCellWithIdentifier
有一個參數,作為我們設定這個cell型態的識別碼名稱。方法的回傳型態是optional,而我們將它以「?」陳述轉型(casting)為PFTableViewCell
,轉型基本上是將一種型態轉型為另一個相容型態,在這裏,我們將UITableViewCell
轉為PFTableViewCell
,為什麼需要用optional?這是因為如果沒cell去dequeue,這個方法會回傳nil。
然後,當cell實際上是空值,我們從PFTableViewCell
建立一個新的cell。我們以識別碼來指出cell的型態,並給予一個UITableViewCellStyle.Default
的樣式。
接著,在下一個if陳述,我們做一些很酷的事。通常,你使用optional時,你需要解開(unwrap)它。在這麼做之前,你需要檢查optional是否為空值(nil)。你不能解開一個有可能是空值的optional。在我們的例子,我們使用optional binding(if let)來確認optional是否為非空值。倘若它有包含一個值,我們將這個值指定給一個暫時的常數(pfObject)。
然後,我們指定object[“name”]為textLabel的文字屬性。這個文字標籤很明顯地是在表格視圖的列上做顯示。這個型態為PFObject的變數物件是NSObject
的子類別,所以我們可以使用[“…”] 標記來取得名稱為「name」物件的屬性。然後將其轉型為optional String?,因為這個物件可能有,也可能沒有一個稱作name的屬性,它有可能是空值。
最後,我們回傳cell。
快速回顧一下CatsTableViewController
好的,在CatsTableViewController
類別內,我們做了三件事:
- 設定幾個基本設定來初始化類別實體。
- 覆寫
queryForTable:
來整合Parse 後台,這裏我們要使用的類別也是我們的快取方案。 - 建立能重複使用的cell並將資料填入,將資料放置畫面中。
將表格視圖放至畫面上
現在,當你想要執行我們的App,它還不會做任何事,我們還沒有將CatsTableViewController
連結到我們的App!我們來進行吧。
回到AppDelegate
類別並調整application didFinishLaunchingWithOptions
方法如下所示:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
Parse.setApplicationId("...", clientKey: "...")
var tableVC:CatsTableViewController = CatsTableViewController(className: "Cat")
tableVC.title = "Paws"
UINavigationBar.appearance().tintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0)
UINavigationBar.appearance().barTintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0)
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent
var navigationVC:UINavigationController = UINavigationController(rootViewController: tableVC)
let frame = UIScreen.mainScreen().bounds
window = UIWindow(frame: frame)
window!.rootViewController = navigationVC
window!.makeKeyAndVisible()
return true
}
首先,確認你有使用正確的 Parse Application ID與Client Key。
然後,我們建立新的CatsTableViewController
的實體,並指定給tableVC 變數。我們使用一個只有一個classNmae(值為「Cat」)參數的初始器,一開始這個初始器在內部會被呼叫。我們也給tableVC一個標題(title),「Paws」,一個在任何UIViewController
(我們的表格視圖繼承這個類別)可以取得的屬性,並且稍後被導覽控制器(navigation controller)來使用。
然後,我們變更UINavigationBar
的外觀。該類別是由導覽控制器所使用,這是在許許多多的App中在上方所使用的導覽條(navigation bar)。設定外觀來確認該類別的實體有繼承一些樣式規則,在這個範例是指 tintColor
與 barTintColor
。它們兩個都是設為青色,並直接以UIColor的實體來指定。導覽條的文字顏色是設為白色,然後我們設定iPhone狀態欄(status bar)為light主題(也是白色)。
然後,我們最後建立UINavigationController
自己的實體,並將其指定給navigationVC變數。我們以rootViewController,也就是第一個視圖控制器為:tableVC來初始化它。以下是我們的視圖層級表示:
UIWindow -> UINavigationController -> CatsTableViewController
那麼,我們在導覽控制器中內部顯示一個表格視圖控制器,並把它放進我們App 的UIWindow
的最上層根視圖控制器。
最後,一些樣板程式:我們建立一個UIWindow
實體,指定給它一個框架,指定根視圖控制器,讓它變成我們App的主視窗。
執行App
好的,我們按下Command-R,或按下左上的Play按鈕來執行App。如果一切都正常的話,你的App應該會出現一個基本的藍色表格視圖,顯示10筆貓咪名。
你可以下拉表格視圖來更新,它會從Parse下載新的資料,並重新載入表格視圖。
我們待會可以豐富這個基礎App的內容。花點時間來享受目前的成果吧!你已經成功的以Pars實作出一個可以運作的App原型,太棒了!
在介面建構器建立一個客制的 UITableViewCell
好的,讓我們以客製化的表格視圖cell來美化這個App,我們不再只是顯示文字的cell,我們會以圖像–名稱–票選 的cell來代替,它看起來就如同這篇文章最上面的圖片一樣。
我們由建立一個新類別來開始,類別名稱為CatsTableViewCell
。在Xcode的檔案導覽器中的Paws按右鍵,並選取New File… 。從iOS->Source加入一個Cocoa Touch類別。將它稱作CatsTableViewCell
並作為UITableViewCell
的子類別。然後勾選Also create XIB file。語言選Swift,檔案當然就是建立在Paws目錄中。
然後,打開CatsTableViewCell.swift 檔,並變更類別定義為:
class CatsTableViewCell: PFTableViewCell
看一下我們做了什麼?這個類別是擴展為PFTableViewCell
,取代了UITableViewCell
。記得tableView cellForRowAtIndexPath
方法嗎?它回傳PFTableViewCell
型態的實體,這也是我們要變更的原因。
在這個類別的第一行,第一個大括號之後,加入以下的outlet至新的CatsTableViewCell
中。
@IBOutlet weak var catImageView:UIImageView?
@IBOutlet weak var catNameLabel:UILabel?
@IBOutlet weak var catVotesLabel:UILabel?
@IBOutlet weak var catCreditLabel:UILabel?
我們需要這4個outlet來顯示Parse的4種資料:貓咪圖片、貓咪名稱、所得票選數、以及圖片的原始作者。
接下來,從專案導覽器中打開CatsTableViewCell.xib,它會在介面建構器中打開,這是在Xcode內對設定App使用者介面超有幫助的工具。這是一個鷹架的工具,它不會建立一個有功能的App UI,只是先做為定義。以汽車音響來比喻的話,介面建構器建立了音響主控台,而你寫的Swift程式就是線路。
首先,執行以下步驟:
- 在左側的文件大綱(Document Outline)點擊主cell元件。
- 選取右側尺寸檢閱器(Size Inspector)標籤。
- 變更Row Height 為350,Width為 320,同樣的Height為350。
主要視圖現在應該會重新調整了大小。
現在我們將會加入4個視圖:
- 從右下元件庫中,找到
UIImageView
類別。 - 從元件庫拖曳圖像視圖(image view)至cell視圖。
- 調整圖像視圖大小,將其水平置中,並距離上、左、右側邊緣各15點。你也可以用尺寸檢閱器來完成,選擇圖像視圖,然後設定它的X與Y位置為15點,Width與Height設為290點。
重複上面的步驟來加入3個新視圖,也就是UILabel
的實體。將其中一個對齊左側,一個對齊右側。以上面的視圖做為參考。左側標籤位於(25,317,209,21),分別代表X、Y、Width與Height 。右側的標籤是位於(225,317,69,21)。而圖片版權聲明標籤是位於(199,285,106,21)。
接下來,設置全部的4個視圖。帶出右側的屬性檢閱器,針對每一個視圖做以下的設定:
- 圖像視圖:Mode 設定為Aspect Fill、勾選 Clip Subviews。
- 左側標籤:Font 設定為 System Bold 17.0 ,而 Color 設為 Black。
- 右側標籤:Font設定為System 14.0而Color 設為 Light Gray Color。
- 圖片版權聲明標籤: Font設定為System 14.0 而Color 設為 White。
現在我們將這些視圖與outlet做連結。首先,再次選取在左側文件大綱(Document outline)的Cats Table View Cell。然後切換至右邊的連結檢閱器(Connections Inspector)標籤。
現在,在位於檢閱器下Outlets下的四個outlet。是否看到空的圓圈?從catImageView右側圓圈,拖曳至cell中的圖像視圖。會出現一條藍色線,而在檢閱器,outlet出現了一個對應選項。重複以上的步驟來完成其他3個標籤。
針對客製化Cell設定Auto Layout約束條件
為了讓你的App在iPhone 4、4S、5、5S、6與6Plus看起來都正常,我們必須針對介面建構器的UI元件加上一些規則。介面建構器內建了一個非常方便的功能稱作Auto Layout,並使用約束條件來管理視圖位置、對齊與尺寸調整。Auto Layout的用法可能有點奇特,因為它直覺且非常具邏輯性。說倒底,這個工具在處理複雜的的調整工作非常有效率,可以替你省下佈局程式的撰寫工作量。
技術上,約束條件只是視圖的規則。以下是我們的視圖要去遵循的規則:
- 圖像視圖: 水平置中、高度固定為 290 點、寬度彈性、但是距離cell上、左、右側各 15 點。
- 左側標籤:寬度彈性、高度固定為21點、距離cell 左側邊緣25點(稱作Leading Space)、而距離cell底部11點。
- 右側標籤:寬度彈性、高度固定為21點、距離cell 右側邊緣25點、而距離cell底部11點。
- 圖片版權聲明標籤:寬度彈性、高度固定為21點、距離右側邊緣15點、而距離頂部285點(有效的將其定位在圖像視圖的右下方)。
至少有四種方式可以設定約束條件,但是我們會使用最簡單的方式:以上方的Editor選單來實作。
要設置圖像視圖,選取它之後再選取以下的選單選項:
- Editor -> Align -> Horizontal Center In Container
- Editor -> Pin -> Height
- Editor -> Pin -> Leading Space To Superview
- Editor -> Pin -> Trailing Space To Superview
- Editor -> Pin -> Top Space To SuperView
然後,設定左側標籤:
- Editor -> Pin -> Height
- Editor -> Pin -> Leading Space To Superview
- Editor -> Pin -> Bottom Space To Superview
再來是設定右側標籤:
- Editor -> Pin -> Height
- Editor -> Pin -> Trailing Space To Superview
- Editor -> Pin -> Bottom Space To Superview
然後,設定圖片版權聲明標籤:
- Editor -> Pin -> Height
- Editor -> Pin -> Trailing Space To Superview
- Editor -> Pin -> Bottom Space To Superview
你有看到我們剛剛將一些視圖靠近邊緣,並以一些位置來固定?你的畫面看起來會跟下圖類似。
以Swift來使用客製化表格視圖Cell
好的,我們回到程式部分–介面建構部分就這樣!打開
CatsTableViewController
.swift ,位於初始器init(style: classname:) 處。
在該方法self.parseClassName = className;
的下方處,加入以下兩行程式:
self.tableView.rowHeight = 350
self.tableView.allowsSelection = false
第一行設定列為合適高度,而第二行不允許做cell的選取。
然後,加入以下的程式至viewDidLoad
方法內,在super.viewDidLoad()
上方:
tableView.registerNib(UINib(nibName: "CatsTableViewCell", bundle: nil), forCellReuseIdentifier: cellIdentifier)
這一行有可能會產生一個錯誤。為了解決它,將以下在tableView cellForRowAtIndexPath
方法的這一行移到類別上面,並重新命名其值為「CatCell」。
let cellIdentifier:String = "Cell"
這類別目前的定義如下所示:
class CatsTableViewController: PFQueryTableViewController
{
let cellIdentifier:String = "CatCell"
override init!(style: UITableViewStyle, className: String!)
我們已經將cellIdentifier 常數從local方法範圍移到class範圍,可以在整個類別使用:tableView cellForRowAtIndexPath 以及 viewDidLoad
皆可。
接下來,以下面的程式去取代tableView cellForRowAtIndexPath
的內容:
var cell:CatsTableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? CatsTableViewCell
if(cell == nil) {
cell = NSBundle.mainBundle().loadNibNamed("CatsTableViewCell", owner: self, options: nil)[0] as? CatsTableViewCell
}
if let pfObject = object {
cell?.catNameLabel?.text = pfObject["name"] as? String
var votes:Int? = pfObject["votes"] as? Int
if votes == nil {
votes = 0
}
cell?.catVotesLabel?.text = "\(votes!) votes"
var credit:String? = pfObject["cc_by"] as? String
if credit != nil {
cell?.catCreditLabel?.text = "\(credit!) / CC 2.0"
}
}
return cell
跟我們之前所使用的程式有什麼不同呢?是這樣的:
- 這個cell型態從
PFTableViewCell
變更為CatsTableViewCell
。 - 當cell是nil(也就是它不能被dequeue時),一個新cell從我們剛建立的XIB中建立出來。我們從集合中取得它,並給予目前類別的所有權,然後將其轉型為
CatsTableViewCell
。 - 然後我們檢查是否有存在的物件,並試著去將之前Parse物件的name欄位值指定做為其標籤文字。
- 然後,catVotesLabel與其文字也是一樣。在Parse內的votes欄,不是String型態而是Int型態,因此轉型為Int?。當votes碰巧是空的,我們將其設定為零。然後以一個稱作字串插值(string interpolation)的技巧來將標籤文字設為 …votes。至於圖片版權聲明標籤的內容大致相同。
最後,我們回傳cell。注意是正確的型態!
我們再次執行App。一切都沒問題嗎?沒有bug、閃退、冒煙或者有火焰?很好!
但是,圖像在哪?
從Parse非同步下載圖像
沒有圖像!我們來將它加入吧。在tableView cellForRowAtIndexPath 加入以下的程式,將其加在最後的if陳述(針對圖片版權聲明標籤)之後,return陳述之前。
cell?.catImageView?.image = nil
if var urlString:String? = pfObject["url"] as? String {
var url:NSURL? = NSURL(string: urlString!)
if var url:NSURL? = NSURL(string: urlString!) {
var error:NSError?
var request:NSURLRequest = NSURLRequest(URL: url!, cachePolicy: NSURLRequestCachePolicy.ReturnCacheDataElseLoad, timeoutInterval: 5.0)
NSOperationQueue.mainQueue().cancelAllOperations()
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {
(response:NSURLResponse!, imageData:NSData!, error:NSError!) -> Void in
cell?.catImageView?.image = UIImage(data: imageData)
})
}
}
哇!這又做什麼?好的,技術上我們將Parse url 欄變成NSURL型態的實體。
我們在主執行佇列使用它來開始非同步的NSURLConnection
,下載圖像為NSData
物件。當下載完成之後,一個閉包(closure)開始執行,將下載資料指定為UIImage
,也就是指定給catImageView的image屬性。
這裏我不深入探討,因為上面錯綜複雜的程式跟我們的App不相關。不過,注意以下幾點:
- NSURLConnection的使用很快速,但是可能有點難懂。我們使用很棒的AFNetworking (Objective-C) 或Alamofire (Swift) 函式庫來處理許多複雜的網路資料源。
- Parse允許你儲存圖像在雲端,且可以直接利用,這是ParseUI整合的部分,但是它不能處理外部的 URLs(例如來自Flicker的貓咪圖片)
- 在開始另一個非同步的連結,我們先清除所有主佇列的操作。這可能有點難理解:也就是它從主佇列移除pending或沒有完成的下載。你可以試著移除該行程式並執行App,你會見到所有的圖像混在一起。當它重新使用一個cell時,這個dequeue機制沒有重設任何pending的連結,因而造成所有下載圖像的混亂。
準備好了嗎?再次執行App,看是否正常運作。
再次加碼:做出像Instagram一樣的功能
你已經走到這裡了,真的太棒了!我們的App還欠缺一味?要完成的話需要加入最後的功能:像「Instagram」一樣的功能,也就是當你按圖片兩下,就會投該隻貓咪一票,並加上一個簡潔的小貓爪動畫。
首先,幫貓爪圖片加上outlet至表格視圖cell。加入以下的程式至 CatsTableViewCell
,在其他4個outlet的下面:
@IBOutlet weak var catPawIcon:UIImageView?
然後,加上一個UIImageView
至在介面建構器的CatsTableViewCell.xib,記得怎麼做嗎?
- 在元件庫尋找UIImageView 類別。
- 從元件庫拖曳它至表格視圖cell。
確認拖曳至圖像視圖的中心,調整新的圖像視圖的寬度與高度皆為100點。它的X與Y兩者大約是110點。然後,圖像視圖選取後,加上以下的約束條件。
- Editor -> Pin -> Width
- Editor -> Pin -> Height
- Editor -> Pin -> Top Space To Superview
- Editor -> Align -> Horizontal Center In Container
你可以見到,圖像視圖水平置中,寬度與高度皆固定為100點,跟上方保持固定間距,能確保將其置於貓咪圖像視圖的正中間。
現在,在文件大綱選擇上方元件選取Cats Table View Cell來建立一個outlet連結,然後選取連結檢閱器標籤,並從catPawIcon拖曳一個藍色線至cell中的圖像視圖中。
接下來,下載paw.zip 。這個檔案包含了三個圖檔,一張圖片有三種解析度。我們必須要在可以使用它之前導入它。
首先,將壓縮檔解開。然後打開在Xcode的Images.xcassets 檔。然後,在左側的清單按下右鍵(一個稱作AppIcon的啟動圖像)並點擊New Image Set,或使用左下的「+」按鈕。將剛加入進去的asset重新命名為Paw並打開它。
現在拖曳剛解壓縮的檔案,一個接一個拖曳進去打開的asset 中,要確認檔案有相符:
- paw.png to 1x.
- [email protected] to 2x.
- [email protected] to 3x.
倘若你看不見這些檔案的話別擔心,因為它們全部都是白色的。
然後回到CatsTableViewCell.xib 並選取小的圖像視圖。至屬性檢閱器,從下拉式清單選取正確的Paw圖像,這個白色的貓爪圖像現在應該在cell視圖中能看到了。
最後記得將小的圖像視圖跟catPawIcon outlet連結在一起。
現在,我們回到程式部分。打開在Xcode中的 CatsTableViewCell
。在awakeFromNib
方法的super.awakeFromNib()
之前加入下面的程式。
let gesture = UITapGestureRecognizer(target: self, action:Selector("onDoubleTap:"))
gesture.numberOfTapsRequired = 2
contentView.addGestureRecognizer(gesture)
catPawIcon?.hidden = true
這裏做了兩件事:
- 首先,我們設定一個
UITapGestureRecognizer
,可以讓我們跟任何視圖互動。在這個範例中,我們將它加進去contentView,也就是存放cell的兩個標籤以及兩個圖像視圖的視圖。 它以self做為target,一個選擇onDoubleTap: 方法的selector做為action來初始化。所以,偵測到點擊兩下的動作時,自己(目前類別)的onDoubleTap:
方法便會執行。另外,我們設定點擊數為2,也就是點擊兩下。 - 第二,我們隱藏catPawIcon outlet。
接下來,加入onDoubleTap
方法至類別中,在awakeFromNib()
下:
func onDoubleTap(sender:AnyObject) {
catPawIcon?.hidden = false
catPawIcon?.alpha = 1.0
UIView.animateWithDuration(1.0, delay: 1.0, options:nil, animations: {
self.catPawIcon?.alpha = 0
}, completion: {
(value:Bool) in
self.catPawIcon?.hidden = true
})
}
這樣的方法稱作一個action,並且總是需要一個參數:以AnyObject型態來做為sender。在這個方法,以下的動畫程式會發生:
- 首先,透過設定hidden 為 false將catPawIcon 變成可視。
- 第二,它的alpha(透明度)是設為1.0,也就是可以全部看見。這是在圖像狀態需要重設時必須做的,其動畫結束時的alpha值是為0。
- 然後設定動畫程式。使用
UIView
的一個類別方法,有帶5個參數:動畫時間,動畫之前的延遲時間、一些選項、接著是動畫屬性的閉包,然後是動畫完畢後要執行的閉包。
視覺上,會發生這些動作:
- 重設視圖為可視,並設定透明度(alpha channel)為可視。
- 延遲一秒。
- 在一秒內,動畫的透明度從1變為0。
- 完成後,隱藏圖像。
這個方案最棒的事是它很容易使用:這程式將會完全處理動畫。我們只需要設定它的開始狀態與結束狀態以及區間,而動畫框架(framework)會自行安排狀態以及步驟。技術上我們使用兩個屬性:一個連續alpha值、一個處理貓爪圖像可視性的布林值。
最後,執行這個App並了解是否功能正常。你可以點擊cell兩下,會短暫出現一個貓爪圖示,然後慢慢淡出。
動作正常嗎?非常棒!
以Parse整合投票動作
還剩下一件事要做,就是當執行按兩下動作時,在Parse Cat物件增加votes欄的票數。
但是要如何做呢?
我們要變更的物件是在CatsTableViewController
的tableView cellForRowAtIndexPath 方法的PFObject型態物件。我們不能在表格視圖cell中,也就是點擊兩下動作的地方來存取它。
我們不能移動onDoubleTap
方法,所以我們要建立表格視圖物件與表格視圖cell的關聯性。
採取以下的步驟來實現這個做法:
- 在
CatsTableViewCell
,在類別的上方,outlet底下以這行程式來建立一個新屬性: - 然後,在tableView cellForRowAtIndexPath,在cell==nil陳述大括號的下方,寫下這行程式:
var parseObject:PFObject?
cell?.parseObject = object
我們現在已經建立了一個機制,將物件從cellForRowAtIndexPath
複製到表格視圖cell,讓物件實體可以在CatsTableViewCell
類別取得。
然後,調整CatsTableViewCell
的onDoubleTap 方法,在方法的開始處加入以下的程式:
if(parseObject != nil) {
if var votes:Int? = parseObject!.objectForKey("votes") as? Int {
votes!++
parseObject!.setObject(votes!, forKey: "votes");
parseObject!.saveInBackground();
catVotesLabel?.text = "\(votes!) votes";
}
}
這個程式做以下的工作:
- 檢查
parseObject
是否為空值。 - 試著從
parseObject
取得投票數(votes),將其轉型為optional Int。 - 倘若
votes
不是空值,以++運算符來增加votes
變數值,這跟votes = votes! + 1;
一樣。 - 然後以
setObject
方法將votes
變數指定回去給parseObject
集合。 - 然後呼叫在
parseObject!
的saveInBackground()
方法。這會將目前的物件儲存在背景,然後在適當時機將其寫入Parse雲中。 - 最後,更新它的文字來呈現新的投票數。
嗯,這就是全部的內容!按下 Command-R或者執行按鈕來執行App,確認新功能是否可行。
是不是可以運作?太棒了!
總結
以下是你今天所學習的內容:
- 以Parse來存取雲端中的資料。
- 以Cocoapods來整合Swift專案與Objective-C框架。
- 以介面建構來設定視圖與客製表格視圖cell。
- 從無到有以Swift撰寫整個App。
- Auto Layout與約束條件的實作。
- 使用手勢辨識、optionals、條件式、閉包、屬性、outlet與action。
你可以下載完整Paws專案檔來進一步了解。請使用Xcode 6.3(或以上)的版本來執行這個專案。注意你必須將在AppDelegate.swift檔中的application key與 client key 變更為你自己的。同樣的,記得自己寫程式會更學得更好,更能有所領悟,所以不要只是複製貼上而已。
你對這篇文章有什麼看法呢?可以在以下的評論中留言給我。
原文:Building an Instagram-Like App with Parse and Swift