Alphabetical Sections in Table Table View in Swift

Alphabetical sections in table table view in swift

You can put your arrays with names into dictionary with letter keys.

For example

var names = ["a": ["and", "array"], "b": ["bit", "boring"]]; // dictionary with arrays setted for letter keys

then you need to access values in your dictionary in the next way

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return names[usernames[section]].count; // maybe here is needed to convert result of names[...] to NSArray before you can access count property
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{

let cellID = "cell"

let cell: UITableViewCell = self.tv.dequeueReusableCellWithIdentifier(cellID) as UITableViewCell

cell.textLabel?.text = names[usernames[indexPath.section]][indexPath.row]; // here you access elements in arrray which is stored in names dictionary for usernames[indexPath.section] key

return cell
}

Alphabetical sections in table table view

In cellForRowAt You need to use contactValue instead of contactArray.

Change line

cell.textLabel?.text = contactArray[indexPath.row]

To

cell.textLabel?.text = contactValue[indexPath.row]

Also instead of titleForFooterInSection you need to implement titleForHeaderInSection.

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

To change the Section title color to white implement willDisplayFooterView method.

override func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) {
if let view = view as? UITableViewHeaderFooterView {
view.textLabel?.textColor = .white
}
}

Change sectionIndexBackgroundColor property of tableView to color that you want instead of white color.

self.tableView.sectionIndexBackgroundColor = .black

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

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

How do I get alphabetic tableView sections from an object

You have to group contactArray and sections (this name is sufficient) must become the data source array

var sections = [Section]()

and you have to declare Section

struct Section {
let letter : String
let people : [ExternalAppContactsBook]
}

let groupedDictionary = Dictionary(grouping: contactArray, by: {String($0.lastName.prefix(1))})
let keys = groupedDictionary.keys.sorted()
sections = keys.map{Section(letter: $0, people: groupedDictionary[$0]!.sorted(by: {$0.lastName < $1.lastName})}

According to my answer in the linked question the datasource and delegate methods are

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].people.count
}

func numberOfSectionsInTableView(tableView: UITableView) -> Int{
return sections.count
}

func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return sections.map{$0.letter}
}

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

Event generation on selection of Alphabetical sections in table table view in swift

There is no direct event you could handle when the user taps on a section index title, but what you could implement is

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

This method is called on the data source and translates a section index title to a section in the table view. This is typically called when the user taps on an entry in the index.

UITableView with Multiple Sections in Alphabetical Order using Realm and Swift, with multiple labels connected with same data

There are a number of issues with the code in the question and instead of trying to dissect them, let me provide an example that may help.

Suppose we have a bunch of fruits stored in Realm. We want to display those in alphabetical order in a tableview using sections

A
Apple
B
Banana

etc

This first piece of code is a structure to hold a section title and then an array of the fruits that go in that section. Also, a class fruitNameArray which will act as our tableView DataSource that holds all of the FruitStructs.

struct FruitStruct {
var sectionTitle = ""
var fruitNameArray = [String]()
}

var fruitDataSource = [FruitStruct]()

I am using an array to hold the initial data but in your case it could be a Realm results object populated from a Realm. This is called from viewDidLoad to organize our data into the dataSource array

func setupDataSourceData() {
let fruitArray = ["Apple", "Pear", "Banana", "Bing Cherry", "Grape", "Orange", "Plum", "Watermelon", "Cantelope"]

let allFirstChars = fruitArray.map { String($0.prefix(1)) } //get all first chars for section titles
let sectionTitles = Array(Set(allFirstChars)).sorted() //eliminate dups and sort

//iterate over the unique section titles and get the fruits that are in that section
// sort and then craft structs to hold the title and the associated fruits
sectionTitles.forEach { firstChar in
let results = fruitArray.filter { $0.prefix(1) == firstChar }
let sortedFruits = results.sorted()
let fruit = FruitStruct(sectionTitle: firstChar, fruitNameArray: sortedFruits)
fruitDataSource.append(fruit)
}

for fruitData in fruitDataSource {
print(fruitData.sectionTitle)
let fruits = fruitData.fruitNameArray
for fruitName in fruits {
print(" \(fruitName)")
}
}
}

Then, there are 4 functions a tableView needs to show the sections and then the rows in each section. You appear to have these 4 but an additional one sectionIndexTitles which may not be needed.

//
//handle sections
//
func numberOfSections(in tableView: UITableView) -> Int {
return self.fruitDataSource.count
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let title = self.fruitDataSource[section].sectionTitle
return title
}

//
//handleTableView rows
//
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let rowsInSection = self.fruitDataSource[section].fruitNameArray.count
return rowsInSection
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellReuseIdentifier", for: indexPath)
let text = self.fruitDataSource[indexPath.section].fruitNameArray[indexPath.row]
cell.textLabel?.text = text
return cell
}

Note that I am not performing any filtering and any other stuff as the tableView is being populated. That section of code needs to be fast and lean to the UI is as responsive as possible.

I am just getting a string

let text = self.fruitDataSource[indexPath.section].fruitNameArray[indexPath.row]

but in your case, you may want to return an exersise object where you could then get the exerciseName and bodypartName to then assign those in the custom cellView

Best Practice Note: class properties should be lower cased - exerciseName, not uppercase, ExerciseName. Class and Struct names are typically capitalized.

How do you sort data in a tableView alphabetically by section using a custom model class?

You are doing it the wrong way. You are not using your data to generate your index.

Let's consider you have an array of your data var name: [Name]

  1. Define the section into which every Name belongs:
extension Name {
var titleFirstLetter: String {
return String(self.nameTitle[self.nameTitle.startIndex]).uppercased()
}
}

  1. Generate the index from your data
// all the first letters in your data
let firstLetters = names.map { $0.titleFirstLetter }
// some letters appear multiple times, let's remove duplicates
let uniqueFirstLetters = Array(Set(firstLetters))
// sort them
// this is your index
let sortedFirstLetters = uniqueFirstLetters.sorted()

  1. Generate sections
let sections: [[Name]] = sortedFirstLetters.map { firstLetter in
return names
.filter { $0.titleFirstLetter == firstLetter } // only names with the same first letter in title
.sorted { $0.nameTitle < $1.nameTitle } // sort them
}

  1. Use them
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sortedFirstLetters[section]
}

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

func numberOfSections(in tableView: UITableView) -> Int {
return sections.count // or sortedFirstLetters.count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let name = sections[indexPath.section][indexPath.row]
...
}

Edit - complete example:

class Name {
let nameTitle: String
let nameDetail: String

init(nameTitle: String, nameDetail: String) {
self.nameTitle = nameTitle
self.nameDetail = nameDetail
}

var titleFirstLetter: String {
return String(self.nameTitle[self.nameTitle.startIndex]).uppercased()
}
}

class ViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView?

var names: [Name] = []

var sortedFirstLetters: [String] = []
var sections: [[Name]] = [[]]

override func viewDidLoad() {
super.viewDidLoad()

let cell001 = Name(nameTitle: "Acker", nameDetail: "Details for Acker are listed here.")
names.append (cell001)

let cell002 = Name(nameTitle: "Baker", nameDetail: "Details for Baker are listed here.")
names.append (cell002)

let cell003 = Name(nameTitle: "Caker" , nameDetail: "Details for Caker are listed here.")
names.append (cell003)

let cell004 = Name(nameTitle: "Dacker", nameDetail: "Details for Dacker are listed here.")
names.append (cell004)

let cell005 = Name(nameTitle: "Ecker", nameDetail: "Details for Ecker are listed here.")
names.append (cell005)

let cell006 = Name(nameTitle: "Facker", nameDetail: "Details for Facker are listed here.")
names.append (cell006)

let firstLetters = names.map { $0.titleFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))

sortedFirstLetters = uniqueFirstLetters.sorted()
sections = sortedFirstLetters.map { firstLetter in
return names
.filter { $0.titleFirstLetter == firstLetter }
.sorted { $0.nameTitle < $1.nameTitle }
}
}

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

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

func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let name = sections[indexPath.section][indexPath.row]

let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = name.nameTitle
cell.detailTextLabel?.text = name.nameDetail

return cell
}
}

example screenshot



Related Topics



Leave a reply



Submit