I store strings of a view controller in a string array. I import this string array as a Data Source in my table view. This all works smoothly. But now I would like to sort the table view and add section headers. The section header should be from the alphabet, the rows of the meaning sections should be all strings from the array, starting with the letter of the section header.
I know how I can achieve this with static arrays. But how can I make it that only the sections are shown, which also have rows(strings in the array)? And how can I make it so that it generates a new section when saving a new string with a letter, which does not yet exist in the sections?
I hope I have explained it accurately enough. I tried for a long time to solve this problem. It would be great if someone could help me.
Here are some code snippets:
class OverViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var addButton: UIBarButtonItem!
@IBOutlet weak var editButton: UINavigationItem!
var kontaktListe = Telefonbuch.loadArray()
var sections = [[String]]()
var collation = UILocalizedIndexedCollation.currentCollation()
override func viewDidLoad()
{
super.viewDidLoad()
tableView.dataSource = self
configureSectionData()
tableView.reloadData()
}
func configureSectionData()
{
let names = kontaktListe.map{$0.name}
let selector: Selector = "description"
sections = Array(count:collation.sectionTitles.count, repeatedValue: [])
let sortedObjects = collation.sortedArrayFromArray(names, collationStringSelector: selector)
for object in sortedObjects {
let sectionNumber = collation.sectionForObject(object, collationStringSelector: selector)
sections[sectionNumber].append(object as! String)
}
}
I load the object var kontaktListe = Telefonbuch.loadArray()
and get the name property let names = kontaktListe.map{$0.name}
. And there I would like to get the strings to sort and add from.
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()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With