Overview
UILocalizedIndexedCollation is a class that helps to organise data in table views with section index titles in a locale-aware manner. Rather than creating the object directly, a shared instance corresponding to the current locale supported by your application is accessed, with UILocalizedIndexedCollation +currentCollation.
The first task for UILocalizedIndexedCollation is to determine what section index titles to display for the current locale, which can be read from the sectionIndexTitles property.
There are different section index titles for different locale. Like
en_US locale has section titles are : 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, #
AR_sa locale has section titles are : 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, آ, ب, ت, ث, ج, ح, خ, د, ذ, ر, ز, س, ش, ص, ض, ط, ظ, ع, غ, ف, ق, ك, ل, م, ن, ه, و, ي, #
To prepare the data for a section index, your view controller creates a indexed-collation object and then, for each model object that is to be indexed, calls section(for:collationStringSelector:). This method determines the section in which each of these objects should appear and returns an integer that identifies the section.
The table-view controller then puts each object in a local array for its section. For each section array, the controller calls the sortedArray(from:collationStringSelector:) method to sort all of the objects in the section. The indexed-collation object is now the data store that the view controller uses to provide section-index data to the table view.
Now, here is a demo project and code :
We have a Model class Contact like as:
class Contact { var name: String! var mobile: String! init(name: String, mob: String) { self.name = name self.mobile = mob } }
In view controller we have a collection of contacts (which will be shown in tableview in alphabetical sections) like as :
var contacts = [Contact]() //In viewDidLoad method i have created some contacts and append them into contacts array. override func viewDidLoad() { super.viewDidLoad() //1. Create some contact objects for adding in contacts array. let contact1 = Contact(name: "Anuska", mob: "123434") let contact2 = Contact(name: "Anuj Sinha", mob: "2321234") let contact3 = Contact(name: "Maria", mob: "343434") let contact4 = Contact(name: "Jacob", mob: "34454545") let contact5 = Contact(name: "Macculam", mob: "455656") let contact6 = Contact(name: "Sophia", mob: "4567890") //2. Add all these contacts in contacts array self.contacts = [contact1, contact2, contact3, contact4, contact5, contact6] }
In the first part of the demo, we have created a tableview without sections.
Here is the code for TableViewDataSource and TableViewDelegate :
Before implementing tableview datasource, create a tableview cell for contact like below :
Add this code in your ViewController file (Out of ViewContoller class scope)
class ContactCell: UITableViewCell { @IBOutlet var lblName: UILabel! @IBOutlet var lblMobile: UILabel! } extension ViewController : UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return contacts.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ContactCell let contact = contacts[indexPath.row] cell.lblName.text = contact.name cell.lblMobile.text = contact.mobile return cell } }
Now Run your app and see the output screen : it looks like below screenshot
Next, for section wise table view, we need to create partition of contacts array in alphabetical sections.
For this we have used the UILocalizedIndexCollation class.
Add bellow line of code in your ViewController
let collation = UILocalizedIndexedCollation.current() +current() func create a locale collation object, by which we can get section index titles of current locale. Add this extension of UILocalizedIndexedCollation in your project extension UILocalizedIndexedCollation { //func for partition array in sections func partitionObjects(array:[AnyObject], collationStringSelector:Selector) -> [AnyObject] { var unsortedSections = [[AnyObject]]() //1. Create a array to hold the data for each section for _ in self.sectionTitles { unsortedSections.append([]) //appending an empty array } //2. put each objects into a section for item in array { let index:Int = self.section(for: item, collationStringSelector:collationStringSelector) unsortedSections[index].append(item) } //3. sort the array of each sections var sections = [AnyObject]() for index in 0 ..< unsortedSections.count { sections.append(self.sortedArray(from: unsortedSections[index], collationStringSelector: collationStringSelector) as AnyObject) } return sections } }
Above extension have a func partitionObjects(_:) , which contain code in 3 parts, each one is describe in code.
This func will return a array of sorted arrays in alphabetical order.
Now we have used this func from ViewController for partition of contacts array in sections.
Add below line of code in ViewController :
var contactsWithSections = [[Contact]]()
In this array, each item contains the array of Contact objects, which is used to bind with tableview.
Now we should need to modify the viewDidLoad func by adding below line of code :
contactsWithSections = collation.partitionObjects(array: self.contacts, collationStringSelector: #selector(getter: Contact.name)) as! [[Contact]]
Now viewDidLoad func looks like as below:
override func viewDidLoad() { super.viewDidLoad() //1. create some contact objects for adding in contacts array. let contact1 = Contact(name: "Anuska", mob: "123434") let contact2 = Contact(name: "Anuj Sinha", mob: "2321234") let contact3 = Contact(name: "Maria", mob: "343434") let contact4 = Contact(name: "Jacob", mob: "34454545") let contact5 = Contact(name: "Macculam", mob: "455656") let contact6 = Contact(name: "Sophia", mob: "4567890") //2. add all these contacts in contacts array self.contacts = [contact1, contact2, contact3, contact4, contact5, contact6] //3. Create sections of contacts using collation object contactsWithSections = collation.partitionObjects(array: self.contacts, collationStringSelector: #selector(getter: Contact.name)) as! [[Contact]] }
Here collation.partitionObjects is used to partition your collection into sections, which needs two parameters, first parameter is a array and second parameter is a Selector.
Parameters:
- Array: an array to which you want to partition into sections.
- Selector: Selector is used to grouped your collection into sections. (like : name, title, descriptions of the array items’s object)
Make sure you should pass a string property or func (which returns a string) as a Selector.
Another thing which you should need to know is that, the property or func which you have to used as Selector should be @objc type.
In this example, i have used name property of Contact class as Selector. To make the Contact class as objective c type, Contact class should inherited from NSObject class.
I have replaced Contact class with below code:
class Contact : NSObject { var name: String! var mobile: String! init(name: String, mob: String) { self.name = name self.mobile = mob } }
Now next step, to create the section tableview, we need to bind the tableview datasource with contactsWithSections array.
For this, i have replaced tableview datasource code with below code :
func numberOfSections(in tableView: UITableView) -> Int { return contactsWithSections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return contactsWithSections[section].count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ContactCell let contact = contactsWithSections[indexPath.section][indexPath.row] cell.lblName.text = contact.name cell.lblMobile.text = contact.mobile return cell } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return collation.sectionTitles[section] }
Now run the project and see the output. Here is the screenshot of output screen
In above screenshot, you can see the tableview with sections. Each contacts are shown in its proper section titles. But there are some unexpected sections, which does not have any contact items in it.
To remove these empty item sections we need to modify our code once again.
First of all we have to modify the returns type of partitionObjects(_:) func of UILocalizedIndexedCollation extension.
For that i have replaced old partitionObjects(_:) func with below new func :
//func for partition array in sections func partitionObjects(array:[AnyObject], collationStringSelector:Selector) -> ([AnyObject], [String]) { var unsortedSections = [[AnyObject]]() //1. Create a array to hold the data for each section for _ in self.sectionTitles { unsortedSections.append([]) //appending an empty array } //2. Put each objects into a section for item in array { let index:Int = self.section(for: item, collationStringSelector:collationStringSelector) unsortedSections[index].append(item) } //3. sorting the array of each sections var sectionTitles = [String]() var sections = [AnyObject]() for index in 0 ..< unsortedSections.count { if unsortedSections[index].count > 0 { sectionTitles.append(self.sectionTitles[index]) sections.append(self.sortedArray(from: unsortedSections[index], collationStringSelector: collationStringSelector) as AnyObject) } } return (sections, sectionTitles) }
This new partitionObjects(_:) func returns a Tuple Type like as : ([AnyObject], [String]). This tuple contain two types in it. First type of an array of AnyObject in sections and another one is an array of strings which contains custom section titles for tableview.
Now, we also need to modify the code in viewDidLoad() func.
Before that we also declare a String array variable named as sectionTitles in ViewController class for storing the section tiles for tableview.
var sectionTitles = [String]()
and replace below code with
viewDidLoad fund : override func viewDidLoad() { super.viewDidLoad() let contact1 = Contact(name: "Anuska", mob: "123434") let contact2 = Contact(name: "Anuj Sinha", mob: "2321234") let contact3 = Contact(name: "Maria", mob: "343434") let contact4 = Contact(name: "Jacob", mob: "34454545") let contact5 = Contact(name: "Macculam", mob: "455656") let contact6 = Contact(name: "Sophia", mob: "4567890") //add all these contacts in contacts array self.contacts = [contact1, contact2, contact3, contact4, contact5, contact6] //Create sections of contacts using collation object let (arrayContacts, arrayTitles) = collation.partitionObjects(array: self.contacts, collationStringSelector: #selector(getter: Contact.name)) self.contactsWithSections = arrayContacts as! [[Contact]] self.sectionTitles = arrayTitles }
And also replace below code with tableview datasource and delegate
extension ViewController : UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return contactsWithSections[section].count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ContactCell let contact = contactsWithSections[indexPath.section][indexPath.row] cell.lblName.text = contact.name return cell } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionTitles[section] } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 44 } }
Now Run your project and see the output screen, it looks like as below screenshot: