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:
Here is the detail controller:
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
In order to have a section for each array element,
return array.count
infunc numberOfSections(in tableView: UITableView)
Set
return 1
infunc tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int)Specify cell value as
array[indexPath.section]
infunc 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
Checking When a Date Has Passed - Swift
How to Read and Write Data to a Text File in Swift Using Playground
Swift 4 Attributedstring Get Typing Attributes
Swiftui @Fetchrequest Core Data Changes to Relationships Don't Refresh
Scenekit Shadow on a Transparent Scnfloor()
Reason for Assigning Optional to New Variable in Conditional Statement in Swift
Getting Results from Arbitrary SQL Statements with Correct Binding in SQLite.Swift
Self.Image.Frame.Width = 20 Give Get Only Property Error
Able to Write/Read File But Unable to Delete File Swift
Iterating Through an Enum in Swift 3.0
Get Url to a Local File with Spm (Swift Package Manager)
Swift Programmatically Set Segues
Iterating with for .. in on a Changing Collection
How to Combine Two Strings (Date & Time) into a New Date in Swift 3
Nsstatusitem in Nsstatusbar, Action Selector Method Not Responding
What Are the Advantages/Use Cases of Optional Patterns Introduced in Swift 2
How to Provide Default Implementations for Uipageviewcontrollerdatasource