How to Make Alphabetically Section Headers in Table View with a Mutable Data Source

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

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
}

Display data from JSON in alphabetical sections in Table View in Swift

I tried to solve your problem using some different approach. Here is the Sample I made.

Here is my controller code and tried to make it as similar to contact app as per the data provided by the API. I just took restaurant name in model as It will only be needed for sorting in alphabetical order. All the other details and explanation are mentioned in comments within the code.

import UIKit

class ViewController: UIViewController {
@IBOutlet weak var restaurantsTableView: UITableView!

//The main array for tableView
var dataArray = [(String,[Restaurant])]()

var indexTitles = [String]()

override func viewDidLoad() {
super.viewDidLoad()
getData()
}

func getData() {

let url = URL(string: "http://barhoppersf.com/json/neighborhoods.json")
URLSession.shared.dataTask(with: url!) { (data, response, error) in

guard let data = data else {return}

let json = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String : AnyObject]

guard let hoods = json?["hoods"] else { return }

guard let names = hoods["neighborhoodNames"] as? [String:[AnyObject]] else { return }

self.makeDataSource(names: names)

DispatchQueue.main.async {
self.restaurantsTableView.reloadData()
}
}.resume()
}

// The main logic for sorting and making the data like Contacts App tableView
func makeDataSource(names:[String:[AnyObject]]) {
//Temporary array to hold restaurants on different indexes
var dict = [String:[Restaurant]]()

//Character set taken to check whether the starting key is alphabet or any other character
let letters = NSCharacterSet.letters

for (_,value) in names {
//Iterating Restaurants
for resObj in value {
if let restaurantName = resObj["name"] as? String {
let restaurant = Restaurant(name: restaurantName)
var key = String(describing: restaurant.name.characters.first!)

key = isKeyCharacter(key: key, letters: letters) ? key : "#"

if let keyValue = dict[key] {
//Already value exists for that key
var filtered = keyValue
filtered.append(restaurant)

//Sorting of restaurant names alphabetically
filtered = filtered.sorted(by: {$0.0.name < $0.1.name})
dict[key] = filtered
} else {
let filtered = [restaurant]
dict[key] = filtered
}
}
}
}
//To sort the key header values
self.dataArray = Array(dict).sorted(by: { $0.0 < $1.0 })

//Logic to shift the # category to bottom
let temp = self.dataArray[0]
self.dataArray.removeFirst()
self.dataArray.append(temp)

//For setting index titles
self.indexTitles = Array(dict.keys.sorted(by: <))

//Making the index title # at the bottom
let tempIndex = self.indexTitles[0]
self.indexTitles.removeFirst()
self.indexTitles.append(tempIndex
}
}

//Function to check whether key is alphabet or not
func isKeyCharacter(key:String,letters:CharacterSet) -> Bool {
let range = key.rangeOfCharacter(from: letters)
if let _ = range {
//Your key is an alphabet
return true
}
return false
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {

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

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantsTableCell") as! RestaurantsTableCell
let restaurant = dataArray[indexPath.section].1[indexPath.row]
cell.restaurantNameLabel.text = restaurant.name
return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

//Pass this model to detail VC
let restaurant = dataArray[indexPath.section].1[indexPath.row]

let detailVC = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
detailVC.restaurant = restaurant
self.navigationController?.pushViewController(detailVC, animated: true)
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}

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

//For index titles
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return self.indexTitles
}
}

The Restaurant class is now separated. Also modified the Sample in the link and created a new DetailViewController class.

import Foundation

class Restaurant {
var name = ""

init(name:String) {
self.name = name
}
}

This code can be improved in the sort logic. Improvements are welcome.

Here is the Output:

Sample Image

Here is the detail controller:

Sample Image

How to display section header\Tile after sorting uitableview

maybe it is not the fastest way, but i think it is simple

first create a small inner class like this:

@interface ProductSection
@property (strong, nonatomic) NSString* sectionName;
@property (strong, nonatomic) NSMutableArray* products;
@end

then use this instead your sort:

NSSortDescriptor *aSort =[[NSSortDescriptor alloc] initWithKey:@"Dis" ascending:YES];
NSArray* products = [distribArray sortUsingDescriptors:[NSMutableArray arrayWithObject:aSort]];

self.sections = [NSMutableArray array];

NSString* currentDistributor = nil;
for (Product* p in products) {
if (![p.Dis isEqualToString:currentDistributor]) {
ProductSection* section = [[ProductSection alloc] init];
section.sectionName = p.Dis;
section.products = [NSMutableArray array];
[self.sections addObject:section];

currentDistributor = p.Dis;
}
ProductSection* section = [self.sections lastObject];
[section.products addObject:p];

}
[self.tableView reloadData];

where self.sections is a mutable array of ProductSection

next use this in your Table View Data Source:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[[self.sections objectAtIndex:section] products] count];

}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.sections count];

}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
[[self.sections objectAtIndex:section] sectionName];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Product* p = [[[self.sections objectAtIndex:indexPath.section] products] objectAtIndex:indexPath.row];
...

}

hope that will help

Add Sections in an Array in Table View in iOS


  1. In order to have a section for each array element, return array.count in func numberOfSections(in tableView: UITableView)

  2. Set return 1 in func tableView(_ tableView: UITableView,
    numberOfRowsInSection section: Int)

  3. Specify cell value as array[indexPath.section] in func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

Dynamically sort NSMutableArray data into NSDictionary alphabetically

There are many possibilities to prepare your data. But since your songs are already sorted your view controller could look something like this:

@interface TableViewController ()

@property (strong, nonatomic) NSArray *sectionTitles;
@property (strong, nonatomic) NSArray *songsInSections;

@end

@implementation TableViewController

- (void)viewDidLoad {
[super viewDidLoad];

NSArray *songs = @[@"A song", @"Another song", @"Some other song", @"Yet another song"];

// store all the needed section titles
NSMutableArray *sectionTitles = [NSMutableArray array];
// store the songs in sections (arrays in array)
NSMutableArray *songsInSections = [NSMutableArray array];

// prepare the data for the table view
for (NSString *song in songs) {
// get the song's section title (first letter localized and uppercased)
NSString *sectionTitle = [[song substringToIndex:1] localizedUppercaseString];

// check if a section for the song's section title has already been created and create one if needed
if (sectionTitles.count == 0 || ![sectionTitle isEqualToString:sectionTitles[sectionTitles.count - 1]]) {
// add the section title to the section titles array
[sectionTitles addObject:sectionTitle];
// create an (inner) array for the new section
[songsInSections addObject:[NSMutableArray array]];
}

// add the song to the last (inner) array
[songsInSections[songsInSections.count - 1] addObject:song];
}

// "store" the created data to use it as the table view's data source
self.sectionTitles = sectionTitles;
self.songsInSections = songsInSections;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.songsInSections count];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return self.sectionTitles[section];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.songsInSections[section] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.songsInSections[indexPath.section][indexPath.row];
return cell;
}

- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return self.sectionTitles;
}

@end


Related Topics



Leave a reply



Submit