How to make alphabetically section headers in table view with a mutable data source

非 Y 不嫁゛ 提交于 2019-11-29 07:09:31
Stefan

I would change the way you store your contacts to a dictonary with the initial letters as keys and put the names that correspond to that initial letter into a subarray:

contacts = ["A": ["Anton", "Anna"], "C": ["Caesar"]]

I simplified the way of the contacts here (in form of strings), but you get the concept.

I would also save the section number of the letter in a seperate array like this:

letters = ["A", "C"]

Keep the array sorted and organized, so check after each insertion/deletion/update. This is not part of the table view implementation. I would make the Viewcontroller a delegate of the phonebook, so you can fire an update-like method from the phonebook to update the table.

How to get the data for the data source:

the number of sections:

letters.count

the section title for section at index i is

letters[i]

the number of cells in a section i is

contacts[letters[i]].count

and the content for a specific cell c in section i is:

contacts[letters[i]][c]

Feel free to ask further questions if anything is still not clear.

UPDATE - How to generate the arrays:

I don't require the data to be sorted, if you pass it already sorted, you can delete the sorting lines below ...

let data = ["Anton", "Anna", "John", "Caesar"] // Example data, use your phonebook data here.

// Build letters array:

var letters: [Character]

letters = data.map { (name) -> Character in
    return name[name.startIndex]
}

letters = letters.sort()

letters = letters.reduce([], combine: { (list, name) -> [Character] in
    if !list.contains(name) {
        return list + [name]
    }
    return list
})


// Build contacts array:

var contacts = [Character: [String]]()

for entry in data {

    if contacts[entry[entry.startIndex]] == nil {
        contacts[entry[entry.startIndex]] = [String]()
    }

    contacts[entry[entry.startIndex]]!.append(entry)

}

for (letter, list) in contacts {
    list.sort()
}

For Swift 3:

let data = ["Anton", "Anna", "John", "Caesar"] // Example data, use your phonebook data here.

// Build letters array:

var letters: [Character]

letters = data.map { (name) -> Character in
    return name[name.startIndex]
}

letters = letters.sorted()

letters = letters.reduce([], { (list, name) -> [Character] in
    if !list.contains(name) {
        return list + [name]
    }
    return list
})


// Build contacts array:

var contacts = [Character: [String]]()

for entry in data {

    if contacts[entry[entry.startIndex]] == nil {
        contacts[entry[entry.startIndex]] = [String]()
    }

    contacts[entry[entry.startIndex]]!.append(entry)

}

for (letter, list) in contacts {
    contacts[letter] = list.sorted()
}

I ran the code in playground and got the following outputs for

letters:

["A", "C", "J"]

contacts:

["J": ["John"], "C": ["Caesar"], "A": ["Anton", "Anna"]]

For Swift 3. Thank you @Stefan! Here is my version with Set

var tableViewSource: [Character : [String]]!
var tableViewHeaders: [Character]!

let data = ["Anton", "Anna", "John", "Caesar"]

func createTableData(wordList: [String]) -> (firstSymbols: [Character], source: [Character : [String]]) {

    // Build Character Set
    var firstSymbols = Set<Character>()

    func getFirstSymbol(word: String) -> Character {
        return word[word.startIndex]
    }

    wordList.forEach {_ = firstSymbols.insert(getFirstSymbol(word: $0)) }

    // Build tableSourse array
    var tableViewSourse = [Character : [String]]()

    for symbol in firstSymbols {

        var words = [String]()

        for word in wordList {
            if symbol == getFirstSymbol(word: word) {
                words.append(word)
            }
        }

        tableViewSourse[symbol] = words.sorted(by: {$0 < $1})
    }

    let sortedSymbols = firstSymbols.sorted(by: {$0 < $1})

    return (sortedSymbols, tableViewSourse)
}

func getTableData(words: [String]) {
    tableViewSource = createTableData(wordList: words).source
    tableViewHeaders = createTableData(wordList: words).firstSymbols
}

getTableData(words: data)

print(tableViewSource)  // ["J": ["John"], "C": ["Caesar"], "A": ["Anna", "Anton"]]
print(tableViewHeaders) // ["A", "C", "J"]

I did it within one loop, not few (Swift 4):

struct ContactData {
    let longName: String
    let phones: [String]
    let thumbnailImageData: Data?
}
var contacts = [ContactData]()
var tableViewSource = [Character : [ContactData]]()
var headerTitles = [Character]()

func createContactsData(completionHandler: @escaping () -> Swift.Void) {
    contacts = extractContacts() // convert CNContact to custom ContactData
    tableViewSource.removeAll()
    var prevChar: Character?
    var currentBatch: [ContactData]!
    contacts.forEach { contact in
        guard let firstChar = contact.longName.first else {
            return
        }
        if prevChar != firstChar {
            if prevChar != nil {
                tableViewSource[prevChar!] = currentBatch
            }
            prevChar = firstChar
            currentBatch = [ContactData]()
        }
        currentBatch.append(contact)
    }

    let allKeys = Array(tableViewSource.keys)
    let sortedSymbols = allKeys.sorted(by: {$0 < $1})
    headerTitles = sortedSymbols
    completionHandler()
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!