Sorting a TableView alphabetically in Swift 3 - arrays

I have searched Stack Overflow well and truly with a variety of different options offered but none I can seem to get working.
I have an app which has a glossary of terms for NFL (e.g. offence/defence) etc. It comes from a JSON API and is stored locally. I would like to sort the data programmatically, so the terms are all listed in their respective sections ("A", "B", "C" etc.) alphabetically.
View Controller:
import UIKit
class GlossaryVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let glossaryCellId = "GlossaryCell"
var glossarys = [Offence]()
var filteredGlossarys = [Offence]()
private var subCategorySet: Set<String> = []
private var glossarysDictionary: [String: [Offence]] = [:]
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var noResultsView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
filteredGlossarys = glossarys
tableView.delegate = self
tableView.dataSource = self
tableView.backgroundColor = UIColor.clear
searchBar.delegate = self
noResultsView.isHidden = true
hideKeyboardWhenTappedAround()
}
override func viewDidAppear(_ animated: Bool) {
DataProvider.getData(with: .GLOSSARY_DATA_URL) { (letters) in
self.glossarys = letters.flatMap { $0.offences }
self.filteredGlossarys = self.glossarys
self.groupGlossarys()
}
}
func groupGlossarys() {
glossarysDictionary.removeAll()
subCategorySet.removeAll()
for glossary in filteredGlossarys {
subCategorySet.insert(glossary.glossaryLetter!)
}
for subCategory in subCategorySet {
glossarysDictionary[subCategory] = []
}
for glossary in filteredGlossarys {
var list = glossarysDictionary[glossary.glossaryLetter!]
list?.append(glossary)
glossarysDictionary[glossary.glossaryLetter!] = list
}
checkAndShowTableView()
}
func checkAndShowTableView() {
if subCategorySet.count > 0 {
tableView.isHidden = false
noResultsView.isHidden = true
tableView.reloadData()
} else {
tableView.isHidden = true
noResultsView.isHidden = false
}
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header = view as! UITableViewHeaderFooterView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func numberOfSections(in tableView: UITableView) -> Int {
return subCategorySet.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let subCategoryList = Array(subCategorySet)
return subCategoryList[section]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let subCategoryList = Array(subCategorySet)
let glossaryList = glossarysDictionary[subCategoryList[section]]
return glossaryList!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let glossaryCell = tableView.dequeueReusableCell(withIdentifier: glossaryCellId, for: indexPath) as? GlossaryCell else {
fatalError("Unable to dequeue contact cell")
}
let subCategoryList = Array(subCategorySet)
let glossaryList = glossarysDictionary[subCategoryList[indexPath.section]]
glossaryCell.configure(glossary:(glossaryList?[indexPath.row])!)
return glossaryCell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor.clear
cell.contentView.backgroundColor = UIColor.clear
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let viewGlossaryDetailsVC = segue.destination as? GlossaryDetailsVC,
let section = tableView.indexPathForSelectedRow?.section, let row = tableView.indexPathForSelectedRow?.row {
let subCategoryList = Array(subCategorySet)
let glossaryList = glossarysDictionary[subCategoryList[section]]
let glossarys = glossaryList?[row]
viewGlossaryDetailsVC.glossarys = glossarys
}
}
}
extension GlossaryVC: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if !searchText.isEmpty {
filteredGlossarys = glossarys.filter({ (glossary) -> Bool in
let glossaryTitleRange = glossary.glossaryTitle?.range(of: searchText, options: .caseInsensitive)
let glossaryDescriptionRange = glossary.glossaryDescription?.range(of: searchText, options: .caseInsensitive)
let glossaryKeywordsRange = glossary.glossaryKeywords?.range(of: searchText, options: .caseInsensitive)
return glossaryTitleRange != nil || glossaryDescriptionRange != nil || glossaryKeywordsRange != nil
})
} else {
filteredGlossarys = glossarys
}
groupGlossarys()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
}
I have so far tried the sorted(by: {}) method on the let subCategoryList = Array(subCategorySet) in the titleForHeaderInSection which does arrange the section headers alphabetically but the data doesn't change. So you end up having words beginning with "H" in the "A" subcategory.
Data is grouped based on a "letter" key in the database, e.g., so all "A" are grouped together, etc.
Any help would be appreciated, all I want to do is sort it alphabetically and I feel a little out of my depth.

Related

UIKit SearchBar Filter Two Arrays in textDidChange

I have two initialized and uninitialized arrays as follows:
var colours: [String] = ["Blue", "Red", "Green", "Yellow"]
var numbers: [Int] = [11, 12, 13, 14]
var coloursFiltered: [String]?
var numbersFiltered: [Int]?
In my search bar, I could filter only one array in the textDidChange function as follows:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
coloursFiltered = colours.filter({ item -> Bool in
if searchText.isEmpty { return true }
return item.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
/* Later in the Code */
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coloursFiltered!.count
}
I need to filter the numbers array as well to match the corresponding indices of the filtered colours array. I cannot do so as I cannot access the current index in the colours.filter function. This is to achieve the following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
self.tableView.register(UINib.init(nibName: "ItemTableViewCell", bundle: nil), forCellReuseIdentifier: "ItemTableViewCell")
let cell = self.tableView.dequeueReusableCell(withIdentifier: "ItemTableViewCell", for: indexPath) as! ItemTableViewCell
cell.name.text = "\(coloursFiltered![indexPath.row])"
cell.code.text = ""\(numbersFiltered![indexPath.row])" // I need this to work
return cell
}
Is there a way to filter two arrays using the same string?
You better have a model
struct Item {
let color:String
let number:Int
}
Then
var all = [Item(color:"Blue",number:11),.....]
var coloursFiltered = [Item]()
And filter it instead
coloursFiltered = all.filter({ item -> Bool in
if searchText.isEmpty { return true }
return item.color.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coloursFiltered.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
self.tableView.register(UINib(nibName: "ItemTableViewCell", bundle: nil), forCellReuseIdentifier: "ItemTableViewCell")
let cell = self.tableView.dequeueReusableCell(withIdentifier: "ItemTableViewCell", for: indexPath) as! ItemTableViewCell
let item = coloursFiltered[indexPath.row]
cell.name.text = item.color
cell.code.text = "\(item.number)"
return cell
}
I tried the answer from #Sh_Khan and it is working and very helpful. Here is a similar approach to the solution that I found in another question inspired by #Aju Antony.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let result = zip(colours, numbers).filter { name, code in
name.lowercased().contains(searchText.lowercased()) ||
String(code).contains(searchText.lowercased())
}
let filteredNames = result.map({ $0.0 })
let filteredCodes = result.map({ $0.1 })
self.coloursFiltered = filteredNames
self.numberseFiltered = filteredCodes
tableView.reloadData()
}

How do you save new index location in persistentcontainer?

I have a tableview where I have added the ability to move the location of rows. I am able to use the "swapAt" function to update my array and it works fine. The problem is that when I close and re-open the app, the rows continue to show in their old IndexPath. How can I also update the index location in the PersistentContainer so that when I open the app, it is updated with the new IndexPath?
I tried calling the save method of the context but it does not work.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var toDoList = [Item] ()
var count: Int {
toDoList.count
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addItem))
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editItem))
title = "Todo"
loadItem()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toDoList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath)
cell.textLabel?.text = toDoList [indexPath.row].title
if toDoList[indexPath.row].checkmark == true {
cell.accessoryType = .checkmark }
else {
cell.accessoryType = .none
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if toDoList[indexPath.row].checkmark == false {
toDoList[indexPath.row].checkmark = true } else {
toDoList[indexPath.row].checkmark = false
}
tableView.reloadData()
saveItem()
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
toDoList.swapAt(sourceIndexPath.row, destinationIndexPath.row)
saveItem()
}
#objc func addItem () {
let ac = UIAlertController(title: "Add new category", message: nil, preferredStyle: .alert)
var textField = UITextField()
let submitAction = UIAlertAction(title: "Add", style: .default) { (action) in
let newItem = Item(context: self.context)
newItem.title = textField.text
newItem.checkmark = false
newItem.order = Int64(self.count)
self.toDoList.append(newItem)
self.saveItem()
}
ac.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
ac.addAction(submitAction)
present(ac, animated: true, completion: nil)
}
func saveItem () {
do {
try context.save()
} catch {
print("This is the \(error)")
}
self.tableView.reloadData()
}
func loadItem () {
let request: NSFetchRequest<Item> = Item.fetchRequest()
let sortRequest = NSSortDescriptor(key: "order", ascending: true)
request.sortDescriptors = [sortRequest]
do {
toDoList = try context.fetch(request)
} catch {
print("the error is \(error)")
}
tableView.reloadData()
}
#objc func editItem () {
if tableView.isEditing {
tableView.isEditing = false
} else {
tableView.isEditing = true
}}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
context.delete(toDoList[indexPath.row])
saveItem()
self.toDoList.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
I would tackle this problem by having an index property in the Coredata entity; this index reflects the position of the element in the table view. In the swapAtFunction add the logic to switch the index properties of the two elements.
Then, when you fetch the entities, you add a predicate to your request, to sort the elements by the index property.
let fetchRequest = NSFetchRequest<EntityName>(entityName: "EntityName")
let sortDescripor = NSSortDescriptor(key: "index", ascending: true)
fetchRequest.sortDescriptors = [sortDescripor]
Then execute fetchRequest as you normally would, and assign the resulting array to the UICollectionView's Datasource.

Remove item from array in UITableview

What I'm trying to accomplish is when user selects an element for UITableView this element gets append to servicioSeleccionadoarray. But I'm stuck in this because if user decides to deselect the cell I want to remove that item from the array. I've try anyarray.remove(at:)but I can figure the way to tapp into that index.
This is my code so far.
class ServicioHogarViewController: UIViewController{
let serviciosHogar = [String](arrayLiteral: "Alfombras", "Muebles Madera", "Sillones", "Marmol", "Aplicación Teflón","Vestiduras", "Salas", "Colchones", "Sillas Oficinas")
#IBOutlet weak var servicioHogarTB1: UITableView!
var selectedIndex : Int? = nil
var servicioSeleccionado : [String] = []
#IBAction func doneButton(_ sender: UIButton) {
performSegue(withIdentifier: "datePick2", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
servicioHogarTB1.delegate = self
servicioHogarTB1.dataSource = self
servicioHogarTB1.register(UINib(nibName: "ServicioHogarCell", bundle: nil), forCellReuseIdentifier: "servicioCell1")
servicioHogarTB1.separatorStyle = .none
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
print(servicioSeleccionado)
}
}
// MARK : UITabeView Delegation
extension ServicioHogarViewController : UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return serviciosHogar.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark {
servicioSeleccionado.append(serviciosHogar[indexPath.row])
print(servicioSeleccionado)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "servicioCell1", for: indexPath) as! ServicioHogarCell
let servicio = serviciosHogar[indexPath.row]
cell.servicioLabel.text = servicio
return cell
}
}
If you have an array :
var cast = ["Vivien", "Marlon", "Kim", "Karl"]
and you want to remove "Marlon" from it, you can find the index of Marlon using the func firstIndex(of: Element) -> Int? method for an array and then remove it like so :
if let index = cast.firstIndex(of: "Marlon"){
cast.remove(at: index)
print(cast)
}
That said, on didSelectRowAt run this function for your array and you'll accomplish what you're looking to.

Populating an UITableview from a struct

I have two viewControllers one called programlist that displays the list of tiles and populates a a suitable view.
the second viewController inputs the data. Issues implementing the callback due to an error in the prepareForsegue function. Getting the error "Instance member 'callback' cannot be used on type 'addWorkout'"
viewController 1 aka Programlist:
import UIKit
struct Item: Codable {
var title: String
var others: [String]
}
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate{
var Programs = [Item]()
#IBOutlet weak var programTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(Programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
Programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = Programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
Programs.remove(at: indexPath.row)
programTableView.reloadData()
save()
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage"{
workoutController.callback = { [weak self] string in
let entered = Item(title: string, others: ["hi"])
self?.programs.append(entered)
let indexPath = IndexPath(row: self?.programs.count - 1, section: 0)
self?.tableView.insertRows(at: [indexPath], with: .automatic)
self?.save()
}
}
}
}
}
}
viewController 2 aka addWorkout:
import UIKit
class addWorkout: UIViewController {
#IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
The main mistake is you are trying to save an array of Item – which is not supported anyway – to UserDefaults and read an array of String. That's a clear type mismatch.
To be able to save an array of a custom struct to UserDefaults adopt Codable to save the struct as JSON.
struct Item : Codable {
var title: String
var others: [String]
}
Further it's a very bad practice to declare a data source array outside of any class.
This is the ProgramList class with adjusted load and save methods and the data source array inside the class. The method viewDidAppear is not needed.
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var programTableView: UITableView!
var programs = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
programs.remove(at: indexPath.row)
programTableView.deleteRows(at: [indexPath], with: .automatic)
save()
}
}
}
To share data between controllers use a closure as callback and pass the string
class AddWorkout: UIViewController {
#IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
#IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
Back in ProgramList controller assign a closure to the callback property in prepareForSegue (or right before presenting the controller)
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage" {
let workoutController = segue.destination as! AddWorkout
workoutController.callback = { string in
let entered = Item(title: string, others: ["hi"])
self.programs.append(entered)
let indexPath = IndexPath(row: self.programs.count - 1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .automatic)
self.save()
}
}
}

How do I populate two sections in a tableview with two different arrays using swift?

I have two arrays Data1 and Data2 and I want to populate the data within each of these (they contain strings) into a tableview in two different sections.
The first section should have a heading "Some Data 1" and the second section should be titled "KickAss".
I have both sections populating with data from the first array (but with no headings either).
Here is my code so far:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rowCount = 0
if section == 0 {
rowCount = Data1.count
}
if section == 1 {
rowCount = Data2.count
}
return rowCount
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let ip = indexPath
cell.textLabel?.text = Data1[ip.row] as String
return cell
}
in the cellForRowAtIndexPath method, is it possible for me to identify the section somehow like I did in the numberOfRowsInSection method?
Also, how do I give titles to each section?
TableView Cells
You could use a multidimensional array. For example:
let data = [["0,0", "0,1", "0,2"], ["1,0", "1,1", "1,2"]]
For the number of sections use:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return data.count
}
Then, to specify the number of rows in each section use:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data[section].count
}
Finally, you need to setup your cells:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellText = data[indexPath.section][indexPath.row]
// Now do whatever you were going to do with the title.
}
TableView Headers
You could again use an array, but with just one dimension this time:
let headerTitles = ["Some Data 1", "KickAss"]
Now to set the titles for the sections:
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < headerTitles.count {
return headerTitles[section]
}
return nil
}
The code above checks to see there's a title for that section and returns it, otherwise nil is returned. There won't be a title if the number of titles in headerTitles is smaller than the number of arrays in data.
The Result
You could create a Struct to hold the data that belongs to a section, as an alternative to my previous answer. For example:
struct SectionData {
let title: String
let data : [String]
var numberOfItems: Int {
return data.count
}
subscript(index: Int) -> String {
return data[index]
}
}
extension SectionData {
// Putting a new init method here means we can
// keep the original, memberwise initaliser.
init(title: String, data: String...) {
self.title = title
self.data = data
}
}
Now in your view controller you could setup your section data like so:
lazy var mySections: [SectionData] = {
let section1 = SectionData(title: "Some Data 1", data: "0, 1", "0, 2", "0, 3")
let section2 = SectionData(title: "KickAss", data: "1, 0", "1, 1", "1, 2")
return [section1, section2]
}()
Section Headers
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return mySections.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return mySections[section].title
}
Compared to my previous answer, you now don't have to worry about matching the number of headerTitles to the number of arrays in data.
TableView Cells
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mySections[section].numberOfItems
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellTitle = mySections[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = cellTitle
return cell
}
You can determine which section you are in by looking at indexPath.section.
To specify the titles, override the function
func tableView(tableView: UITableView!, titleForHeaderInSection section: Int) -> String!
In Swift 4 or Swift 5 you can use the code below.
Here a custom header section with filter is shown:
create a project
Add table view
create UITableView Cell
Connect label to uitable view & table with view controller
Add bellow code
import UIKit
struct Category {
let name : String
var items : [[String:Any]]
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UITextFieldDelegate {
#IBOutlet weak var txtName: UITextField!
#IBOutlet weak var tableView: UITableView!
var originalArr = [[String:Any]]();
var recentArr = [[String:Any]]();
var searchArrRes = [[String:Any]]()
var searching:Bool = false
//
var sections = [Category]()
override func viewDidLoad() {
super.viewDidLoad()
//Assign delegate don't forget
txtName.delegate = self
tableView.delegate = self
tableView.dataSource = self
recentArr = [
["name": "Enamul", "number": "+8800000003"],
["name": "Enam", "number": "+8800000004"]
]
originalArr = [
["name": "abdul", "number": "+8800000001"],
["name": "abdin", "number": "+8800000002"],
["name": "Enamul", "number": "+8800000003"],
["name": "enam", "number": "+8800000004"],
["name": "Rafi", "number": "+8800000005"],
["name": "Ehaque", "number": "+8800000006"]
]
//my array
sections = [
Category(name:"Recent", items:recentArr),
Category(name:"ALL", items:originalArr)
]
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
guard let tableView = view as? UITableViewHeaderFooterView else { return }
tableView.textLabel?.textColor = UIColor.red
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
//input text
let searchText = textField.text! + string
searchArrRes = self.originalArr.filter({(($0["name"] as! String).localizedCaseInsensitiveContains(searchText))})
if(searchArrRes.count == 0){
searching = false
}else{
searching = true
}
self.tableView.reloadData();
return true
}
func numberOfSections(in tableView: UITableView) -> Int {
if( searching == true){
return 1
}
return self.sections.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if( searching == true){
return ""
}
return self.sections[section].name
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if( searching == true){
return searchArrRes.count
}else{
let items = self.sections[section].items
return items.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Custom_cell
// var dict = itemsA[indexPath.section]
if( searching == true){
var dict = searchArrRes[indexPath.row]
cell.name.text = dict["name"] as? String
cell.number.text = dict["number"] as? String
}else{
let items = self.sections[indexPath.section].items
let item = items[indexPath.row]
cell.name.text = item["name"] as? String
cell.number.text = item["number"] as? String
}
return cell
}
}
You can download full source from GitHub.GitHub Like: https://github.com/enamul95/Custom_table_view_section.git
Can do Sections in Tableview and can change the colours of Header sections
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tabview: UITableView!
var tablecell = NewTableViewCell()
let data = [["SWIFT", "BALENO", "ALTO", "CIAZ"], ["INNOVA", "GLANZA", "FORTUNER"] , ["BMW X5", "BMW M4", "BMW 7 Series", "BMW X7", "BMW i3"]]
let brand: Array<String> = ["MARUTHI", "TOYOTA", "BMW"]
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return brand.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data[section].count }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.brand[section]
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
// can change the header color of background and title with this code :)
(view as! UITableViewHeaderFooterView).contentView.backgroundColor = UIColor.red.withAlphaComponent(0.4)
(view as! UITableViewHeaderFooterView).textLabel?.textColor = UIColor.yellow
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell1: NewTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell1") as! NewTableViewCell
let text = data[indexPath.section][indexPath.row]
cell1.textLabel!.text = text
return cell1
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Resources