本文是Intermediate iOS 11 Programming with Swift 4系列?的 第 二 篇.
如果您想在UITableView中顯示大量的記錄,您最好重新考慮如何顯示數(shù)據(jù)的方法祭衩。隨著行數(shù)的增加灶体,表視圖變得難以處理。改進(jìn)用戶體驗(yàn)的一種方法是將數(shù)據(jù)組織成部分掐暮。通過將相關(guān)數(shù)據(jù)分組在一起蝎抽,可以為用戶提供更好的訪問方式。
此外路克,還可以在表視圖中實(shí)現(xiàn)索引列表樟结。索引表視圖或多或少與純樣式表視圖相同。惟一的區(qū)別是它在表視圖的右邊包含一個(gè)索引精算。索引表在iOS應(yīng)用程序中非常常見瓢宦。最著名的例子是iPhone的內(nèi)置聯(lián)系人應(yīng)用。通過提供索引滾動灰羽,用戶可以立即訪問表的特定部分驮履,而無需滾動每個(gè)部分鱼辙。
讓我們看看如何向一個(gè)簡單的表應(yīng)用程序中添加節(jié)和索引列表。如果您對UITableView實(shí)現(xiàn)有基本的了解玫镐,那么添加節(jié)和索引列表并不太難倒戏。基本上恐似,您需要處理UITableViewDataSource協(xié)議中定義的這些方法:
numberofSections (in:)?方法 - 返回表視圖中所有的部分杜跷。通常,我們將section的數(shù)量設(shè)置為1矫夷。如果你想要有多個(gè)部分葛闷,把這個(gè)值設(shè)置為大于1的數(shù)字.
tableView(_ :titleForHeaderInsection:) 方法 - 返回不同部分的標(biāo)題。如果您不喜歡為部分分配標(biāo)題双藕,則此方法是可選的.
tableView(_ : numberOfRowsInSection:)? 方法 - 返回特定部分中的行總數(shù).
tableView(_ : CellForRowAt:)?方法——如果您知道如何在UITableView中顯示數(shù)據(jù)孵运,那么這種方法對您來說不應(yīng)該是陌生的。它返回特定部分的表數(shù)據(jù).
sectionIndexTitles( for :)??方法——返回出現(xiàn)在表視圖右邊的索引列表中的索引標(biāo)題蔓彩。例如,您可以返回包含從a到Z值的字符串?dāng)?shù)組.
tableView(_ : sectionForSectionIndexTitle: at: )?方法——返回當(dāng)用戶點(diǎn)擊特定索引時(shí)驳概,表視圖應(yīng)該跳轉(zhuǎn)到的部分索引.
沒有比給你看一個(gè)例子更好的解釋實(shí)現(xiàn)的方法了赤嚼。和往常一樣,我們將構(gòu)建一個(gè)Simple App顺又,它將使您對索引列表實(shí)現(xiàn)有更好的理解.
Demo App
首先更卒,讓我們快速瀏覽一下我們將要構(gòu)建的演示應(yīng)用程序。這是一個(gè)非常簡單的應(yīng)用程序稚照,在標(biāo)準(zhǔn)的表格視圖中顯示動物列表蹂空。這個(gè)應(yīng)用程序并沒有列出所有的動物,而是將它們分組到不同的區(qū)域果录,并顯示一個(gè)索引列表上枕,以便快速訪問.? 下面的屏幕截圖顯示了演示應(yīng)用程序的最終交付.
下載Xcode項(xiàng)目模板
演示的重點(diǎn)是實(shí)現(xiàn)部分和索引列表.
因此,您可以從這里下載項(xiàng)目模板.這個(gè)模板已經(jīng)包含了你需要的一切弱恒。如果構(gòu)建模板辨萍,您將有一個(gè)應(yīng)用程序在表視圖中顯示動物列表(但沒有節(jié)和索引)。稍后返弹,我們將修改應(yīng)用程序锈玉,將數(shù)據(jù)分組到節(jié)中,并向表中添加索引列表.
顯示部分UITableView
好的,讓我們開始吧义起。如果您打開IndexTableDemo項(xiàng)目拉背,動物數(shù)據(jù)將在數(shù)組中定義:
let animals = ["Bear", "Black Swan", "Buffalo", "Camel", "Cockatoo", "Dog", "Donkey", "Emu", "Giraffe", "Greater Rhea", "Hippopotamus", "Horse", "Koala", "Lion", "Llama", "Manatus", "Meerkat", "Panda", "Peacock", "Pig", "Platypus", "Polar Bear", "Rhinoceros", "Seagull", "Tasmania Devil", "Whale", "Whale Shark", "Wombat"]
我們會根據(jù)動物名字的第一個(gè)字母將數(shù)據(jù)分成幾個(gè)部分。有很多方法可以做到這一點(diǎn)默终。一種方法是用字典手工替換動物數(shù)組椅棺,如下所示:
let animals: [String: [String]] = ["B" : ["Bear", "Black Swan", "Buffalo"],
? ? ? ? "C" : ["Camel", "Cockatoo"],
? ? ? ? "D" : ["Dog", "Donkey"],
? ? ? ? "E" : ["Emu"],
? ? ? ? "G" : ["Giraffe", "Greater Rhea"],
? ? ? ? "H" : ["Hippopotamus", "Horse"],
? ? ? ? "K" : ["Koala"],
? ? ? ? "L" : ["Lion", "Llama"],
? ? ? ? "M" : ["Manatus", "Meerkat"],
? ? ? ? "P" : ["Panda", "Peacock", "Pig", "Platypus", "Polar Bear"],
? ? ? ? "R" : ["Rhinoceros"],
? ? ? ? "S" : ["Seagull"],
? ? ? ? "T" : ["Tasmania Devil"],
? ? ? ? "W" : ["Whale", "Whale Shark", "Wombat"]]”
在上面的代碼中犁罩,我們把動物數(shù)組變成了字典。動物名字的第一個(gè)字母被用作鑰匙土陪。與相應(yīng)鍵相關(guān)聯(lián)的值是一個(gè)動物名稱數(shù)組昼汗。
我們可以手動創(chuàng)建字典,但是如果我們可以通過編程的方式從animals數(shù)組中創(chuàng)建索引鬼雀,那不是很好嗎?讓我們看看怎么做顷窒。
首先,在AnimalTableViewController類中聲明兩個(gè)實(shí)例變量:
var animalsDict = [String: [String] ] ()
var animalSectionTitles = [String] ()
我們初始化用于存儲動物的空字典和用于存儲表的節(jié)標(biāo)題的空數(shù)組源哩。章節(jié)標(biāo)題是動物名字的首字母(如B)鞋吉。
因?yàn)槲覀兿霃腶nimals數(shù)組生成一個(gè)字典,所以我們需要一個(gè)helper方法來處理生成励烦。在AnimalTableViewController類中插入以下方法:
在此方法中谓着,我們遍歷動物數(shù)組中的所有項(xiàng)。對于每一項(xiàng)坛掠,我們首先提取動物名字的第一個(gè)字母赊锚。要獲取特定位置的索引(例如string . index),您必須向字符串本身詢問startIndex屉栓,然后調(diào)用index方法以獲得所需的位置舷蒲。在本例中,目標(biāo)位置為1友多,因?yàn)槲覀冎粚Φ谝粋€(gè)字符感興趣牲平。
在Swift 3中,您使用字符串的substring(to:)方法來獲取一個(gè)新字符串域滥,該字符串包含直到給定索引為止的字符∽菔粒現(xiàn)在,在Swift 4中启绰,該方法已被棄用昂儒。相反,您可以使用如下這樣的下標(biāo)將字符串分割為子字符串:
let animalKey = String(animal[..<firstLetterIndex])
aimal[..< firstLetterIndex]? ?將動物字符串切片到指定的索引酬土。在上面的例子中荆忍,它意味著提取第一個(gè)字符。您可能想知道為什么我們需要使用字符串初始化來包裝返回的子字符串撤缴。在Swift 4中刹枉,當(dāng)您將一個(gè)字符串分割為一個(gè)子字符串時(shí),您將得到一個(gè)子字符串實(shí)例屈呕。它是一個(gè)臨時(shí)對象微宝,與原始字符串共享其存儲。為了將子字符串實(shí)例轉(zhuǎn)換為字符串實(shí)例虎眨,需要使用String()對其進(jìn)行包裝.
如前所述蟋软,動物名字的第一個(gè)字母被用作字典的鍵镶摘。字典的值是該特定鍵的動物數(shù)組。因此岳守,一旦我們獲得了這個(gè)鍵凄敢,我們要么創(chuàng)建一個(gè)新的動物數(shù)組,要么將這個(gè)項(xiàng)目附加到一個(gè)現(xiàn)有的數(shù)組中湿痢。這里我們展示了前四次迭代的animalsDict值:
迭代 #1: animalsDict["B"] = ["Bear"]
迭代?#2: animalsDict["B"] = ["Bear", "Black Swan"]
迭代?#3: animalsDict["B"] = ["Bear", "Black Swan", "Buffalo"]
迭代?#4: animalsDict["C"] = ["Camel"]
在完全生成animalsDict之后涝缝,我們可以從字典的鍵中檢索section標(biāo)題。
要檢索字典的鍵譬重,只需調(diào)用keys方法拒逮。但是,返回的鍵是無序的臀规。Swift的標(biāo)準(zhǔn)庫提供了一個(gè)名為sort的函數(shù)滩援,該函數(shù)根據(jù)所提供的排序閉包的輸出返回已知類型的值的排序數(shù)組。
閉包接受相同類型的兩個(gè)參數(shù)(在本例中是字符串)塔嬉,并返回一個(gè)Bool值玩徊,以聲明第一個(gè)值在第一個(gè)值被排序后是應(yīng)該出現(xiàn)在第二個(gè)值之前還是之后。如果第一個(gè)值出現(xiàn)在第二個(gè)值之前谨究,它應(yīng)該返回true佣赖。
編寫排序閉包的一種方法是:
animalSectionTitles = animalSectionTitles.sorted( by: { (s1:String, s2:String) -> Bool in
? ? ? ? ? ? return s1 < s2
? ? ? ? })
您應(yīng)該非常熟悉閉包表達(dá)式語法。在閉包的主體中记盒,我們比較兩個(gè)字符串值。如果第二個(gè)值大于第一個(gè)值外傅,則返回true纪吮。例如,s1的值是B, s2的值是E萎胰,因?yàn)锽小于E碾盟,閉包返回true,表示B應(yīng)該出現(xiàn)在E之前技竟。
如果您仔細(xì)閱讀前面的代碼片段冰肴,您可能會奇怪為什么我要這樣編寫排序閉包:
animalSectionTitles = animalSectionTitles.sorted(by: { $0 < $1 })
它是編寫內(nèi)聯(lián)閉包的一種快捷方式。這里$0和$1指的是第一個(gè)和第二個(gè)字符串參數(shù)榔组。如果您使用簡短的參數(shù)名稱熙尉,您可以省略幾乎所有的閉包,包括參數(shù)列表和關(guān)鍵字;您只需要編寫閉包的主體搓扯。
Swift 3提供了另一個(gè)排序函數(shù)sort检痰。這個(gè)函數(shù)和排序后的函數(shù)很相似。排序函數(shù)不是返回排序的數(shù)組锨推,而是對原始數(shù)組進(jìn)行排序铅歼。您可以用下面的代碼替換代碼行:
animalSectionTitles.sort(by: { $0 < $1 })
創(chuàng)建助手方法后公壤,更新viewDidLoad方法以調(diào)用它:
下一步,改變numberOfSections(in:)方法椎椰,并返回section的總數(shù):
要在每個(gè)部分顯示標(biāo)題標(biāo)題厦幅,我們需要實(shí)現(xiàn)tableView(_:titleForHeaderInSection:)方法。每次顯示一個(gè)新節(jié)時(shí)慨飘,都會調(diào)用此方法确憨。基于給定的section索引套媚,我們返回相應(yīng)的section標(biāo)題
這很簡單,對吧?接下來缚态,我們必須告訴表視圖特定部分中的行數(shù)。在AnimalTableViewController中更新tableView(_:numberOfRowsInSection:)方法.
當(dāng)應(yīng)用程序開始在表視圖中呈現(xiàn)數(shù)據(jù)時(shí)堤瘤,每當(dāng)顯示新節(jié)時(shí)玫芦,就調(diào)用tableView(_:numberOfRowsInSection:)方法。根據(jù)節(jié)索引本辐,我們可以獲取節(jié)標(biāo)題并將其用作檢索該節(jié)的動物名稱的鍵桥帆。然后我們返回該部分的動物名稱的總數(shù)。在上面的代碼中慎皱,我們使用guard關(guān)鍵字來確定字典是否為特定的animalKey返回一個(gè)有效的數(shù)組老虫。如果不是,我們返回0.
在這種情況下茫多,guard關(guān)鍵字特別有用祈匙。在繼續(xù)執(zhí)行之前,我們希望確保animalValues包含一些值天揖。而且夺欲,它使代碼更清晰、更可讀今膊。
最后些阅,修改tableView(_:cellForRowAt:)方法如下:
indexPath參數(shù)包含當(dāng)前行號,以及當(dāng)前的節(jié)索引斑唬。因此市埋,基于section索引,我們檢索section title(例如:“B”)并使用它作為檢索該部分動物名稱的鍵恕刘。代碼的其余部分非常簡單缤谎。我們只需獲取動物的名字并將其設(shè)置為細(xì)胞標(biāo)簽。imageFilename變量的計(jì)算方法是將動物名稱轉(zhuǎn)換為小寫字母褐着,然后用下劃線替換所有出現(xiàn)的空格弓千。
好了,你準(zhǔn)備好了!點(diǎn)擊Run按鈕献起,你會得到一個(gè)包含部分但沒有索引列表的應(yīng)用洋访。
向UITableView 添加列表索引?
那么镣陕,如何向表視圖添加索引列表呢? 同樣,這比您想象的要簡單姻政,并且只需幾行代碼就可以實(shí)現(xiàn)呆抑。只需添加sectionindextitle (for:)方法并返回一個(gè)section索引數(shù)組。這里我們將使用章節(jié)標(biāo)題作為索引汁展。
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
? ? return animalSectionTitles
}
就是這樣! 重新編譯并運(yùn)行應(yīng)用程序鹊碍。您應(yīng)該在表的右邊找到索引。有趣的是食绿,您不需要任何實(shí)現(xiàn)侈咕,而且索引已經(jīng)工作了!嘗試點(diǎn)擊任何索引,您將被帶到表的特定部分.
添加 A -- Z 列表索引??
看來我們什么都做過了器紧。但是為什么我們一開始就提到了tableView(_:sectionForSectionIndexTitle:at:)方法呢?
目前耀销,索引列表不包含整個(gè)字母表。它只顯示那些被定義為動物字典的鍵的字母铲汪。有時(shí)熊尉,您可能希望在索引列表中顯示A-Z。讓我們在animaltableviewcontroller中聲明一個(gè)名為animalindextitle的新變量:
let animalIndexTitles = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
接下來掌腰,更改sectionIndexTitle (for:)方法并返回animalIndexTitle數(shù)組狰住,而不是animalSectionTitle數(shù)組
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
? ? return animalIndexTitles
}
現(xiàn)在,重新編譯并運(yùn)行這個(gè)應(yīng)用程序齿梁。太酷了!該應(yīng)用程序?qū)⑺饕龔腁顯示到Z催植。
但是等一下,它不能正常工作!如果你嘗試點(diǎn)擊索引“C”勺择,應(yīng)用程序會跳轉(zhuǎn)到“D”部分查邢。如果你點(diǎn)擊索引“G”,它會指向“K”部分酵幕。下面顯示新舊索引之間的映射。
好,你可能會注意到,索引的數(shù)量大于部分的數(shù)量,和UITableView對象不知道如何處理索引缓苛。您的職責(zé)是實(shí)現(xiàn)tableView(_:sectionForSectionIndexTitle:at:)方法芳撒,并在選中特定索引時(shí)顯式地告訴表視圖區(qū)段編號。增加以下新方法:
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
? ? guard let index = animalSectionTitles.index(of: title) else {
? ? ? ? return -1
? ? }
? ? return index
}
基于所選的索引名(即title)未桥,我們定位了animalsectiontitle的正確節(jié)索引笔刹。在Swift中,您使用名為index(of:)的方法來查找數(shù)組中特定項(xiàng)的索引冬耿。
實(shí)現(xiàn)的整個(gè)要點(diǎn)是驗(yàn)證給定的標(biāo)題是否可以在animalsectiontitle數(shù)組中找到舌菜,并返回相應(yīng)的索引。然后亦镶,表視圖移動到相應(yīng)的部分日月。例如袱瓮,如果標(biāo)題是B,我們檢查B是否是有效的節(jié)標(biāo)題并返回索引1爱咬。如果找不到標(biāo)題(例如A)尺借,我們返回-1。
重新編譯并運(yùn)行應(yīng)用程序精拟。索引列表現(xiàn)在應(yīng)該可以工作了.
定制部分標(biāo)題
通過覆蓋UITableView類和UITableViewDelegate協(xié)議中定義的一些方法燎斩,您可以輕松地定制節(jié)頭。在這個(gè)演示中蜂绎,我們將做一些簡單的更改:?
更改節(jié)標(biāo)題的高度?
更改節(jié)標(biāo)題的字體和背景顏色?
要更改區(qū)段標(biāo)題的高度栅表,您可以簡單地覆蓋tableView(_:heightForHeaderInSection:)方法并返回首選的高度.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
? ? return 50
}
在顯示節(jié)頭視圖之前,將調(diào)用tableView(_:willDisplayHeaderView:forSection:)方法师枣。該方法包含一個(gè)名為view的參數(shù)怪瓶。這個(gè)視圖對象可以是自定義頭視圖,也可以是標(biāo)準(zhǔn)頭視圖坛吁。在我們的演示, 我們只使用標(biāo)準(zhǔn)的header視圖劳殖,它是UITableViewHeaderFooterView對象。一旦有了header視圖拨脉,就可以更改文本顏色哆姻、字體和背景顏色。
再次運(yùn)行應(yīng)用程序玫膀。頭視圖應(yīng)該用您喜歡的字體和顏色進(jìn)行更新矛缨。
總結(jié)?
當(dāng)您需要顯示大量記錄時(shí),將數(shù)據(jù)組織到節(jié)中并提供索引列表以方便訪問是簡單而有效的帖旨。在本章中箕昭,我們已經(jīng)介紹了索引表的實(shí)現(xiàn)。現(xiàn)在解阅,我相信您應(yīng)該知道如何向您的表視圖添加區(qū)段和索引列表.
你可以在這里下載原作者的 Xcode 項(xiàng)目.?