The iOS Apprentice2-1 / 2代理

1. Your own todo app

1.1. 最終效果圖


Paste_Image.png

1.2. TableViews 和 NavigationControllers

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

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

1.3. Checklist app的設(shè)計(jì),界面以及界面遷移的設(shè)計(jì)

Paste_Image.png

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è)置為肖像模式。


屏幕快照 2016-05-09 23.15.19.png

Upside down
iphone中支持這種模式,但是一般不用,如果來(lái)電話時(shí),可能聽筒和話筒反了給用戶造成困擾。
但是ipad是可以用這種模式的。

2.2 編輯storyboard

  1. 刪除既有的View Controller.(因?yàn)槲覀円褂锰囟ǖ膙iew Controller-TableViewController)
  2. 在swift 文件中,修改代碼
  • 修改前:class ViewController: UIViewController
  • 修改后:class ChecklistViewController: UITableViewController
  1. 修改swift 文件名為 ChecklistViewController.swift
  2. 將一個(gè)TableViewController拖到storyBoard中,選中TableViewController,將其customeClass的class type 選為 ChecklistViewController .

Controller: The whole screen
Table view: The object that actually draws the list

  1. 將 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)容不同)


Paste_Image.png

Table通過cell顯示數(shù)據(jù),一個(gè)cell關(guān)聯(lián)到一個(gè)row,如上圖。一個(gè)屏幕能顯示6個(gè)cell,但是能夠有100行的數(shù)據(jù)。

  1. 選擇prototype cell,拖動(dòng)一個(gè)label到cell上.
  2. 再次選擇tableViewCell, 在AI(Attribute Inspector)中,將accessory設(shè)置為check mark。(是tableViewCell的一個(gè)屬性)
  3. 將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ù),如下圖

Paste_Image.png

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中

  1. 將label的tag設(shè)置為1000,tag是一個(gè)便于日后檢索的數(shù)字標(biāo)識(shí)符
  2. 將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í)行。

  1. override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath)
  1. 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上。

Paste_Image.png

方法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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容