Intermediate iOS 11 Programming with Swift (二): 在UITableView中添加区段和索引列表

本文是Intermediate iOS 11 Programming with Swift 4系列 的 第 二 篇.

2.0

如果您想在UITableView中显示大量的记录,您最好重新考虑如何显示数据的方法。随着行数的增加,表视图变得难以处理。改进用户体验的一种方法是将数据组织成部分。通过将相关数据分组在一起,可以为用户提供更好的访问方式。

此外,还可以在表视图中实现索引列表。索引表视图或多或少与纯样式表视图相同。惟一的区别是它在表视图的右边包含一个索引。索引表在iOS应用程序中非常常见。最著名的例子是iPhone的内置联系人应用。通过提供索引滚动,用户可以立即访问表的特定部分,而无需滚动每个部分。

让我们看看如何向一个简单的表应用程序中添加节和索引列表。如果您对UITableView实现有基本的了解,那么添加节和索引列表并不太难。基本上,您需要处理UITableViewDataSource协议中定义的这些方法:

numberofSections (in:) 方法 - 返回表视图中所有的部分。通常,我们将section的数量设置为1。如果你想要有多个部分,把这个值设置为大于1的数字.

tableView(_ :titleForHeaderInsection:) 方法 - 返回不同部分的标题。如果您不喜欢为部分分配标题,则此方法是可选的.

tableView(_ : numberOfRowsInSection:)  方法 - 返回特定部分中的行总数.

tableView(_ : CellForRowAt:) 方法——如果您知道如何在UITableView中显示数据,那么这种方法对您来说不应该是陌生的。它返回特定部分的表数据.

sectionIndexTitles( for :)  方法——返回出现在表视图右边的索引列表中的索引标题。例如,您可以返回包含从a到Z值的字符串数组.

tableView(_ : sectionForSectionIndexTitle: at: ) 方法——返回当用户点击特定索引时,表视图应该跳转到的部分索引.

没有比给你看一个例子更好的解释实现的方法了。和往常一样,我们将构建一个Simple App,它将使您对索引列表实现有更好的理解.

Demo App

首先,让我们快速浏览一下我们将要构建的演示应用程序。这是一个非常简单的应用程序,在标准的表格视图中显示动物列表。这个应用程序并没有列出所有的动物,而是将它们分组到不同的区域,并显示一个索引列表,以便快速访问.  下面的屏幕截图显示了演示应用程序的最终交付.

2.1 Demo App 


下载Xcode项目模板

演示的重点是实现部分和索引列表.

因此,您可以从这里下载项目模板.这个模板已经包含了你需要的一切。如果构建模板,您将有一个应用程序在表视图中显示动物列表(但没有节和索引)。稍后,我们将修改应用程序,将数据分组到节中,并向表中添加索引列表.

2.1 


显示部分UITableView

好的,让我们开始吧。如果您打开IndexTableDemo项目,动物数据将在数组中定义:

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"]

我们会根据动物名字的第一个字母将数据分成几个部分。有很多方法可以做到这一点。一种方法是用字典手工替换动物数组,如下所示:

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"]]”

在上面的代码中,我们把动物数组变成了字典。动物名字的第一个字母被用作钥匙。与相应键相关联的值是一个动物名称数组。

我们可以手动创建字典,但是如果我们可以通过编程的方式从animals数组中创建索引,那不是很好吗?让我们看看怎么做。

首先,在AnimalTableViewController类中声明两个实例变量:

var animalsDict = [String: [String] ] ()

var animalSectionTitles = [String] ()

我们初始化用于存储动物的空字典和用于存储表的节标题的空数组。章节标题是动物名字的首字母(如B)。

因为我们想从animals数组生成一个字典,所以我们需要一个helper方法来处理生成。在AnimalTableViewController类中插入以下方法:

在此方法中,我们遍历动物数组中的所有项。对于每一项,我们首先提取动物名字的第一个字母。要获取特定位置的索引(例如string . index),您必须向字符串本身询问startIndex,然后调用index方法以获得所需的位置。在本例中,目标位置为1,因为我们只对第一个字符感兴趣。

在Swift 3中,您使用字符串的substring(to:)方法来获取一个新字符串,该字符串包含直到给定索引为止的字符。现在,在Swift 4中,该方法已被弃用。相反,您可以使用如下这样的下标将字符串分割为子字符串:

let animalKey = String(animal[..<firstLetterIndex])

aimal[..< firstLetterIndex]   将动物字符串切片到指定的索引。在上面的例子中,它意味着提取第一个字符。您可能想知道为什么我们需要使用字符串初始化来包装返回的子字符串。在Swift 4中,当您将一个字符串分割为一个子字符串时,您将得到一个子字符串实例。它是一个临时对象,与原始字符串共享其存储。为了将子字符串实例转换为字符串实例,需要使用String()对其进行包装.

如前所述,动物名字的第一个字母被用作字典的键。字典的值是该特定键的动物数组。因此,一旦我们获得了这个键,我们要么创建一个新的动物数组,要么将这个项目附加到一个现有的数组中。这里我们展示了前四次迭代的animalsDict值:

迭代 #1: animalsDict["B"] = ["Bear"]

迭代 #2: animalsDict["B"] = ["Bear", "Black Swan"]

迭代 #3: animalsDict["B"] = ["Bear", "Black Swan", "Buffalo"]

迭代 #4: animalsDict["C"] = ["Camel"]

在完全生成animalsDict之后,我们可以从字典的键中检索section标题。

要检索字典的键,只需调用keys方法。但是,返回的键是无序的。Swift的标准库提供了一个名为sort的函数,该函数根据所提供的排序闭包的输出返回已知类型的值的排序数组。

闭包接受相同类型的两个参数(在本例中是字符串),并返回一个Bool值,以声明第一个值在第一个值被排序后是应该出现在第二个值之前还是之后。如果第一个值出现在第二个值之前,它应该返回true。

编写排序闭包的一种方法是:

animalSectionTitles = animalSectionTitles.sorted( by: { (s1:String, s2:String) -> Bool in

            return s1 < s2

        })

您应该非常熟悉闭包表达式语法。在闭包的主体中,我们比较两个字符串值。如果第二个值大于第一个值,则返回true。例如,s1的值是B, s2的值是E,因为B小于E,闭包返回true,表示B应该出现在E之前。

如果您仔细阅读前面的代码片段,您可能会奇怪为什么我要这样编写排序闭包:

animalSectionTitles = animalSectionTitles.sorted(by: { $0 < $1 })

它是编写内联闭包的一种快捷方式。这里$0和$1指的是第一个和第二个字符串参数。如果您使用简短的参数名称,您可以省略几乎所有的闭包,包括参数列表和关键字;您只需要编写闭包的主体。

Swift 3提供了另一个排序函数sort。这个函数和排序后的函数很相似。排序函数不是返回排序的数组,而是对原始数组进行排序。您可以用下面的代码替换代码行:

animalSectionTitles.sort(by: { $0 < $1 })

创建助手方法后,更新viewDidLoad方法以调用它:

下一步,改变numberOfSections(in:)方法,并返回section的总数:



要在每个部分显示标题标题,我们需要实现tableView(_:titleForHeaderInSection:)方法。每次显示一个新节时,都会调用此方法。基于给定的section索引,我们返回相应的section标题

这很简单,对吧?接下来,我们必须告诉表视图特定部分中的行数。在AnimalTableViewController中更新tableView(_:numberOfRowsInSection:)方法.

当应用程序开始在表视图中呈现数据时,每当显示新节时,就调用tableView(_:numberOfRowsInSection:)方法。根据节索引,我们可以获取节标题并将其用作检索该节的动物名称的键。然后我们返回该部分的动物名称的总数。在上面的代码中,我们使用guard关键字来确定字典是否为特定的animalKey返回一个有效的数组。如果不是,我们返回0.

在这种情况下,guard关键字特别有用。在继续执行之前,我们希望确保animalValues包含一些值。而且,它使代码更清晰、更可读。

后,修改tableView(_:cellForRowAt:)方法如下:



indexPath参数包含当前行号,以及当前的节索引。因此,基于section索引,我们检索section title(例如:“B”)并使用它作为检索该部分动物名称的键。代码的其余部分非常简单。我们只需获取动物的名字并将其设置为细胞标签。imageFilename变量的计算方法是将动物名称转换为小写字母,然后用下划线替换所有出现的空格。

好了,你准备好了!点击Run按钮,你会得到一个包含部分但没有索引列表的应用。

向UITableView 添加列表索引 

那么,如何向表视图添加索引列表呢? 同样,这比您想象的要简单,并且只需几行代码就可以实现。只需添加sectionindextitle (for:)方法并返回一个section索引数组。这里我们将使用章节标题作为索引。

override func sectionIndexTitles(for tableView: UITableView) -> [String]? {

    return animalSectionTitles

}

就是这样! 重新编译并运行应用程序。您应该在表的右边找到索引。有趣的是,您不需要任何实现,而且索引已经工作了!尝试点击任何索引,您将被带到表的特定部分.

2.3


添加 A -- Z 列表索引  

看来我们什么都做过了。但是为什么我们一开始就提到了tableView(_:sectionForSectionIndexTitle:at:)方法呢?

目前,索引列表不包含整个字母表。它只显示那些被定义为动物字典的键的字母。有时,您可能希望在索引列表中显示A-Z。让我们在animaltableviewcontroller中声明一个名为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数组,而不是animalSectionTitle数组

override func sectionIndexTitles(for tableView: UITableView) -> [String]? {

    return animalIndexTitles

}

现在,重新编译并运行这个应用程序。太酷了!该应用程序将索引从A显示到Z。

但是等一下,它不能正常工作!如果你尝试点击索引“C”,应用程序会跳转到“D”部分。如果你点击索引“G”,它会指向“K”部分。下面显示新旧索引之间的映射。

好,你可能会注意到,索引的数量大于部分的数量,和UITableView对象不知道如何处理索引。您的职责是实现tableView(_:sectionForSectionIndexTitle:at:)方法,并在选中特定索引时显式地告诉表视图区段编号。增加以下新方法:

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的正确节索引。在Swift中,您使用名为index(of:)的方法来查找数组中特定项的索引。

实现的整个要点是验证给定的标题是否可以在animalsectiontitle数组中找到,并返回相应的索引。然后,表视图移动到相应的部分。例如,如果标题是B,我们检查B是否是有效的节标题并返回索引1。如果找不到标题(例如A),我们返回-1。

重新编译并运行应用程序。索引列表现在应该可以工作了.

定制部分标题

通过覆盖UITableView类和UITableViewDelegate协议中定义的一些方法,您可以轻松地定制节头。在这个演示中,我们将做一些简单的更改: 

更改节标题的高度 

更改节标题的字体和背景颜色 

要更改区段标题的高度,您可以简单地覆盖tableView(_:heightForHeaderInSection:)方法并返回首选的高度.

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {

    return 50

}

在显示节头视图之前,将调用tableView(_:willDisplayHeaderView:forSection:)方法。该方法包含一个名为view的参数。这个视图对象可以是自定义头视图,也可以是标准头视图。在我们的演示, 我们只使用标准的header视图,它是UITableViewHeaderFooterView对象。一旦有了header视图,就可以更改文本颜色、字体和背景颜色。

再次运行应用程序。头视图应该用您喜欢的字体和颜色进行更新。


2.5 


总结 

当您需要显示大量记录时,将数据组织到节中并提供索引列表以方便访问是简单而有效的。在本章中,我们已经介绍了索引表的实现。现在,我相信您应该知道如何向您的表视图添加区段和索引列表.

你可以在这里下载原作者的 Xcode 项目. 

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容