第二章 为UITableView添加Section和Index list

如果您想在UITableView中显示大量的记录,则最好重新考虑显示数据的方式。随着行数的增加,table view会变得非常卡顿。改进用户体验的一种方法是将数据组织成部分。通过将相关数据分组,您可以为用户提供更好的访问方式。

此外,您可以在table view中实现索引列表。索引表视图或多或少与普通样式table view相同。唯一的区别是它在表视图的右侧包含一个索引。索引表在iOS应用程序中非常常见。最着名的例子是iPhone上内置的Contacts应用程序。通过索引滚动,用户可以立即访问表格的特定部分,而无需滚动每个部分。

让我们看看如何将一个section和一个index list添加到一个简单的表格app。如果您对UITableView的实现已经有基本的了解,那么add section和index list并不难。基本上你需要处理UITableViewDataSource协议中下面的这些方法:

  • numberOfSections(in :)方法 - 返回table view中的section总数。通常我们将其设置为1。如果要具有多个section,请将此值设置为大于1的数字。
  • tableView(_:titleForHeaderInSection :)方法 - 返回不同Section的标题。如果您不喜欢为该Section分配标题,则此方法是可选的。
  • tableView(_:numberOfRowsInSection :)方法 - 返回指定Section中的总行数。
  • tableView(_:cellForRowAt :)方法 - 如果您知道如何在UITableView中显示数据,此方法不应该是新的。它返回指定Section的表数据。
  • sectionIndexTitles(for :)方法 - 返回出现在table view右侧的索引列表中的索引标题。例如,您可以返回一个包含值从A到Z的字符串数组。
  • tableView(_:sectionForSectionIndexTitle:at :) method - 当用户点击特定索引时table view 所跳转的Section索引。

没有比实战更好解释实现表格索引的方法了。像往常一样,我们将构建一个简单的应用程序,它可以让您更好地了解索引列表的实现。

A Brief Look at the Demo App

首先,我们来看看要构建的演示程序。这是一个非常简单的应用程序,在标准table view中显示动物列表。应用程序将动物列入不同部分,而不是列出所有动物,并显示一个索引列表,以便快速访问。下面的截图显示了演示应用程序的最终交付内容。

![Uploading uitableview-section-2_552914.png . . .]

Download the Xcode Project Template

该演示的重点是Section和Index List的实现。因此,您可以从http://www.appcoda.com/resources/swift3/IndexedTableDemoStarter.zip下载项目模板,而不是从头开始构建Xcode项目。

该模板已经包括您需要开始做的所有内容。如果您构建模板,那么您将有一个应用程序在table view中显示动物列表(但不包含section和index)。稍后,我们将修改应用程序,将数据分组成section,并将index list添加到表中。

uitableview-section-2.png

Displaying Sections in 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"]

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

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

在上面的代码中,我们已经将动物数组变成一个字典。动物名称的第一个字母用作关键字。与相应键相关联的是动物名称的数组。

我们可以手动创建字典,但是如果我们可以以编程方式从动物数组中创建索引,那不是很棒吗?让我们看看如何做到这一点。

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

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

我们初始化一个用于存储动物的空字典和一个用于存储表的section标题的空数组。section标题是动物名称的第一个字母(例如B)。

因为我们想从动物数组中生成一个字典,所以我们需要一个帮助程序来处理这段代码。在AnimalTableViewController类中插入以下方法:

func createAnimalDict() {
    for animal in animals {
        // Get the first letter of the animal name and build the dictionary
        let firstLetterIndex = animal.index(animal.startIndex, offsetBy: 1)
        let animalKey = animal.substring(to: firstLetterIndex)
        
        if var animalValues = animalsDict[animalKey] {
            animalValues.append(animal)
            animalsDict[animalKey] = animalValues
        } else {
            animalsDict[animalKey] = [animal]
        }
    }

    // Get the section titles from the dictionary's keys and sort them in ascending order
    animalSectionTitles = [String](animalsDict.keys)
    animalSectionTitles = animalSectionTitles.sorted(by: { $0 < $1 })
}

在这种方法中,我们循环遍历animals数组中的所有项目。对于每个项目,我们最初提取动物姓名的第一个字母。在Swift中,字符串的substring(to:)方法可以根据给定的索引返回一个包含字符的新字符串。索引应该是String.Index类型。要获取指定位置的索引,您必须向字符串本身询问startIndex,然后调用index方法以获取所需的位置。在这种情况下,目标位置为1,因为我们只对第一个字符感兴趣。

如前所述,动物名字的第一个字母被用作字典的关键字。字典的值是该指定键的动物数组。所以一旦我们得到了钥匙,我们可以创建一个新的动物数组,或者把这个项目附加到现有的数组中。在这里我们显示动物的值为前四次的迭代:

  • Iteration#1:animalsDict [“B”] = [“Bear”]
  • Iteration#2:animalsDict [“B”] = [“Bear”,“Black Swan”]
  • Iteration#3:animalsDict [“B”] = ["Bear", "Black Swan", "Buffalo"]
  • Iteration#4:animalsDict [“C”] = ["Camel"]

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

要检索字典的键,您可以简单地调用keys方法。然而,返回的键是无序的。Swift的标准库提供了一个名为sorted的函数,该函数根据您提供的排序闭包的输出返回已知类型的排序数组。

闭包接受相同类型的两个参数(在这个例子中,它是字符串),并返回一个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})

这是Swift编写内联闭包的缩写。这里$0$1指的是第一个和第二个String参数。如果使用简写参数名称,可以省略几乎所有的关闭包括参数列表和关键字;你只需要写封闭的本体。

Swift 3提供了另一种排序功能。此函数与排序函数非常相似。排序函数不是返回一个排序的数组,而是对原始数组进行排序。您可以用以下代码替换代码行:

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

创建帮助方法后,更新viewDidLoad方法来调用它:

override func viewDidLoad() {
    super.viewDidLoad()

    // Generate the animal dictionary
    createAnimalDict()
}

接下来,更改numberOfSections(in :)方法并返回section总数:

override func numberOfSections(in tableView: UITableView) -> Int {
    // Return the number of sections.
    return animalSectionTitles.count
}

要显示每个section的标题,我们需要实现tableView(_:titleForHeaderInSection :)方法。每次显示新的section时都会调用此方法。根据给定的section index,我们返回相应的section标题。

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return animalSectionTitles[section]
}

这很直接,对吧?接下来,我们必须告诉table view中特定section的行数。在AnimalTableViewController.swift中更新tableView(_:numberOfRowsInSection :)方法,如下所示:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // Return the number of rows in the section.
    let animalKey = animalSectionTitles[section]
    guard let animalValues = animalsDict[animalKey] else {
        return 0
    }

    return animalValues.count
}

当应用程序开始在table view中呈现数据时,每次显示新的section时都会调用tableView(_:numberOfRowsInSection :)方法。根据section index,我们可以得到section标题,并将其用作检索该section的动物名称的键。然后我们返回该section的动物名称总数。在上面的代码中,我们使用guard关键字来确定字典是否返回特定的animalKey的有效数组。如果没有,我们只返回0

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

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

    // Configure the cell...
    let animalKey = animalSectionTitles[indexPath.section]
    if let animalValues = animalsDict[animalKey] {
        cell.textLabel?.text = animalValues[indexPath.row]

        // Convert the animal name to lower case and
        // then replace all occurrences of a space with an underscore
        let imageFilename = animalValues[indexPath.row].lowercased().replacingOccurrences(of: " ", with: "_")
        cell.imageView?.image = UIImage(named: imageFilename)
    }

    return cell
}

indexPath参数包含当前行号以及当前section索引。因此,根据section索引,我们检索section标题(例如“B”),并将其用作检索该section的动物名称的键。其余的代码非常简单。 我们只需获取动物名称并将其设置为Cell标签。通过将动物名称转换为小写字母来计算imageFilename变量,然后用下划线替换所有出现的空格。

好的,你准备好了! 点击Run按钮,您应该使用带有section但没有index list的应用程序。

Adding An Index List to UITableView

那么如何在table view中添加index list?它比你想象的更容易,只需几行代码即可实现。只需添加sectionIndexTitles(for :)方法并返回一个section index数组。这里我们将使用section标题作为索引。

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

仅此而已!再次编译并运行应用程序。您应该在表的右侧找到索引。有趣的是,您不需要任何实现,并且索引已经正常工作!尝试点击任何索引,您将被带到表的特定部分。

uitableview-section-3.png

Adding An A-Z Index List

看起来我们已经做了一切 但是为什么我们最开始提到tableView(_:sectionForSectionIndexTitle:at :)方法?

目前,index list不包含整个字母表。它只显示被定义为动物词典的键的那些字母。有时,您可能希望在索引列表中显示A-Z。我们在AnimalTableViewController.swift中声明一个名为animalIndexTitles的新变量:

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

接下来,更改sectionIndexTitles(for :)方法并返回animalIndexTitles数组,而不是animalSectionTitles数组。

override func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {
    return animalIndexTitles
}

现在,再次编译并运行应用程序。酷!该应用程序显示从A到Z的索引。

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

uitableview-section-4.png

那么,你可能会注意到,索引的数量大于section数,UITableView对象不知道如何处理索引。 您有责任实现tableView(_:sectionForSectionIndexTitle:at :)方法,并在特定索引被轻触时明确地告诉table view中的section号。添加以下新方法:

override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {

    guard let index = animalSectionTitles.index(of: title) else {
        return -1
    }

    return index
}

根据所选的索引名称(即标题),我们找到animalSectionTitles的正确section索引。在Swift中,您可以使用名为index(:)的方法来查找数组中指定的项目索引。

实现的全部要点是验证是否可以在animalSectionTitles数组中找到给定的标题,并返回相应的索引。然后,table view移动到相应的section。例如,如果标题是B,我们检查B是一个有效的section标题并返回索引1。如果没有找到标题(例如A),我们返回-1。

再次编译并运行应用程序。 索引列表现在应该可以工作了!

Customizing Section Headers

您可以通过覆盖在UITableView类和UITableViewDelegate协议中定义的一些方法来轻松地自定义section标题。在此演示中,我们将进行几个简单的更改:

  • 改变部分标题的高度
  • 更改部分标题的字体和背景颜色

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

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

在显示section标题视图之前,将调用tableView(_:willDisplayHeaderView:forSection :)方法。该方法包括一个名为view的参数。 此视图对象可以是自定义标题视图或标准视图。 在我们的演示中,我们只使用标准标题视图,即UITableViewHeaderFooterView对象。 一旦你有标题视图,你可以改变文本颜色,字体和背景颜色。

override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
    let headerView = view as! UITableViewHeaderFooterView
    headerView.backgroundView?.backgroundColor = UIColor(red: 236.0/255.0, green: 240.0/255.0, blue: 241.0/255.0, alpha: 1.0)
    headerView.textLabel?.textColor = UIColor(red: 231.0/255.0, green: 76.0/255.0, blue: 60.0/255.0, alpha: 1.0)

    headerView.textLabel?.font = UIFont(name: "Avenir", size: 25.0)
}

再次运行应用程序 标题视图应使用您首选的字体和颜色进行更新。

Summary

当您需要显示大量记录时,将数据组织成几个section并提供索引列表以便于访问是简单有效的办法。在本章中,我们向您介绍了索引表的实现。现在,我相信你应该知道如何在表格视图中添加section和index list。

作为参考,您可以从http://www.appcoda.com/resources/swift3/IndexedTableDemo.zip下载完整的Xcode项目。

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

推荐阅读更多精彩内容

  • 概述在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似...
    liudhkk阅读 8,908评论 3 38
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • #pragma mark someValueAboutTableView 1.tableView的样式:UITab...
    潇岩阅读 863评论 0 0
  • 注视着,注视着,注视着那熟悉而又越看越模糊的脸,我的心好疼,我的思绪好乱——我最好的朋友将要离我而去,随着她的家迁...
    大大小小呃呃呃阅读 221评论 0 0
  • 博客
    yangqi916阅读 195评论 0 0