1. Your own todo app
1.1. 最終效果圖

1.2. TableViews 和 NavigationControllers
- TableView :顯示list用,是iOS開始中最有用的組件。
- NavigationControllers:顯示在界面頂端的導(dǎo)航條,用來(lái)遷移界面。
- 示例圖如下:

上面兩個(gè)組件是iOS開發(fā)中最重要的兩個(gè),也是本系列的重點(diǎn),另外我們也將學(xué)習(xí)如何在界面之間傳遞數(shù)據(jù),這個(gè)問題很是困擾初學(xué)者。
1.3. Checklist app的設(shè)計(jì),界面以及界面遷移的設(shè)計(jì)

2. Playing with table views
本周todo:
- 將一個(gè)table view放到app
- 將數(shù)據(jù)放入table view
- 用戶點(diǎn)擊table 中的行,能夠on/off
2.1 創(chuàng)建 Single View Application
參考文檔創(chuàng)建,注意將朝向設(shè)置為肖像模式。

Upside down
iphone中支持這種模式,但是一般不用,如果來(lái)電話時(shí),可能聽筒和話筒反了給用戶造成困擾。
但是ipad是可以用這種模式的。
2.2 編輯storyboard
- 刪除既有的View Controller.(因?yàn)槲覀円褂锰囟ǖ膙iew Controller-TableViewController)
- 在swift 文件中,修改代碼
- 修改前:
class ViewController: UIViewController - 修改后:
class ChecklistViewController: UITableViewController
- 修改swift 文件名為 ChecklistViewController.swift
- 將一個(gè)TableViewController拖到storyBoard中,選中TableViewController,將其customeClass的class type 選為 ChecklistViewController .
Controller: The whole screen
Table view: The object that actually draws the list
- 將 TableViewController 設(shè)置為Is Initial View Controller,既將該畫面設(shè)置為起始畫面,如果不設(shè)置的話,iOS不知道load那個(gè)viewController,就會(huì)顯示黑畫面。
2.3 深入TableView
TableView:用于顯示list,有兩種狀態(tài),plain(每行顯示相同的內(nèi)容) 和 grouped(每行顯示的內(nèi)容不同)

Table通過cell顯示數(shù)據(jù),一個(gè)cell關(guān)聯(lián)到一個(gè)row,如上圖。一個(gè)屏幕能顯示6個(gè)cell,但是能夠有100行的數(shù)據(jù)。
- 選擇prototype cell,拖動(dòng)一個(gè)label到cell上.
- 再次選擇tableViewCell, 在AI(Attribute Inspector)中,將accessory設(shè)置為check mark。(是tableViewCell的一個(gè)屬性)
- 將Table View Cell的Identifier設(shè)置為ChecklistItem,為TableViewCell設(shè)置reuse Identifier。(cell是row的可視化表現(xiàn),而不是實(shí)際數(shù)據(jù)的, 還需要將數(shù)據(jù)添加到table中)
2.4 源數(shù)據(jù)
// Tableview發(fā)送消息請(qǐng)求數(shù)據(jù)條數(shù),row的數(shù)目
override func tableView(tableView:
UITableView,numberOfRowsInSection section: Int)
-> Int {
return 1
}
// table view想要描繪一個(gè)row時(shí),請(qǐng)求一個(gè)cell
override func tableView(tableView:
UITableView,cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier
("ChecklistItem", forIndexPath: indexPath)
return cell
}
上面兩個(gè)特定的方法,是TableView的data source protocol。
data source是聯(lián)系data和table view的紐帶,一般來(lái)說(shuō)view controller充當(dāng)data source的角色并執(zhí)行這些特定的方法。
TableView需要知道數(shù)據(jù)有幾行和應(yīng)該顯示什么數(shù)據(jù),如下圖

TableView中有幾種方式去生成cells,之前的方式是最簡(jiǎn)單的,總結(jié)如下
- 在storyBoard中為TableView添加一個(gè)prototype cell
- 為prototype cell添加一個(gè)reuse identifier
- 調(diào)用dequeueReusableCellWithIdentifier,生成prototype cell的copy或是回收不再使用的cell。
Index paths,NSIndexpath是table中一個(gè)指向特定row的對(duì)象,有row number 和 section number。section number是用于分組的,這里暫時(shí)用不上。另外,以NS開頭命名的類,屬于Foundation framework,NS表示NextStep。
2.5 將row數(shù)據(jù)放入cells中
- 將label的tag設(shè)置為1000,tag是一個(gè)便于日后檢索的數(shù)字標(biāo)識(shí)符
- 將tableview(cellForRowAtIndexPath)修改為如下
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath)->UITableViewCell{
//創(chuàng)建一個(gè)新的或是回收一個(gè)不用的cell,存入本地常量
let cell = tableView.dequeueReusableCellWithIdentifier(
"ChecklistItem",forIndexPath: indexPath)
// 獲取一個(gè)剛才標(biāo)識(shí)為1000的Label
let label = cell.viewWithTag(1000) as! UILabel
if indexPath.row%5 == 0{
label.text = "1.Walk the dog"
}else if indexPath.row%5 == 1 {
label.text = "2.Brush my teeth"
}
else if indexPath.row%5 == 2 {
label.text = "3.Learn ios dev"
}
else if indexPath.row%5 == 3 {
label.text = "4.Soccer practice"
}
else if indexPath.row%5 == 4 {
label.text = "5.Eat ice cream"
}
return cell
}
用數(shù)據(jù)標(biāo)識(shí)符獲取一個(gè)UI element的引用,而不是用IBOutlet是一個(gè)比較好的小技巧。為什么這里不用IBOutlet來(lái)關(guān)聯(lián)UI element和變量呢,因?yàn)橐粋€(gè)table中有很多cell,每個(gè)cell都有它的label,label不是屬于view controller而是屬于cell的。
運(yùn)行后效果圖如下
有100個(gè)rows,有13個(gè)看得到的cell,如果最下面的cell出來(lái)一半,最上面的出來(lái)一半,那么需要14個(gè)cell,如果滾動(dòng)很快的話,需要更多的臨時(shí)cell。
- rows - 實(shí)際數(shù)據(jù),可以有很多個(gè),本例子中100個(gè)
- cells - 數(shù)據(jù)在屏幕上的呈現(xiàn),有限個(gè)

2.6 點(diǎn)擊rows
添加如下代碼
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .None {
cell.accessoryType = .Checkmark
} else {
cell.accessoryType = .None }
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
checkmark是cell的accessory屬性,所以第一步應(yīng)該找到對(duì)應(yīng)的cell。 注意第一個(gè)語(yǔ)句 只有存在cell的時(shí)候才繼續(xù)執(zhí)行。
- override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath)
- if let cell = tableView.cellForRowAtIndexPath(indexPath)
上述兩者除了名字類似以外,其實(shí)兩個(gè)不同對(duì)象的不同方法,執(zhí)行不同的任務(wù)。第一個(gè)data source方法,如果一個(gè)row要出現(xiàn)在屏幕上,則將一個(gè)新的或是回收的cell交給table。而第二個(gè)也是返回cell,但是是返回一個(gè)已經(jīng)在顯示的cell,不會(huì)生成新的cell。
上述程序中有bug,比如在一個(gè)cell,取消了checkmark,然后將這個(gè)cell移動(dòng)到不可見,然后又將其可見,這個(gè)cell的checkmark又會(huì)被勾選,另外如果其他row回收了這個(gè)cell,這個(gè)row(cell)的checkmark是會(huì)被取消的。
因?yàn)樵谶@里checkmark被設(shè)置為cell的屬性,而cell是循環(huán)利用的,所以依賴cell去設(shè)置checkmark是不對(duì)的,應(yīng)該是設(shè)置在row上。

方法1的第一個(gè)參數(shù)是UITableView對(duì)象,是這些方法被觸發(fā)的對(duì)象,這樣寫的話,就不需要像第一個(gè)系列中用IBOutlet去將message送給tableview了。
方法1的第二個(gè)參數(shù)是個(gè)sectionNumber,方法2和方法3的是index-path。
方法1在其他的語(yǔ)言中,可以用如下方式去描述
Int numberOfRowsInSection(UITableView tableView, Int section) { ...}
在上面的示例中,第二個(gè)參數(shù)有兩個(gè)名稱,第一個(gè)如numberofRowsSection是一個(gè)外部名稱,當(dāng)調(diào)用方法的時(shí)候使用,第二個(gè)名稱是內(nèi)部名稱,在方法內(nèi)部使用。
在swift語(yǔ)言中這種方式很普遍,第一個(gè)參數(shù)只有一個(gè)名稱,其他參數(shù)有多個(gè)名稱。
為什么這里三個(gè)函數(shù)名稱完全一樣,都叫tableview(),其實(shí)參數(shù)也是方法名的一部分,實(shí)際的命名應(yīng)該是如下
tableView(numberOfRowsInSection)
tableView(cellForRowAtIndexPath)
tableView(didSelectRowAtIndexPath)
2.7 代理 delegate
在ios中,代理的概念很常用,一個(gè)對(duì)象經(jīng)常依賴其他對(duì)象去完成特定的任務(wù),這種分離使得系統(tǒng)更簡(jiǎn)潔,因?yàn)槊總€(gè)對(duì)象做它擅長(zhǎng)的部分。
比如TableView并不關(guān)心誰(shuí)是data source,也不關(guān)心app要處理的數(shù)據(jù),它只需要發(fā)送一個(gè)cellForRowAtIndexPath獲取cell既可。同樣,TableView知道何時(shí)row的按下被觸發(fā)了,發(fā)送一個(gè)消息后讓代理來(lái)處理。TableView使用了兩個(gè)代理,UITableViewDataSource用來(lái)將rows放到table,UITableViewDelegate用來(lái)處理row上的點(diǎn)擊。
說(shuō)得挺玄乎,感覺代理就是讓各自做自己擅長(zhǎng)的事情,根據(jù)功能來(lái)定義不同的類,比如我A今天要買票去北京,讓代理B去確認(rèn)票價(jià)和剩余票數(shù),并把結(jié)果告訴A。代碼實(shí)現(xiàn)如下
- 創(chuàng)建一個(gè)協(xié)議,在協(xié)議中構(gòu)建兩個(gè)方法,分別返回票價(jià)和剩余票數(shù) 。
#import <Foundation/Foundation.h>
@protocol TicketDelegate <NSObject>
//返回票價(jià)
- (double)price;
//返回剩余票數(shù)
- (int)remainTicketNumber;
@end
- 代理遵守協(xié)議并實(shí)現(xiàn)協(xié)議中的方法
#import <Foundation/Foundation.h>
#import "TicketDelegate.h"
@interface Agent : NSObject<TicketDelegate>
@end
/**
* 代理實(shí)現(xiàn)協(xié)議中的方法
*/
#import "Agent.h"
@implementation Agent
//返回票價(jià)
- (double)price
{
return 100;
}
//返回剩余票數(shù)
- (int)remainTicketNumber
{
return 10;
}
@end
- A中創(chuàng)建一個(gè)遵守協(xié)議的代理
#import <Foundation/Foundation.h>
#import "TicketDelegate.h"
@interface Person : NSObject
@property (nonatomic,strong) id <TicketDelegate> delegate;
- (void)buyTicket;
@end
#import "Person.h"
@implementation Person
- (void)buyTicket
{
// 叫代理去幫自己買票(詢問一下票價(jià)、詢問一下票的剩余張數(shù))
double price1 = [_delegate price];
int number = [_delegate remainTicketNumber];
NSLog(@"票價(jià)是%f,還剩余%d張",price1,number);
}
@end