My JSON data file is already alphabetized and is properly showing in my TableView. I set up my section titles using:
struct FigureStats: Decodable {
let name: String
let number: String
let year: String?
}
var figSectionTitles = [String]()
var figures = [FigureStats]()
override func viewDidLoad() {
super.viewDidLoad()
figSectionTitles = [String](arrayLiteral: "#", "A", "B", "C")
}
func tableView( tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return figSectionTitles [section]
}
func tableView( tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return figures.count
}
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://xxxx")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.figures = try JSONDecoder().decode([FigureStats].self, from: data!)
DispatchQueue.main.async {
completed()
}
} catch {
print("JSON Error")
}
}
}.resume()
}
The problem is all of the entries are repeated inside each section instead of falling under the correct section based on the first character of each name.
How can get the first character of each name and then get those entries to fall under the correct section?
You can use init(grouping:by:) to group the array into Dictionary.
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var figures = [Employee]()
var figuresByLetter = [(key: String, value: [Employee])]()
override func viewDidLoad() {
super.viewDidLoad()
figures = [Employee(name: "Kofi", year: 2000, id: 1),Employee(name: "Abena", year: 2002, id: 2),Employee(name: "Efua", year: 2003, id: 3),Employee(name: "Kweku", year: 2003, id: 3),Employee(name: "Akosua", year: 2003, id: 3)]
figuresByLetter = Dictionary(grouping: figures, by: { String($0.name.trimmingCharacters(in: .whitespaces).first!) }).sorted(by: { $0.0 < $1.0 })
print(figuresByLetter)
}
func numberOfSections(in tableView: UITableView) -> Int {
return figuresByLetter.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return figuresByLetter[section].key
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return figuresByLetter[section].value.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = figuresByLetter[indexPath.section].value[indexPath.row].name
return cell
}
}
struct Employee {
var name:String?
var year:Int?
var id:Int?
}
Data after grouping
[(key: "A", value: [Employee(name: "Abena", year: 2002, id: 2), Employee(name: "Akosua", year: 2003, id: 3)]),
(key: "E", value: [Employee(name: "Efua", year: 2003, id: 3)]),
(key: "K", value: [Employee(name: "Kofi", year: 2000, id: 1), Employee(name: "Kweku", year: 2003, id: 3)])]
func numberOfSections(in tableView: UITableView) -> Int {
return figuresByLetter.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionTitle = figuresByLetter[indexPath.section]
return sectionTitle
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionTitle = figuresByLetter[indexPath.section]
//filteredArray = filter the array of figures based on current section value
return filteredArray.count
}
Related
import UIKit
class MessageDetailViewController: UIViewController {
#IBOutlet weak var tableview: UITableView!
struct data{
let time: String
let message: String
let date: String
let type: String
}
struct section {
let date: String
var array = [data]()
}
let sectionArray = [
section(date: "yesterday", array: [
data(time: "3:30 PM", message: "I am looking for your service, can you please give more information on that.", date: "yesterday", type: "sender"),
data(time: "3:40 PM", message: "Sure i am here to help you", date: "yesterday", type: "receiver")
]),
section(date: "today", array: [data(time: "4:40 PM", message: "Ok, I wil contact you on your phone for that.", date: "today", type: "sender")])
]
override func viewDidLoad() {
super.viewDidLoad()
self.tableview.register(UINib(nibName: "SenderCell", bundle: nil), forCellReuseIdentifier: "SenderCell")
self.tableview.register(UINib(nibName: "ReceiverCell", bundle: nil), forCellReuseIdentifier: "ReceiverCell")
self.tableview.register(UINib(nibName: "customHeaderViewCell", bundle: nil), forCellReuseIdentifier: "customHeaderViewCell")
}
}
extension MessageDetailViewController: UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return sectionArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sectionArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if sectionArray[indexPath.section].array[indexPath.row].type == "sender"
{
let cell = tableview.dequeueReusableCell(withIdentifier: "SenderCell", for: indexPath) as! SenderCell
cell.setData(reciverTime: sectionArray[indexPath.section].array[indexPath.row].time, reciverMsg: sectionArray[indexPath.section].array[indexPath.row].message)
return cell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "ReceiverCell", for: indexPath) as! ReceiverCell
cell.setData(reciverTime: sectionArray[indexPath.section].array[indexPath.row].time, reciverMsg: sectionArray[indexPath.section].array[indexPath.row].message)
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
let headerCell = tableView.dequeueReusableCell(withIdentifier: "customHeaderViewCell") as! customHeaderViewCell
if sectionArray[section].date == "yesterday"{
headerCell.labelHeader.text = "yesterday"
}
else{
headerCell.labelHeader.text = "today"
}
headerView.addSubview(headerCell)
return headerView
}
}
This is my message detail code.
I want to achieve these
But through these array I achieved these
what changes should I do in my code so that I can get same day message in one section
this ---
sectionArray.append(section(date: "yesterday", array: [data(time: "3:40 PM", message: "SURE i am here to help you", date: "yesterday", type: "receiver")]))
Making a new section I don't want to make new section when sure I am here o help you array comes.
As I've seen your code, you're using String to indicate the day of the message which is bad practice. Instead, you should use Date, which will help you group the messages by date:
let sectionArray = messages.reduce(into: [section]()) { result, element in
if let section = result.firstIndex(where: {Calendar.current.isDate($0.date, inSameDayAs: element.date)}) {
result[section].append(element)//append message to section
}else {
//create new section
}
}
If you really need to use String, use this:
let sectionArray = messages.reduce(into: [section]()) { result, element in
if let section = result.firstIndex(where: {$0.date == element.date}) {
result[section].append(element)//append message to section
}else {
//create new section
}
}
I am trying to use results from a Realm query as section headers in a UITableView.
Realm classes:
class Person: Object {
#objc dynamic var personId = UUID().uuidString
#objc dynamic var firstName: String = ""
#objc dynamic var surname: String = ""
#objc dynamic var mobileNumber: Int = 0
#objc dynamic var password: String = ""
override static func primaryKey() -> String? {
return "personId"
}
}
class Category: Object {
#objc dynamic var categoryId = UUID().uuidString
#objc dynamic var person: Person?
#objc dynamic var categoryName: String = ""
let categoryContent = List<String>()
override static func primaryKey() -> String? {
return "categoryId"
}
}
My code:
class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let realm = try! Realm()
var itemsInSections: Array<Array<String>> = [["1A"], ["2A"], ["3A"], ["4A"], ["5A"], ["6A"], ["7A"], ["8A"], ["9A"], ["10A"]] //Test content to figure out later
#IBOutlet weak var tableDetails: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableDetails.dataSource = self
tableDetails.delegate = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return getCategoryNames().count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemsInSections[section].count
}
private func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> [String] {
return getCategoryNames()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath)
let text = self.itemsInSections[indexPath.section][indexPath.row]
cell.textLabel!.text = text
return cell
}
func getCategoryNames() -> [String] {
let categoryNames = realm.objects(Category.self).filter("person.mobileNumber == %#", mobileNumber).map({$0.categoryName})
return Array(categoryNames)
}
}
The number of sections works perfectly, but the section headers are blank.
If I add:
print(Array(categoryNames))
to the getCategoryNames function, it returns ["Category 1", "Category 2", "Category 3", "Category 4"] several times. This seems to be the correct format for the string that is required for the section headers, but the table header is not showing.
Try this:
private func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let nameArr = getCategoryNames()
return nameArr[section]
}
Function: func tableView(_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String? so you need to return string for a particular section.
I have a section index that contains full alphabet - however, sections in my table contains only a few letters (e.g. C, F, N, U).
I would like to modify sectionForSectionIndexTitle to properly show right sections when sliding finger over section index. I'm still not sure how to do it using sections.append...
Thank you for help.
This is how my code looks like:
Section.swift
struct Section
{
var heading : String
var items : [String]
init(title: String, objects : [String]) {
heading = title
items = objects
}
}
ViewController.swift
func updateSections(index : Int) {
switch index {
case 0:
sections.append(Section(title: "C", objects: ["Czech Republic"]))
sections.append(Section(title: "F", objects: ["France"]))
sections.append(Section(title: "N", objects: ["Norway"]))
sections.append(Section(title: "U", objects: ["USA"]))
default: break
}
tableViewOutlet.reloadData()
}
let arrIndexSection = ["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"]
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
if segmentedControlOutlet.selectedSegmentIndex == 0 {
return arrIndexSection
} else {
return [""]
}
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
let temp = arrIndexSection as NSArray
return temp.indexOfObject(title)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = sections[section]
return section.items.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].heading
}
try following code in your project :
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
var tempIndex:Int = 0
for str in arrIndexSection {
if str == title {
return tempIndex
}
tempIndex += 1
}
return 0
}
I have a Class and I need to get genre object, put in an array, get unique value (because I will have many)
and use the array to populate section of a tableView.
I have the following code:
class Movie {
var name: String!
var plot: String!
var imageUrl: String!
var genre: String!
init(name: String, plot: String, img: String, genre: String) {
self.name = name
self.plot = plot
self.imageUrl = img
self.genre = genre
}
}
}
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
var loadMovie = [Movie]()
let insertMovie = Movie(name: "Blow", plot: "Blow bla bla bla", img: "", genre: "Action")
let insertMovie2 = Movie(name: "BLade", plot: "Blade bla bla bla", img: "", genre: "Action")
let insertMovie3 = Movie(name: "Inside Out", plot: "Blow bla bla bla", img: "", genre: "Kids")
let insertMovie4 = Movie(name: "Titanic", plot: "Blow bla bla bla", img: "", genre: "Drama")
loadMovie.append(insertMovie)
loadMovie.append(insertMovie2)
loadMovie.append(insertMovie3)
loadMovie.append(insertMovie4)
let unique = NSSet(array: loadMovie.map { $0.genre })
unique returns the unique value ( Action, Drama, Kids), but when I try to get section:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return unique[section].genre
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return unique.count
}
it doesn't work.
Where am I wrong?
Filtering the movies by genre each time the table view is reloaded is quite expensive.
This is a solution using an array of header (genre) names and a dictionary containing a [Movie] array for each genre respectively.
var unique = [String]()
var loadMovie = [String:[Movie]]()
Add a method to insert the movies. The logic is:
• If the genre exists, add the movie for that genre to the [Movie] array in the dictionary.
• If the genre does not exist, add the genre to the unique array and the movie to the dictionary.
func insertMovie(movie : Movie) {
let genre = movie.genre
if unique.contains(genre) {
loadMovie[genre]!.append(movie)
} else {
unique.append(genre)
loadMovie[genre] = [movie]
}
}
Now insert the movies
let movie = Movie(name: "Blow", plot: "Blow bla bla bla", img: "", genre: "Action")
insertMovie(movie)
let movie2 = Movie(name: "BLade", plot: "Blade bla bla bla", img: "", genre: "Action")
insertMovie(movie2)
let movie3 = Movie(name: "Inside Out", plot: "Blow bla bla bla", img: "", genre: "Kids")
insertMovie(movie3)
let movie4 = Movie(name: "Titanic", plot: "Blow bla bla bla", img: "", genre: "Drama")
insertMovie(movie4)
The relevant data source and delegate methods are
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return unique[section]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return unique.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let genre = unique[section]
return loadMovie[genre]!.count
}
In cellForRowAtIndexPath populate the cells with
...
let genre = unique[indexPath.section]
let movie = loadMovie[genre]![indexPath.row]
cell.textLabel!.text = movie.name
...
An alternative is an array of arrays
An update for Swift 4
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return unique[section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return unique.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let genre = unique[section]
return loadMovie[genre]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath)
let genre = unique[indexPath.section]
let movie = loadMovie[genre]![indexPath.row]
cell.textLabel!.text = movie.name
return cell
}
I am working on a table view project I've seen in a tutorial, then I came across this piece of code that gives me the **error: Definition conflicts with previous value.**
The piece of code is:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int, titleForHeaderInSection section:Int) -> String? {
// Return the number of rows in the section.
return animalSelectionTitles[section]
}
I have tried to change the String? into String or Int, but String gives me the same error and Int gives me an error on the return line.
Here's my complete code:
import UIKit
class AnimalTableViewController: UITableViewController {
var animalsDict = [String: [String]] ()
var animalSelectionTitles = [String] ()
let animals = ["Bear", "Black Swan", "Buffalo", "Camel", "Cockatoo", "Dog", "Donkey", "Emu", "Giraffe", "Greater Rhea", "Hippopotamus", "Horse", "Koala", "Lion", "Llama", "Manatus", "Meerkat", "Panda", "Peacock", "Pig", "Platypus", "Polar Bear", "Rhinoceros", "Seagull", "Tasmania Devil", "Whale", "Whale Shark", "Wombat"]
func createAnimalDict() {
for animal in animals {
let animalKey = animal.substringFromIndex(advance(animal.startIndex, 1))
if var animalValues = animalsDict[animalKey] {
animalValues.append(animal)
animalsDict[animalKey] = animalValues
} else {
animalsDict[animalKey] = [animal]
}
}
animalSelectionTitles = [String] (animalsDict.keys)
animalSelectionTitles.sort({ $0 < $1})
animalSelectionTitles.sort( { (s1:String, s2:String) -> Bool in
return s1 < s2
})
}
override func viewDidLoad() {
super.viewDidLoad()
createAnimalDict()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return animalSelectionTitles.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int, titleForHeaderInSection section:Int) -> String? {
// Return the number of rows in the section.
return animalSelectionTitles[section]
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
// Configure the cell...
cell.textLabel?.text = animals[indexPath.row]
// Convert the animal name to lower case and
// then replace all occurences of a space with an underscore
let imageFilename = animals[indexPath.row].lowercaseString.stringByReplacingOccurrencesOfString(" ", withString: "_", options: nil, range: nil)
cell.imageView?.image = UIImage(named: imageFilename)
return cell
}
Two things:
1)
You should change your line
let animalKey = animal.substringFromIndex(advance(animal.startIndex, 1))
Currently it substrings from the 2nd character, which means that for the input Black Swan then animalKey would be equal to lack Swan. Instead you should use the following line:
let animalKey = animal.substringToIndex(advance(animal.startIndex, 1))
2)
There is no method in the UITableViewDataSource Protocol which is called tableView:numberOfRowsInSection:titleForHeaderInSection. Instead you need to split it into the following two methods:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let title = animalSelectionTitles[section]
return animalsDict[title]!.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return animalSelectionTitles[section]
}
UPDATE 1:
In your tableView:cellForRowAtIndexPath, you should also update the retrieving of the animal name to reflect what is stored in the dictionary like so:
// Configure the cell...
let secTitle = animalSelectionTitles[indexPath.section]
let animalName = animalsDict[secTitle]![indexPath.row]
cell.textLabel?.text = animalName