iOS Apprentice中文版-從0開始學iOS開發(fā)-第十四課

新增待辦事項

你已經學會了在表格里添加新的一行,但是所有這些新添加的行的文本都是一樣的。你現(xiàn)在要對addItem()做出修改,當這個方法被觸發(fā)后打開一個新的界面,可以使用戶自己輸入文本。

新增待辦事項

我們的計劃是:

1、使用故事模版創(chuàng)建新增待辦事項的界面。

2、添加一個文本框,使用戶可以輸入文字。

3、識別用戶點擊的是Cancel還是Done按鈕。

4、以用戶輸入的文本創(chuàng)建一個ChecklistItem對象。

5、將新創(chuàng)建的ChecklistItem對象添加到界面上。

一個新的界面意味著我們需要一個新的視圖控制器,我們從添加一個新的界面到故事模版上開始。

從對象庫中拖拽一個新的Table View Controller(不是View Controller)到故事模版的畫布上。

拖拽一個新的Table View Controller到畫布上

也許你需要適當?shù)目s小顯示比例,可以在畫布上右鍵點擊鼠標選擇zoom選項,或者使用畫布底端的 -100% + 按鈕。(也可以直接在畫布的空白部分直接雙擊,控制顯示比例)

選擇Checklist View Controller上的?按鈕,按住ctrl拖拽到新的這個視圖控制器上。

按住ctrl拖拽?按鈕到新的視圖控制器

放開鼠標后會彈出一個菜單:

彈出菜單

在這個菜單上有許多不同類型的鏈接供你選擇。

我們選擇Show方式連接。

這種類型的鏈接稱作轉場(segue)。

轉場由兩個視圖間的一個大箭頭代表:

橙色粗箭頭指向的藍色箭頭就是代表專場的符號

運行app,看看會發(fā)生什么。

當你點擊?按鈕時,會從右邊滑出一個新的空白的表格。你可以點擊返回按鈕(就是名字是Checklist的那個),回到之前的界面。

點擊?按鈕后出現(xiàn)的新界面

你不需要寫任何代碼,你有一個會自己工作的導航控制器。

注意,現(xiàn)在?按鈕不再會添加新的一行到表格中。轉場取代了按鈕鏈接的動作方法。僅僅是為了以防萬一,你還是需要移除這個按鈕和addItem()之間的鏈接。

選擇?號按鈕,打開鏈接指示器,點擊addItem后面跟隨的小的x符號,就可以刪除這個鏈接了。

移除addItem()方法和?按鈕之間的鏈接

注意一下,在鏈接指示器中同時也顯示了你剛剛添加的代表專場的鏈接(在Triggered Segues分節(jié)下)

你現(xiàn)在有了一個當你一點擊?就會滑動出來的新的table view controller。這正是你想要的:一個可以讓你添加新的待辦事項的界面,但是我們最好使用modal類型的轉場(之前我們選擇的是Show)。

單擊選定那個代表轉場的藍色箭頭。

轉場和其他類型的對象一樣(記住,所有的東西都是對象!),它也有屬于自己的屬性,并且你可以改變它們。

打開屬性檢查器,選擇Kind選項中的:Present Modally。

你可以看到界面有明顯的變化,在新的視圖控制器上,導航欄消失了。這個新的界面不再屬于導航層級的一部分,而是作為一個獨立的界面,當它出現(xiàn)時,它會替代掉原來存在的視圖。

運行app,看看效果。

你會發(fā)現(xiàn),你沒有辦法回到上一個界面了。好像還不如我們剛才那個版本的效果。

我們想要的效果是一個帶有兩個按鈕(一個Cancel,一個Done)的導航欄。(在有些app里是一個Send或者一個Save和一個Cancel),無論我們點擊Cancel還是Done,都會關閉這個界面回到上一個界面,但是只有點擊Done,才能將你做出的修改保存下來。

添加這樣一個帶有兩個按鈕的導航欄最簡單的辦法就是將這個界面嵌入到屬于他自己的導航控制器中。這個步驟就和我們之前做過的一樣:

選擇新的這個table view controller,然后在菜單欄中選擇Editor->Embed In->Navigation Controller.

現(xiàn)在故事模版看起來應該是這個樣子了:

兩個表格視圖都被嵌入到了屬于自己的導航控制器中

新的導航控制器插在兩個表格視圖之間?,F(xiàn)在點擊?按鈕則會通過modal類型轉場到新的導航控制器上。

雙擊最右面的視圖上的導航欄中間部分,將其重命名為Add Item(你也可以在導航控制器的屬性檢查器中設置這個名稱)。

拖拽兩個Bar Button Items到導航欄上,一個在左邊位置,一個在右邊位置。

完成后的樣子

選擇左邊一個按鈕,在屬性檢查器中找到System選項并選擇為:Cancel。

右邊那個按鈕在屬性檢查器中將System Item和Style選項都選擇為Done。

不要直接去改變按鈕的文本。這里的Cancel和Done模式是系統(tǒng)內建的按鈕類型,會直接顯示為Done和Cancel字樣,如果你的iPhone設置不是英語,那么這兩個按鈕上的字會自動翻譯為你所在國家的語言。

運行app,你就可以看到新的這兩個按鈕了。

app中的Cancel和Done按鈕

新的按鈕看起來不錯!但是你還需要告訴它們當它們被點擊后應該做些什么。

??:Xcode也許會給出一個警告?!癙rototype table cells must have reuse identifiers”,不用管它,我們很快會解決這個問題。

自己做一個視圖控制器的對象

這個Cancel和Done按鈕應該起到關閉新增代辦事項界面并且回到主界面的作用,但是目前它們是辦不到的,你點擊這兩個按鈕后什么都不會發(fā)生。

在下一個課程中,你會學習直接在故事模版里實現(xiàn)“后退”轉場,但是眼下你需要通過代碼來實現(xiàn)這個功能,換而言之,你需要將這些按鈕鏈接到它們的動作方法上。

那么你在哪里寫這些方法呢?并不是在ChecklistViewController.swift中,因為這不是處理這些按鈕的視圖控制器。

取而代之的是,你需要專門做一個新的視圖控制器源代碼文件用于新增待辦事項頁面并且將它和你剛設計的這些場景鏈接起來。

右擊工程導航器中的Checklists組(黃色文件夾圖標的那個),并且選擇New File...然后選擇Swift File模版。

將文件名命名為AddItemViewController.swift。這樣就將新的文件添加到工程中來,這個文件中目前除了幾條注釋和一行代碼以外什么都沒有。

添加以下代碼到新文件中:

import UIKit

class AddItemViewController: UITableViewController {
    
}

這行代碼告訴Swift你有了一個新的table view controller的對象,它的名字是AddItemViewController。我們稍后會完善其余的代碼。首先,你要告訴故事模版,這里有了一個新的視圖控制器。

打開故事模版,選擇Add Item視圖(點擊下圖中那個黃色圖標)然后打開身份檢查器,在Custom Class中,輸入AddItemViewController。

這樣就告訴故事模版新增的AddItemViewController對象是用于控制這個這個新的場景的:

將視圖和控制器關聯(lián)起來

千萬不能漏掉這一步!沒有這一步的話,新增的Add Item界面根本不會工作。

在你更改身份檢查器前,確保你選中的是視圖控制器(上圖中半部分被選中的那個黃色圖標)。一個經常出現(xiàn)的錯誤就是沒有正確的選中視圖控制器。

你現(xiàn)在可以在AddItemViewController.swift中實現(xiàn)動作方法了.

添加Cancel()和done()動作方法:

@IBAction func cancel() {
        dismiss(animated: true, completion: nil)
    }
    
    @IBAction func done() {
        dismiss(animated: true, completion: nil)
    }

這樣就可以通過一個小動畫平滑的關閉掉Add Item界面了。

你還需要將Cancel按鈕和cancel()動作鏈接起來,以及把Done按鈕和done()動作鏈接起來。

打開故事模版并且找到Add Item View Controller。按住ctrl拖拽按鈕到黃色的圖標上,并且在彈出的菜單中選擇對應的動作。

將按鈕和動作鏈接起來

運行app試試看。這下點擊Cancel和Done按鈕后,都會關閉當前頁面回到主頁面了。

當你釋放AddItemViewController對象時,會發(fā)生什么呢?當視圖控制器從屏幕上消失時,它的對象就被破壞掉了,這塊內存也就被系統(tǒng)回收利用了。

每一次用戶打開新增代辦事項界面時,app都會生成一個新的實例。這意味著視圖控制器的生命周期僅在和用戶交互期間存在,當交互結束后,就沒有必要保留下來了。

視圖控制器的容器

我說過一個視圖控制器代表一個界面,但是這里確實出現(xiàn)了兩個視圖控制器指向一個界面:一個導航控制器(Navigation Controller)包含了一個表格視圖控制器(Table View Controller)。

導航控制器是一種特殊的視圖控制器,它扮演著其他視圖控制器的容器的角色。它有一個導航欄并且具備簡單的從一個界面跳轉到另一個到作用,通過滑動的方式將界面滑入滑出。本質上講,這個容器包含著這些界面。

導航控制器僅僅是一個包含視圖控制器的容器,它本身并沒有什么功能,它更像是一個控制器的“目錄”。在這里ChecklistViewController是目錄中的第一個界面,AddItemViewCOntroller是目錄中的第二個界面。

還有一個經常被用到的容器就是Tab Bar Controller,我們會在下一個課程中見到它。

在iPad上,使用視圖控制器的容器是一件司空見慣的事情。視圖控制器在iPhone上經常會占滿整個屏幕,而在iPad上它們通常都至占據(jù)屏幕的一部分。

靜態(tài)表格單元(Static table cells)

我們來改變一下新增代辦事項界面。目前這只是一個空的表格和一個導航欄,但是我想讓它看起來像下面這個樣子:

改造后的樣子

打開故事模版并且選擇Add Item View中的Table View。

然后打開屬性檢查器,改變Content的設置為Static Cells。

改變Content設置

如果表格中的節(jié)數(shù)和行數(shù)是確定的且不會發(fā)生變化的情況下,你就可以使用靜態(tài)單元(static cells)。這種情況多數(shù)用于給用戶提供輸入數(shù)據(jù)的界面,就像你正在做的一樣。

你可以在故事模版中直接設計這些行的樣式。對于使用靜態(tài)單元的表格而言,你無須向它提供數(shù)據(jù)源,并且你可以直接將標簽或者其他類型的控件直接通過cell鏈接到視圖控制的輸入上。

你可以看一下左邊的略縮圖面板,這個table view下面現(xiàn)在掛了個Table View Section的對象,并且在該分節(jié)中包含三個Table View Cells。(如果看不到的話,就點擊小三角將層級展開)

一個分節(jié)和三個靜態(tài)單元

選定后兩個cell然后點擊delete刪除它們,我們只需要一個cell就夠了。

再一次選擇Table View(略縮面板中的),并且打開屬性檢查器,將Style設置為Grouped。這下看上去就是我們想要的樣子了。

選擇table view的樣式為grouped

下一步,你要在table view cell中添加一個文本框組件,使用戶可以輸入文字。

拖拽一個Text Field對象到cell中,并且調整下大小。

在text field的屬性檢查器中,將Border Style設置為no border(就是虛線方框的那個圖標)

文本框的屬性設置

運行app,然后點擊?按鈕,就可以打開新增代辦事項界面了。點擊界面上的行,你就可以看到自動彈出了一個鍵盤。

你在任何時候點擊這一行都會激活文本框,然后鍵盤會自動出現(xiàn)。你可以在文本框中輸入文字(在模擬器中,也可以使用Mac的鍵盤輸入)

你現(xiàn)在可以自由輸入文本了

??:如果模擬器中的鍵盤沒有自動出現(xiàn),可以使用command+K組合鍵或者在菜單Hardware-> Keyboard-> Toggle Software Keyboard中打開虛擬鍵盤。你也可以直接使用Mac的鍵盤輸入,即使屏幕上的虛擬鍵盤不可見。如果不行的話,就選擇菜單Hardware-> Keyboard中的Connect Hardware Keyboard選項。

當你正好點擊到文本框的外部,而恰恰在cell的內部時,會發(fā)生什么呢?

看出哪里奇怪了嗎?

這一行變成淺灰色了,因為你選擇了這一行。這并不是我們想要的,所以我們要把這一行禁用掉。

打開AddItemViewController.swift,添加以下方法:

override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        return nil
    }

這又是一個table view的委托方法,當用戶點擊某一行時,table view發(fā)送一個“willSelectRowAt”的委托,它的意思是“嗨!委托,我馬上就要選擇這一行了”

委托通過返回nil這個特殊的值來禁用這一行,它的意思是“對不起,你不能這樣做。”

給發(fā)送者的返回

你已經見過幾次return這個關鍵字了。你在一個方法中使用return來發(fā)送一個值給調用它的方法。

讓我們來了解一下具體的細節(jié):

一個方法調用另一個方法,并且收到一個返回值。

你不能隨心所欲的返回值。這個返回值的數(shù)據(jù)類型必須和方法名稱中->符號后面跟隨的數(shù)據(jù)類型一致。

例如:tableVIew(numberOfRowsInSection)必須返回一個整數(shù)型的值。

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

這樣就是正確的,但是假如你像下面這樣做:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return "1"
    }

編譯器就會給出一個報錯說“1”是一個字符串,因為你用雙引號將它包圍起來了。對人而言,看起來好像是沒啥區(qū)別的,但是Swift并沒有這么大度。數(shù)據(jù)類型必須嚴格對應,否則就會報錯。

目前我們的這個方法是這個樣子的:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

這也是一種有效的返回,因為items是一個數(shù)組,而數(shù)組中的對象個數(shù)必然是整數(shù),所以items.count的返回值就是一個整數(shù)。

而tableView(cellForRowAt)方法則需要返回一個UITableViewCell的對象:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ChecklistItem", for: indexPath)
        
        let item = items[indexPath.row]
        configureText(for: cell, with: item)
        configureCheckmark(for: cell, with: item)
        return cell
    }

這里的局部常量cell包含一個UITableViewCell的對象,所以這個返回也是ok的。

tableView(willSelectRowAt)方法需要返回一個IndexPath對象。然而,我們給它返回了一個nil,意思是沒有對象。我們先具體看一個這個方法:

override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        return nil
    }

-> IndexPath? 這里的問號在Swift中的意思就是也可以返回空值。僅僅在這里有一個問號或者感嘆號的情況下允許返回空值。

nil這個特殊的值代表的就是“沒有值”,但是貫穿整個iOS的SDK這個nil具備多種意思。有時它代表“什么都沒找到”或者“不要做任何事情”。在我們這個方法中它就代表當用戶點擊后著一行不能被選中。

我們如何確定nil在每一個方法中的意思呢?你可以在每一個方法的說明文檔中找到具體的解釋。

對于“willSelectRowAt”而言,iOS的文檔中寫著:

返回值:一個index-path對象用于確認或者修改被選定的行。如果你想要其他cell被選定的話,可以返回一個除給定IndexPath對象之外的任意indexpath。如果你不需要某一行被選定,可以返回nil。

上面的規(guī)則可以這樣理解:

1、返回與你給定的index-path相同的index-path。這樣就可以確定這一行被選定了。

2、返回另一個index-path,這樣就可以通過點擊A行選定B行。

3、返回nil來避免某一行被選中。

所以,請記住,你需要使用return語句返回一個期望的值來退出一個方法。如果你忘記了,那么Xcode會給出一個報錯:“Missing return in a function expect to return(沒有返回期望的值)”

你也已經見識過了,有些方法不需要返回任何東西:

@IBAction func addItem()

以及

 func configureCheckmark(for cell: UITableViewCell,
                        with item: ChecklistItem)

這些方法沒有->符號出現(xiàn),這種方法不需要傳遞值返回給調用者,因此它們也不需要return語句。(你仍然可以通過使用return語句退出這些方法,只是return后不需要跟隨任何值,哪怕是nil)

簡單的說,即使方法沒有要求返回一個具體類型的值,也可以使用return語句。將這個特殊的返回想象為真空狀態(tài),就是不存在任何東西(不要和nil弄混了,nil是一個具體的值)

有時你會看到類似下面的語句:

func methodThatDoesNotReturnValue() -> ()
func anotherMethodThatDoesNotReturnValue() -> Void

第一個方法指定返回一對空的括號,這就代表不需要返回任何值。術語void的意思和()空括號是一樣的。

不過說真的,如果一個方法不需要返回任何值的時候,簡單的把->省略掉就可以了。還有@IBAction永遠不返回值,這是一個規(guī)則。

避免這一行被選定后變成淺灰色,還有一件事要做。雖然目前已經不可能選定這一行了,因為我們剛剛告訴了table view這一行不允許被選擇。

但是,cell還是會被選中,并且呈現(xiàn)淺灰色。即使我們已經使這一行不能被選擇了,但是有時你點擊這一行時,UIKit仍然會將cell重新繪制為淺灰色。因此你最好將它也禁用掉。

打開storyboard,選擇table view cell,然后打開屬性檢查器,將Selection屬性設置為None。

現(xiàn)在運行app,這一行再也不能被選中了。

從文本框中讀取值

你有了一個在cell內的文本框并且可以輸入文字,但是你如何才能將你輸入的文字讀取出來呢?

當用戶輸入結束,你需要取到用戶的輸入內容并且以某種方法放到一個新的CheclistItem對象中并且將其添加到待辦事項的列表里。這就意味著done()動作必須具備引用這個文本框的功能。

在我們之前的課程中你已經知道了如何從你的視圖控制器中引用控件了:使用Outlet。當你在之前的課程中添加outlet時,我教你的方法是在源代碼文件中通過輸入的方式申明@IBOutlet并且在故事模版中制作鏈接。

這次我要給你展示一個魔法,以節(jié)省你寫代碼的時間。你可以使用界面建造器自動完成這些工作,通過按住ctrl拖拽的方式,直接將控件拖入源代碼中。

首先,打開故事模版并且選定Add Item View Controller。然后打開輔助編輯器(Assistant editor),這個按鈕在工具欄中,見下圖。這個按鈕看上去就是兩個相交的圓形:

點擊工具欄中的輔助編輯器按鈕

這時你的屏幕也許會很擁擠,現(xiàn)在一共打開的有五個水平面板。如果你想獲得更多的空間你也許應該關閉工程導航器和實用工具面板。

輔助編輯器在屏幕的右邊打開了一個新的面板。新版面的跳轉欄,就是工具欄下面的那個,應該是叫做Automatic并且輔助編輯器中顯示的應該是AddItemViewController.swift文件中的內容:

輔助編輯器

“ Automatic”的意思是輔助編輯器自動指向你正在編輯的界面對應的文件(Add Item View Controller對應的文件就是AddItemViewController.swift)。

(Xcode有時會犯糊涂,如果輔助編輯器中展現(xiàn)的不是AddItemViewController.swift,那么可以在上圖中橙色大箭頭所示的跳轉欄中點擊選擇這個文件)

這時你的屏幕上一半是故事模版,一半是源代碼文件。在輔助編輯器中選定文本框按住ctrl將文本框拖拽到源代碼文件中:

按住ctrl拖拽文本框到代碼文件中

當你放開鼠標時,會彈出這樣一個窗口:

可以通過這個彈出窗口添加新的outlet

按照下面的指示選擇:

Connection:Outlet

Name:textField

Type:UITextField

Storage:Weak

??:如果Type選項不是UITextField而是UITableViewCell或者UIView,那就是你選擇了錯誤的控件拖拽了過去。
確保你選定的是text field,而不是cell。界面上這兩個控件分的不是很清楚,它倆基本疊在一起。你如你無法正確的點擊選擇到text field,也可以在故事模版中的綱要視圖中直接選擇。

最后點擊Connect,Xcode就會自動為你創(chuàng)建好一個@IBOutlet并且已經鏈接好了text field對象。

在代碼文件中是這個樣子的:

@IBOutlet weak var textField: UITextField!

僅僅是通過拖拽你就成功的將文本框對象和新的成員textField鏈接起來了,多么容易??!

現(xiàn)在,你需要修改done()動作,將文本框的內容打印到Xcode的調試區(qū)域,我們之前使用過這個調試區(qū)域,就是Xcode底部靠右一點的那個面板。這是一種確認是否成功的讀取用戶輸入的簡便方法。

打開AddItemViewController.swift,將done()方法修改為:

@IBAction func done() {
        print("Contents of the text field: \(textField.text!)")
        dismiss(animated: true, completion: nil)
    }

你可以直接在輔助編輯器里完成這一工作。讓故事模版和代碼編輯區(qū)域位于一個屏幕上是很方便的,不過就是太占用屏幕空間了。

運行app,然后點擊?按鈕,然后在文本框中輸入點內容。當你點擊done按鈕時,新增待辦事項的界面應該會被關閉掉并且Xcode會在調試區(qū)域打印出我們剛才寫的內容:

Contents of the text field: hello world

非常棒,這樣做行得通。print()這個方法應該是你老朋友了。它是我們忠誠的排除故障的伙伴。

回憶一下,你可以通過使用 ( ... ) 在字符串中插入一個值。這里你使用了(textField.text!)來打印文本框的內容(我會在之后介紹那個感嘆號的作用)

??:因為iOS 模擬器本身會在調試區(qū)域打印出很多內容,這樣你也許比較難以尋找自己打印的內容,但是幸運的是調試區(qū)域下面有一個過濾器,你可以輸入關鍵字搜索自己的文本信息。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容