我們先來講幾種循環(huán)數(shù)組的方法
首先是我們已經(jīng)用過很多次的for in趋观,像下面這個(gè)樣子:
for category in categories { . . .
這一行的意思是把categories中的每一個(gè)對(duì)象依次放入名為category的臨時(shí)常量中。
然而躏吊,為了得到每個(gè)對(duì)象的index-path摆出,而不是每個(gè)對(duì)象的名稱,你需要使用另一種方法:
for i in 0..<categories.count {
let category = categories[i]
...
}
我們使用半開操作符0..<使臨時(shí)i依次從0到categories.count-1遞增。如果你需要數(shù)組中的索引而不是名稱介袜,這是一種常見的方式。
還有一種方法是使用enumerated()方法出吹,你會(huì)在下一個(gè)課程中見到它遇伞,現(xiàn)在我們先大概了解一下:
for (i,category) in categories.enumerated() {
...
}
回到我們的app,打開storyboard捶牢,拖拽一個(gè)新的Table View Controller到畫布中鸠珠。在身份檢查器中設(shè)置Class為CategoryPickerViewController巍耗。
選擇table view cell,在屬性檢查器中設(shè)置Style為Basic渐排,Identifier為Cell炬太。
選定Category Cell,然后按住ctrl拖拽到這個(gè)新的table view controller上驯耻,然后在轉(zhuǎn)場(chǎng)類型中選擇Selection Segue下的Show亲族。
將這個(gè)轉(zhuǎn)場(chǎng)的Identifier設(shè)置為PickCategory。
注意:如果你的界面中可缚,右邊的table view controller頂部有一個(gè)返回Tag Location選項(xiàng)霎迫,是沒問題的,不知道只作者截圖有問題帘靡,還是Xcode版本升級(jí)導(dǎo)致的知给。
storyboard部分就到此結(jié)束了,我們下面開始代碼部分描姚。
打開LocationDetailsViewController.swift并且添加一個(gè)新的實(shí)例變量categoryName炼鞠。你會(huì)用它來臨時(shí)存儲(chǔ)被選擇的分類名稱。
var categoryName = "No Category"
這個(gè)變量的初始值是 "No Category"轰胁。它同時(shí)也是分類列表中最上面的第一個(gè)選項(xiàng)谒主。
修改一下viewDidLoad(),將categoryName的值放入標(biāo)簽中:
override func viewDidLoad() {
super.viewDidLoad()
descriptionTextView.text = ""
categoryLable.text = categoryName //修改這里
...
最后赃阀,添加轉(zhuǎn)場(chǎng)方法prepare(for:sender:) :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PickCategory" {
let controller = segue.destination as! CategoryPickerViewController
controller.selectedCategoryName = categoryName
}
}
這里只是簡(jiǎn)單的設(shè)置了category picker的屬性selectedCategoryName霎肯。通過這一操作,現(xiàn)在app就有了一個(gè)分類榛斯。
運(yùn)行app观游,實(shí)際看看效果:
嗯,看起來效果不錯(cuò)驮俗。你現(xiàn)在可以選擇一個(gè)分類了懂缕,但是你選擇某一行后不會(huì)自動(dòng)關(guān)閉這個(gè)界面。當(dāng)你點(diǎn)擊返回按鈕后王凑,你選擇的分類也不會(huì)顯示在界面上搪柑。
練習(xí):整個(gè)拼圖中缺少了哪一部分?
答案:CategoryPickerViewController目前沒有任何通訊方式向LocationDetailsViewController返回?cái)?shù)據(jù)索烹,比如用戶選擇了一個(gè)新的分類工碾。
此時(shí)你一定會(huì)恍然大悟,原來如此百姓!你忘記給它一個(gè)委托協(xié)議了渊额。這就是為什么它無法給其他視圖控制器傳遞消息。
確實(shí),一個(gè)委托協(xié)議是個(gè)不錯(cuò)的方法旬迹,但是我想給你展示一個(gè)新的方法火惊,這是storyboard的一個(gè)特色功能,可以達(dá)到和委托協(xié)議相同的效果奔垦,但是工作量要比創(chuàng)建一個(gè)委托協(xié)議小一些屹耐,它叫做:unwind segues(不知道怎么翻譯這個(gè)術(shù)語合適T T)。
如果你想知道storyboard中的的紅色“Exit”圖標(biāo)是什么宴倍,你現(xiàn)在有了你的答案张症,沒錯(cuò)仓技,它就是:unwind segues鸵贬。
regular segue用于打開一個(gè)新的界面,而unwind segue用于關(guān)閉一個(gè)當(dāng)前激活的界面脖捻。聽起來很簡(jiǎn)單阔逼。然而,創(chuàng)建unwind segue的方法不是非常直觀地沮。
這個(gè)Exit圖標(biāo)似乎沒有任何作用嗜浮,試試按住ctrl拖拽cell上去,它不會(huì)形成一個(gè)鏈接摩疑。
首先危融,你要添加一個(gè)特殊類型的動(dòng)作方法。
打開LocationDetailsViewController.swift雷袋,添加下面的方法進(jìn)去:
@IBAction func categoryPickerDidPickCategory(_ segue: UIStoryboardSegue) {
let controller = segue.source as! CategoryPickerViewController
categoryName = controller.selectedCategoryName
categoryLable.text = categoryName
}
因?yàn)檫@個(gè)方法是以@IBAction前綴開頭的吉殃,所以它是個(gè)動(dòng)作方法。但是它和一般的動(dòng)作方法有什么區(qū)別呢楷怒?區(qū)別在于它的參數(shù)蛋勺,是一個(gè)UIStoryboardSegue對(duì)象。
通常鸠删,如果動(dòng)作方法有一個(gè)參數(shù)的話抱完,它應(yīng)該是觸發(fā)這個(gè)動(dòng)作的控件,比如按鈕和滑條刃泡。但是為了創(chuàng)建一個(gè)unwind segue巧娱,你需要將動(dòng)作方法的參數(shù)寫為UIStoryboardSegue。
這個(gè)方法內(nèi)部代碼的意思非常明顯烘贴。你找到是那個(gè)視圖轉(zhuǎn)場(chǎng)到這個(gè)界面來的(就是源界面)家卖,在這里它就是CategoryPickerViewController,然后讀取它的selectedCategoryName屬性庙楚。它正好包含用戶選擇的分類名稱上荡。
打開storyboard。按住ctrl拖拽cell到Exit按鈕上,這次應(yīng)該能夠創(chuàng)建鏈接了:
然后在彈出菜單的Selection Segue分節(jié)下選擇categoryPickerDidPickCategory酪捡,就是你剛才創(chuàng)建的用于unwind segue的動(dòng)作方法的名字叁征。
如果無法創(chuàng)建鏈接,請(qǐng)確定你選中的是cell逛薇,而不是Content View或者其中的Label捺疼。
運(yùn)行app,是不是非常簡(jiǎn)單永罚?好像也不是那么簡(jiǎn)單啤呼,被選擇的分類被忽視掉了...
這是因?yàn)殡m然categoryPickerDidPickCategory()方法看到了selectedCategoryName屬性,但是這個(gè)屬性此時(shí)沒有寫入值呢袱。
你需要一個(gè)機(jī)制官扣,當(dāng)unwind segue轉(zhuǎn)場(chǎng)被觸發(fā)時(shí),你可以把用戶點(diǎn)擊的那一行的分類名稱寫入到selectedCategoryName屬性中羞福。
我想這個(gè)機(jī)制應(yīng)該就是prepare(for:sender:)惕蹄,沒錯(cuò),這個(gè)方法對(duì)各種轉(zhuǎn)場(chǎng)都適用治专。
打開CategoryPickerViewController.swift卖陵,添加prepare(for:sender:)方法:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PickedCategory" {
let cell = sender as! UITableViewCell
if let indexPath = tableView.indexPath(for: cell) {
selectedCategoryName = categories[indexPath.row]
}
}
}
這段代碼看起來就是把被選擇行的相應(yīng)地category name(分類名稱)放入selectedCategoryName屬性中。
這段代碼假設(shè)unwind segue轉(zhuǎn)場(chǎng)的名稱叫做“PickedCategory”张峰,所以你還需要設(shè)置這個(gè)轉(zhuǎn)場(chǎng)的名稱泪蔫。
不幸的事,unwind segue在storyboard中并不可見喘批。沒有一個(gè)普通轉(zhuǎn)場(chǎng)那樣的大大的箭頭撩荣。你只能在略縮面板中選擇它:
選擇unwind segue谤祖,然后打開屬性檢查器婿滓,設(shè)置identifier為PickedCategory。
再次運(yùn)行app粥喜,現(xiàn)在category picker應(yīng)該可以正常工作了凸主。只要你選擇一條分類,界面就會(huì)自動(dòng)關(guān)閉额湘,并且新的分類的名稱也可以顯示在返回的界面中卿吐。
unwind segue非常棒,并且比使用委托協(xié)議要簡(jiǎn)單的多锋华,特別是在我們?cè)O(shè)計(jì)的這個(gè)app中嗡官。
改進(jìn)用戶體驗(yàn)
雖然Tag Location界面已經(jīng)具備了很多功能,但是它還是可以再改進(jìn)一下毯焕。改進(jìn)一些小細(xì)節(jié)衍腥,可以使你的app更加人性化磺樱,并且在競(jìng)爭(zhēng)對(duì)手中脫穎而出。
我們先來看看Description text視圖的設(shè)計(jì):
在text view和cell的邊界之間有10點(diǎn)的距離婆咸,但是因?yàn)樗鼈z的背景都是白色的竹捉,這樣會(huì)使用戶無法分辨text view的起點(diǎn)位置。
有可能會(huì)導(dǎo)致用戶剛好點(diǎn)在邊邊上尚骄,而無法編輯文本块差,這是非常讓人討厭的。你以為你已經(jīng)點(diǎn)到了倔丈,但是其實(shí)沒有點(diǎn)到憨闰,并且沒有任何反饋提示,用戶有可能會(huì)以為這個(gè)app就是垃圾需五,怒刪之鹉动。
所以這里我們要改進(jìn)一下,不管用戶點(diǎn)擊到了這個(gè)cell的任何位置警儒,text view都應(yīng)該被激活训裆,即使用戶正好點(diǎn)到了邊邊上眶根。
在LocationDetailsViewController.swift的// MARK: - UITableViewDelegate注釋后面添加下面的方法:
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if indexPath.section == 0 || indexPath.section == 1 {
return indexPath
} else {
return nil
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 && indexPath.row == 0 {
descriptionTextView.becomeFirstResponder()
}
}
tableView(willSelectRowAt)方法限定了蜀铲,僅前兩個(gè)分節(jié)(section)的cell可以被點(diǎn)擊∈舭伲回憶一下记劝,||操作符是或的意思,所以僅當(dāng)section為0或者1時(shí)族扰,其中的cell可以被點(diǎn)擊厌丑,而其余的cell都是只讀的。
tableView(didSelectRowAt)方法用來處理實(shí)際被選擇的行渔呵。你不需要對(duì)Category或者Add Photo進(jìn)行響應(yīng)怒竿,這些cell是鏈接到轉(zhuǎn)場(chǎng)的。
但是假如用戶點(diǎn)擊了第一個(gè)分節(jié)中的第一行扩氢,那么你立刻激活text view耕驰。&&操作符是與的意思,就是and录豺。
運(yùn)行app朦肘,試試效果,看看點(diǎn)擊cell邊緣双饥,而不是text view內(nèi)部媒抠,能否激活text view(如果模擬器中的小鍵盤沒有自動(dòng)彈出的話,可以使用快捷鍵command + K)
任何你可以挽救用戶體驗(yàn)的工作都是非常值得的咏花。
就text view而言趴生,一旦你激活了小鍵盤,就無法在關(guān)閉它,要知道小鍵盤可是占據(jù)了一半的屏幕苍匆,這會(huì)讓用戶抓狂。
當(dāng)你點(diǎn)擊屏幕中其他位置時(shí)锉桑,讓小鍵盤自動(dòng)關(guān)閉,是非常棒的一個(gè)功能攻柠,實(shí)現(xiàn)起來也不是特別麻煩。
在viewDidLoad()方法的最后面添加下面的語句:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
gestureRecognizer.cancelsTouchesInView = false
tableView.addGestureRecognizer(gestureRecognizer)
gesture recognizer(手勢(shì)識(shí)別器)是非常便利的一個(gè)對(duì)象后裸,它可以識(shí)別點(diǎn)擊和手指的移動(dòng)瑰钮。你只是簡(jiǎn)單的創(chuàng)建一個(gè)gesture recognizer對(duì)象微驶,當(dāng)特定的手勢(shì)被觀察到時(shí),調(diào)用一個(gè)你指定的方法因苹,并且把這個(gè)識(shí)別器添加到視圖中苟耻。
你使用了一個(gè)UITapGestureRecognizer,它可以識(shí)別簡(jiǎn)單的點(diǎn)擊扶檐,還有一些其它的對(duì)象,可以識(shí)別掃動(dòng)智蝠,按壓奈梳,合攏等。
注意一下#selector()關(guān)鍵字:
...target: self, action: #selector(hideKeyboard))...
每當(dāng)手勢(shì)發(fā)生時(shí)攘须,通過#selector告訴UITapGestureRecognizer要被調(diào)用的方法。
這個(gè)模式叫做target-action(目標(biāo)-動(dòng)作)叫挟,并且你已經(jīng)使用過多次了限煞,比如鏈接UIButton,UIButtonItems署驻,以及其它控件的動(dòng)作方法時(shí),其實(shí)用的都是這個(gè)模式瓶蚂。
traget就是接受被發(fā)送消息的對(duì)象,通常就是self瞳别,action就是發(fā)送的消息杭攻。
這里你做的就是,當(dāng)在table view的其它地方出現(xiàn)點(diǎn)擊這個(gè)行為時(shí)馆铁,hideKeyboard消息就會(huì)被發(fā)送,所以你要執(zhí)行一個(gè)方法來響應(yīng)這個(gè)消息锅睛。
打開LocationDetailsViewController.swift添加hideKeyboard()方法埠巨。把它放在viewDidLoad()方法的下面现拒,其實(shí)放在其它地方也可以:
@objc func hideKeyboard(_ gestureRecognizer: UIGestureRecognizer) {
let point = gestureRecognizer.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: point)
if indexPath != nil && indexPath!.section == 0 && indexPath!.row == 0 {
return
}
descriptionTextView.resignFirstResponder()
}
??:在Objective-C中,選擇器(selector)是一種引用Objective-C方法名稱的類型乍构。 在Swift中扛点,Objective-C選擇器由Selector結(jié)構(gòu)表示岂丘,可以使用#selector表達(dá)式來構(gòu)造。 同時(shí)需要向Objective-C傳遞方法名稱铜邮。
這就是在hideKeyboard()方法名稱前加上@objc前綴的作用寨蹋。
本書寫作的時(shí)候Swift版本還是3,不需要添加這個(gè)前綴秸苗,但是Swift4中必須要這樣做运褪,我在后面翻譯的時(shí)候玖瘸,也會(huì)不斷的將Swift4的新特性加進(jìn)去雅倒。
話說Swift的本意是要擺脫Objective-C弧可,但是畢竟iOS框架與Objective-C已經(jīng)相愛相殺20余年了,所以你懂的殖演。年鸳。
無論何時(shí),用戶點(diǎn)擊table view內(nèi)任何地方時(shí)彼棍,手勢(shì)識(shí)別器就會(huì)調(diào)用這個(gè)方法膳算。方便的是,它也傳遞了一個(gè)引用作為參數(shù)給自己华匾,它可以讓你向手勢(shì)識(shí)別器詢問點(diǎn)擊的發(fā)生位置机隙。
gestureRecognizer.location(in: tableView)方法返回一個(gè)CGPoint數(shù)。CGPoint是你在UIKit中隨處可見的一種結(jié)構(gòu)旭旭。它包含兩個(gè)字段忌穿,x和y霎烙,用于描述界面上的位置闺阱。
使用這個(gè)CGPoint數(shù)灶似,你就可以向table view詢問目前是位置上是哪個(gè)index-path模庐。這非常重要僵朗,因?yàn)楫?dāng)用戶點(diǎn)擊text view內(nèi)部時(shí)屑彻,你不能把鍵盤隱藏掉顶吮。而如果點(diǎn)擊的是其它地方悴了,你則需要隱藏這個(gè)鍵盤。
練習(xí):這里的if語句你熟悉嗎湃交?能不能試著解釋一下呢搞莺?
答案:用戶可能會(huì)在table view內(nèi)部輕擊,而不在單元格內(nèi)迈喉,例如在兩個(gè)部分之間的某個(gè)位置或部分text view上温圆。 在這種情況下,indexPath將是nil得运,所以此時(shí)IndexPath是個(gè)可選型锅移,需要使用if let或者感嘆號(hào)來對(duì)它進(jìn)行解包。
只有當(dāng)index-path不是section為0和row為0時(shí)瞬女,你才隱藏小鍵盤努潘。
??:如果一個(gè)可選型有可能為nil的話坤学,你不能對(duì)其強(qiáng)制解包,除非你愿意承擔(dān)app崩潰掉的風(fēng)險(xiǎn)压怠。所以上面方法中的indexPath!.section和indexPath!.row看起來很危險(xiǎn)飞苇,但是這里是沒問題的蜗顽,因?yàn)榍懊嬗幸粋€(gè)短路語句雇盖,就是indexPath != nil栖忠,如果indexPath為nil則整個(gè)if條件為假,其中的語句不會(huì)執(zhí)行狸相。
另外一個(gè)可選的寫法是:
if indexPath == nil ||
!(indexPath!.section == 0 && indexPath!.row == 0) {
descriptionTextView.resignFirstResponder()
}
能看明白嗎捐川?這個(gè)if語句和之前的意思完全相反,但目的是相同的将谊,這個(gè)if語句使用了||或操作符渐白,意思是indexPath為nil或者index-path不是section為0和row為0時(shí),隱藏小鍵盤(感嘆號(hào)出現(xiàn)在表達(dá)式前面時(shí)栋齿,是非的意思)襟诸。
熟練的使用各種邏輯操作符歌亲,是你編程生涯中很重要的一個(gè)環(huán)節(jié),幸好它不是很難惋鸥。
當(dāng)然你也可以用if let安全的解包悍缠,像下面這個(gè)樣子:
if let indexPath = indexPath, indexPath.section != 0 &&
indexPath.row != 0 {
return
}
descriptionTextView.resignFirstResponder()
我只是給你簡(jiǎn)單的展示了一下if語句的多樣性。你可以選擇一個(gè)你喜歡的一直用下去就好了滤港。
運(yùn)行app趴拧,點(diǎn)擊text view會(huì)自動(dòng)彈出一個(gè)小鍵盤山叮,如果沒有就用快捷鍵command + K添履。
我們還可以實(shí)現(xiàn)缝龄,當(dāng)用戶進(jìn)行滾動(dòng)操作的時(shí)候,也自動(dòng)隱藏小鍵盤瞎饲。
打開storyboard炼绘,選擇Tag Location界面中的table view。在屬性檢查器中找到Keyboard驮捍,選擇其中的Dismiss on drag選項(xiàng)脚曾,這樣就可以實(shí)現(xiàn)了,簡(jiǎn)單吧珊泳。
如果設(shè)置沒有生效的話色查,就在真實(shí)設(shè)備上試試撞芍,模擬器中的虛擬鍵盤有時(shí)候不是很聰明。
同時(shí)試試Dismiss interactively選項(xiàng)验毡,看看那個(gè)你更加喜歡愉镰。