美文网首页iOSiOS开发iOS Developer
第二章 为UITableView添加Section和Index

第二章 为UITableView添加Section和Index

作者: 刘铭iOS | 来源:发表于2017-06-21 04:48 被阅读594次

    如果您想在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项目。

    相关文章

      网友评论

        本文标题:第二章 为UITableView添加Section和Index

        本文链接:https://www.haomeiwen.com/subject/udhzqxtx.html