我已经编写了一种算法来为表视图创建节索引。
不幸的是,当列表仅包含一项时,我有一个错误,结果为空。

您对此有一个优雅的解决方案吗?

var sections : [(index: Int, length :Int, title: String)] = Array()

    func createSectionIndices(participants: List<Participant>){

        sections.removeAll()

        var index = 0;

        let array = participants.sort({$0.lastName < $1.lastName})

        for i in 0.stride(to: array.count, by: 1){

            let commonPrefix = array[i].lastName.commonPrefixWithString(array[index].lastName, options: .CaseInsensitiveSearch)

            if (commonPrefix.isEmpty) {

                let string = array[index].lastName.uppercaseString;
                let firstCharacter = string[string.startIndex]
                let title = "\(firstCharacter)"
                let newSection = (index: index, length: i - index, title: title)
                sections.append(newSection)
                index = i;
            }
        }
        print("sectionCount: \(sections.count)")
    }

最佳答案

这是构建部分列表的单行解决方案:

 var participants:[(firstName:String, lastName:String)] =
     [
        ("John", "Smith"),
        ("Paul", "smith"),
        ("Jane", "Doe"),
        ("John", "denver"),
        ("Leo",  "Twain"),
        ("Jude", "law")
     ]

 // case insensitive sort (will keep "denver" and "Doe" together)
 participants = participants.sort({$0.lastName.uppercaseString < $1.lastName.uppercaseString})

 // The process:
 // - get first letter of each name (in uppercase)
 // - combine with indices (enumerate)
 // - only keep first occurrence of each letter (with corresponding indice)
 // - build section tuples using index, letter and number of participants with name begining with letter
 let sections = participants
                .map({String($0.lastName.uppercaseString.characters.first!)})
                .enumerate()
                .filter({ $0 == 0 || !participants[$0 - 1].lastName.uppercaseString.hasPrefix($1) })
                .map({ (start,letter) in return
                       (
                         index:  start,
                         length: participants.filter({$0.lastName.uppercaseString.hasPrefix(letter)}).count,
                         title:  letter
                       )
                    })

 // sections will contain:
 // (index:0, length:2, title:"D")
 // (index:2, length:1, title:"L")
 // (index:3, length:2, title:"S")
 // (index:5, length:1, title:"T")


基于存储在元组数组中的节,您可能已经有很多现有的代码,但是,如果没有,我建议您采取一些不同的方法,并用字母和参与者数据构建您的节数组。

 let sections = participants
                .map({ String($0.lastName.uppercaseString.characters.first!) })
                .reduce( Array<String>(), combine: { $0.contains($1) ? $0 : $0 + [$1] })
                .map({ (letter) in return
                       (
                         title: letter,
                         participants: participants.filter({$0.lastName.uppercaseString.hasPrefix(letter)})
                       )
                    })


这将允许您使用sections.count来响应节的数量,但也将使在每个节中操作索引路径和数据变得更加容易:


一个部分的参与者数量:sections [index] .participants.count
索引路径上的参与者:section [indexPath.section] .participants [indexPath.row]


这只是语法糖果,但是如果您对参与者列表有很多引用,它将使代码更具可读性。
另外,如果您的参与者是对象而不是元组或结构,则您甚至可以更新主要参与者列表中的数据,而不必重建这些部分(除非更改了姓氏)。

[编辑]修复了最后一个元组语法中的错误

[EDIT2] Swift 4 ...

Swift 4中的字典提供了一种更轻松的方式来管理此类事情。

对于原始参考结构:

 let sections = [Character:[Int]](grouping:participants.indices)
                {participants[$0].lastName.uppercased().first!}
                .map{(index:$1.reduce(participants.count,min), length:$1.count, title:String($0))}
                .sorted{$0.title<$1.title}




对于包含自己的参与者子列表的部分结构(我的建议):

let sectionData = [Character:[Participant]](grouping:participants)
                  {$0.lastName.uppercased().first!}
                  .map{(title:$0, participants:$1)}
                  .sorted{$0.title<$1.title}

10-07 18:55
查看更多