來(lái)個(gè)深加工
在你寫(xiě)代碼將文本作為一條新的紀(jì)錄插入列表之前,讓我們先來(lái)改進(jìn)一下新增待辦事項(xiàng)頁(yè)面的工作方式和設(shè)計(jì)。
例如:如果可以自動(dòng)為用戶(hù)彈出鍵盤(pán),而不是當(dāng)用戶(hù)點(diǎn)擊后才彈出鍵盤(pán),這樣體驗(yàn)會(huì)好一些。
為了達(dá)到這個(gè)目的,需要在AddItemViewController.swift中添加一個(gè)方法viewWillAppear():
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textField.becomeFirstResponder()
}
視圖控制器在即將可視化之前接收viewWillAppear()的消息,這正是激活文本框的完美時(shí)機(jī)。我們?cè)谶@里發(fā)送becomeFirstResponder()消息。
如果你是在其他平臺(tái)上做這個(gè)事,通常被稱(chēng)為“控制聚焦”。在iOS術(shù)語(yǔ)中,這種控制方法稱(chēng)為“第一響應(yīng)(first responder)”
運(yùn)行app然后點(diǎn)擊?號(hào)按鈕,你可以看到鍵盤(pán)會(huì)自動(dòng)滑出來(lái)了。
(再說(shuō)一次,如果模擬器上鍵盤(pán)沒(méi)有自動(dòng)滑出的話(huà),可以通過(guò)command+K組合鍵喚醒鍵盤(pán))
像這樣的小細(xì)節(jié)通常會(huì)帶來(lái)很好的用戶(hù)體驗(yàn)。比起必須點(diǎn)擊文本框才能打字,現(xiàn)在的做法明顯快了不少。在這個(gè)日新月異的年代中,人們使用產(chǎn)品時(shí)不會(huì)有太多的耐心。比如一點(diǎn)點(diǎn)的瑕疵都會(huì)讓用戶(hù)轉(zhuǎn)移到你競(jìng)爭(zhēng)對(duì)手的產(chǎn)品上去。我總是花大量的精力來(lái)使自己的app盡可能的完美。
所以,我們來(lái)繼續(xù)改進(jìn)一下輸入框。
打開(kāi)故事模版并且選定文本框,進(jìn)入到屬性檢查器并且按照下面的列表進(jìn)行設(shè)置:
1、Placeholder:Name of the Item
2、Font:System 17
3、Adjust to Fit:Uncheck this
4、Capitalization:Sentence
5、Return Key:Done

這里有一些選項(xiàng)可以使你配置文本框被激活時(shí)的鍵盤(pán)的屬性。
例如,如果這個(gè)文本框僅可以輸入數(shù)值的話(huà),你可以設(shè)置Keyboard Type為Number Pad。如果這是一個(gè)用于輸入Email地址的文本框,你就可以將Keyboard Type為E-mail Address。對(duì)我們這個(gè)app而言,默認(rèn)的類(lèi)型就正好符號(hào)要求,因?yàn)橛脩?hù)可以隨意輸入自己將要做的事情。
你也可以改變鍵盤(pán)上回車(chē)鍵的名稱(chēng)。默認(rèn)的是“return”,這里我們改為了“Done”。這只是按鈕上的標(biāo)題文本,點(diǎn)擊這個(gè)“Done”并不會(huì)自動(dòng)關(guān)閉當(dāng)前界面,你還需要將鍵盤(pán)上的“Done”按鈕指向到相同的動(dòng)作上,這樣鍵盤(pán)上的Done按鈕才能和導(dǎo)航欄上的Done按鈕起到相同的作用。
選定text field然后打開(kāi)鏈接檢查器,拖拽Did End on Exit事件到視圖控制器,并且選擇done動(dòng)作。
如果你的輔助編輯器還沒(méi)關(guān)的話(huà),也可以直接拖拽到源代碼的done()方法上。

查看done動(dòng)作的鏈接可以點(diǎn)擊done()方法左邊,屏幕邊緣上的那個(gè)小圓圈,點(diǎn)擊后會(huì)彈出一個(gè)窗口顯示done()方法的鏈接內(nèi)容。
運(yùn)行app,點(diǎn)擊鍵盤(pán)上的Done按鈕,這時(shí)界面會(huì)自動(dòng)關(guān)閉,并且在調(diào)試區(qū)域打印出我們預(yù)置的文本信息。

驗(yàn)證用戶(hù)的輸入并且確保它們被接收到了,是必不可缺的一個(gè)步驟。例如,用戶(hù)瞬間點(diǎn)了一下Done按鈕但是什么都沒(méi)輸入會(huì)發(fā)生什么。
在主界面的列表中,新增一個(gè)空白的什么都沒(méi)有的行,可一點(diǎn)也不好,為了避免這件事的發(fā)生你需要在用戶(hù)什么都沒(méi)有輸入前,禁止done按鈕的作用。
當(dāng)然,你需要同時(shí)處理兩個(gè)done按鈕,鍵盤(pán)上的一個(gè),導(dǎo)航欄上的一個(gè)。讓我們先著手處理鍵盤(pán)上的done按鈕,這個(gè)稍微簡(jiǎn)單一些。
選中文本框,打開(kāi)屬性檢查器,選中Auto-enable Return Key。
就這樣,現(xiàn)在你運(yùn)行app,什么也不輸入,點(diǎn)擊鍵盤(pán)上的Done按鈕是不會(huì)起作用的,試試看!

對(duì)于導(dǎo)航欄上的Done按鈕,工作就復(fù)雜一些了。你需要監(jiān)聽(tīng)每次一次用戶(hù)敲擊鍵盤(pán)后,文本框中的內(nèi)容是否為空。如果為空,則禁用Done按鈕。
如果用戶(hù)的本身是點(diǎn)錯(cuò)了,可以通過(guò)Cancel按鈕退出界面,但是用戶(hù)點(diǎn)擊Done按鈕時(shí),必須保證文本框中有輸入的內(nèi)容。
對(duì)于文本框而言,有兩種輸入方法,敲擊鍵盤(pán)輸入和復(fù)制黏貼,你需要給視圖控制器一個(gè)委托用于文本框。
當(dāng)文本框發(fā)送事件到這個(gè)委托時(shí),你就可以知道文本框的情況了。這個(gè)委托存在于AdditemViewController內(nèi),可以對(duì)文本框的事件進(jìn)行響應(yīng),并且做出合適的動(dòng)作。
一個(gè)視圖控制器允許委托多個(gè)對(duì)象。AdditemViewController已經(jīng)有了一個(gè)委托和一個(gè)數(shù)據(jù)源用于UITableView,因?yàn)樗緛?lái)就是一個(gè)UITableViewController?,F(xiàn)在它要增加一個(gè)委托用于text field的對(duì)象,UITextfiled。
這是兩種不同類(lèi)型的委托并且你要使視圖控制同時(shí)執(zhí)行它們的規(guī)則。關(guān)于委托的更多內(nèi)容我們會(huì)在下一個(gè)課程中講解。
如何定義一個(gè)委托
在iOS系統(tǒng)的SDK里,委托無(wú)處不在,所以我們最好記住它總是由三個(gè)步驟完成。
1、你申明自己有能力成為一個(gè)委托——成為UITextField的委托你需要將UITextFieldDelegate寫(xiě)到視圖控制器的類(lèi)的聲明中去。這樣就告訴了編譯器這個(gè)視圖控制器可以處理來(lái)自文本框的消息。
2、你讓那個(gè)有問(wèn)題的對(duì)象知道視圖控制器愿意成為它的委托——在我們這個(gè)例子里,這個(gè)對(duì)象是UITextField。如果你忘記了告訴文本框它有一個(gè)委托,則這個(gè)文本框永遠(yuǎn)不會(huì)發(fā)送任何消息。
3、執(zhí)行委托方法——如果你沒(méi)有對(duì)你發(fā)出的消息進(jìn)行響應(yīng),那么委托根本不會(huì)成立。
通常,委托方法是可選的,所以你不需要執(zhí)行全部的委托方法。例如:UITextFieldDelegate實(shí)際上聲明了七個(gè)方法但是你僅僅關(guān)心textField(shouldChangeCharactersIn, replacementString)這一個(gè)。
打開(kāi)AddItemViewController.swift,添加UITextFieldDelegate到類(lèi)的聲明中。
class AddItemViewController: UITableViewController,UITextFieldDelegate
現(xiàn)在視圖控制器好比在說(shuō):“我可以做為text filed對(duì)象的委托了”
你同時(shí)也需要讓text field知道這個(gè)委托的存在。
打開(kāi)故事模版并且選定text field。
這里有好幾種方法鏈接text field的委托outlet到視圖控制器。我比較喜歡從鏈接檢查器拖拽委托到視圖控制器(黃色圖標(biāo)那個(gè),見(jiàn)下圖):

你同時(shí)還需要添加一個(gè)outlet到導(dǎo)航欄的Done按鈕上,如此你就可以從視圖控制器內(nèi)部向它發(fā)送消息,以達(dá)到禁用它的目的。
打開(kāi)輔助編輯器,并且確保輔助編輯界面展示的是AddItemViewController.swift。
選定導(dǎo)航欄的Done按鈕并且按住ctrl拖拽它到輔助編輯窗口的swift文件中去,給這個(gè)新的outlet命名為doneBarButton。
這是swift文件中應(yīng)該增加了這樣一行:
@IBOutlet weak var doneBarButton: UIBarButtonItem!
在AddItemViewController.swift的底部添加如下方法:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String)-> Bool {
let oldText = textField.text! as NSString
let newText = oldText.replacingCharacters(in: range, with: string) as NSString
if newText.length > 0 {
doneBarButton.isEnabled = true
} else {
doneBarButton.isEnabled = false
}
return true
}
這是UITextField的委托方法之一。每次用戶(hù)修改文本后它都會(huì)被調(diào)用,無(wú)論是輸入還是黏貼復(fù)制。
首先,你判斷了新的文本內(nèi)容:
let oldText = textField.text! as NSString
let newText = oldText.replacingCharacters(in: range, with: string) as NSString
委托方法textField(shouldChangeCharactersIn, replacementString)不能直接給出關(guān)于新文本的內(nèi)容,只有文本發(fā)生變更的時(shí)候才可以。
你需要通過(guò)讀取文本框的內(nèi)容來(lái)計(jì)算新的文本是什么,并且用它自己替換自己。這樣你才能得到一個(gè)新的字符串對(duì)象,并且將它存儲(chǔ)在newText常量中。
NSString與String
文本字符串在swift中的數(shù)據(jù)類(lèi)型為String。但是在上面的方法中你使用了某種叫做NSString的東西。這兩者有什么區(qū)別呢?
NSString是Object-C語(yǔ)言中用于存儲(chǔ)文本的對(duì)象。說(shuō)實(shí)在的,它比Swift中的String要強(qiáng)大而且簡(jiǎn)便。
然而,Swift有一個(gè)絕招:String和NSString是“橋接”的,這就是說(shuō)你可以用NSString代替String。這里,你想要使用屬于NSString的方法replacingCharacters(in:with:),所以你必須使swift知道這個(gè)文本是NSString類(lèi)型的,而不是String。
關(guān)鍵字as NSString的作用就是通知swift這個(gè)oldText是一個(gè)NSString類(lèi)型的常量。如果你沒(méi)有這樣做的話(huà),swift會(huì)根據(jù)類(lèi)型推定斷定oldText是一個(gè)String對(duì)象,這樣就不能使用replacingCharacters(in:with:)方法了。
順便說(shuō)一下,String并不是唯一一個(gè)和Object-C有橋接的類(lèi)型,另一個(gè)例子是數(shù)組(Array)可以橋接到Object-C的NSArray上去。因?yàn)閕OS的架構(gòu)并不完全是由swift寫(xiě)成的,所以O(shè)bject-C的退休時(shí)間延后了。
當(dāng)你讀取到了新的文本之后,你通過(guò)計(jì)算它的長(zhǎng)度檢查它是否為空,并且來(lái)禁用或者啟用Done按鈕:
if newText.length > 0 {
doneBarButton.isEnabled = true
} else {
doneBarButton.isEnabled = false
}
運(yùn)行app并且在文本框中敲一些內(nèi)容,然后刪除這些內(nèi)容,Done按鈕就被禁用了。
唯一的問(wèn)題是:在最初你還什么都沒(méi)輸入的時(shí)候Done按鈕是可用的。這樣相當(dāng)于我們之前的工作都白做了,這是不允許的,所以我們來(lái)一起修復(fù)這個(gè)問(wèn)題。
打開(kāi)故事模版,選定Done按鈕并且打開(kāi)屬性檢查器,取消選定Enabled選框。
現(xiàn)在一開(kāi)始的時(shí)候Done按鈕就是禁用狀態(tài)了。

最后我們來(lái)優(yōu)化一下剛才的代碼:
用下面這一行替換掉if語(yǔ)句
doneBarButton.isEnabled = (newText.length > 0)
之前的if語(yǔ)句是這樣的:
if newText.length > 0 {
// 長(zhǎng)度大于0的情況
} else {
// 長(zhǎng)度小于等于0的情況
}
你通過(guò)檢查條件newText.length > 0來(lái)判斷是否禁用Done按鈕,如果大于0的話(huà)啟用,否則則禁用。
注意一下這個(gè)if語(yǔ)句翻譯過(guò)來(lái)其實(shí)是:如果條件為真則isEnable為真,如果條件為假則isEnable為假。換而言之,isEnable不是為真就是為假。這樣其實(shí)就可以簡(jiǎn)化為:
doneBarButton. isEnable = the result of the condition
就是我們剛才看到的:
doneBarButton.isEnabled = (newText.length > 0)
其實(shí)這里的括號(hào)都沒(méi)有存在的必要,因?yàn)橹挥幸粋€(gè)運(yùn)算,所以不存在優(yōu)先級(jí)。我們可以更加簡(jiǎn)化為:
doneBarButton.isEnabled = newText.length > 0
然而,這樣做會(huì)使可讀性變差,所以我總是習(xí)慣加上括號(hào)。
我們順便來(lái)簡(jiǎn)單介紹一下Swift中的比較運(yùn)算符:
大于 >
< 小于
= 大于等于
<= 小于等于
== 相等
!= 不相等
記住這個(gè)小把戲,無(wú)論何時(shí)你看到這個(gè)結(jié)構(gòu):
if some condition {
something = true
} else {
something = false
}
都可以替換為:
something = (some condition)
在實(shí)際的操作中你使用那個(gè)版本都行。我比較喜歡簡(jiǎn)短的這種,專(zhuān)業(yè)的人都喜歡這種方式。你需要記住的就是當(dāng)比較運(yùn)算符總是返回true和false的時(shí)候,你都可以用簡(jiǎn)短的方式表達(dá)它。