organizing dates in uitableview sections - ios

I don't know where to start...
I need a TableView to show Information, organized by dates.
I created a Class "Model" to hold the data...
class Model: NSObject {
var start: Date?
var str: String?
override init()
{
}
init(start: Date, str: String) {
self.start = start
self.str = str
}
Creating Elements of that Class
let value1 = Model(start: Date1(), str: string1)
let value2 = Model(start: Date2(), str: string2)
Filling an Array of that Elements:
var array = [Model]()
array.append(value1, value2)
Populating the TableView
How can I divide the array, for example into months, or workweeks and so on...
I want the tableView to organize the data in sections !?
func numberOfSections(in tableView: UITableView) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.text = "January", Febuary, e.g.
return label
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ?
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TC_Arbeitszeit
cell.label.text = ?
return cell
}
Any help is greatly appreciated !

Use the dictionary group by functionality. You can organize your array to keys and values, the key will be your sections and the values will be your raw.
example for grouping by day:
extension Date {
var day: Int? {
let components = Calendar.current.dateComponents([.day], from: self)
return components.day
}
}
let array = [model1, model2]
let dict = Dictionary(grouping: array) { (model) -> Int in
return model.start?.day ?? 0
}
for the example let say that the day of "start" parameter on model1 is Sunday
and the day of "start" parameter on model2 is Monday
so the dict will group it like this:
[1: [model1], 2: [model2]]
now you can use the key as section and the values as rows
func numberOfSections(in tableView: UITableView) -> Int {
return dict.keys ?? 0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let day = dic.keys[section]
let label = UILabel()
label.text = String(day)
return label
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let key = dict[section]
return dict[key].count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TC_Arbeitszeit
let data = dict[indexPath.section]
cell.label.text = String(data[indexPath.row].start.day)
return cell
}

Right now your mode is a simple array. Since you want groupings, your model needs to change a little, so that it includes sections/groups. You might start with an dictionary of arrays, where the keys are, say, the month or workweek.
Have a look at Swift's reduce:into: where you can iterate over your existing array and break it down into such a dictionary.
Then sort the dictionary's keys into a 'sections' array. The count of this is your table view's section count. So now your model has a sections array, and a dictionary, dateInfo.
When the table view asks for a row, look up the key in the sections array, let key = sections[indexPath.section], then find the model item itself:
var dateInfo: [Date: [Model]]
var sections: [Date] // sorted
...
let sectionContent = dateInfo[key] as! [Model]
// rowCount is just sectionContent.count
let rowInfo = sectionContent[indexPath.row]
// populate cell...
Hopefully that's enough of a pointer to get you headed in the right direction.

Based on your case, generating a 2d array would be an appropriate choice.
How to do it?
let transfomredArray = Array(Dictionary(grouping: array, by: { $0. start! }).values)
thus transfomredArray is an array of arrays, each array in should contain models with same date.
Therefore you can handle your tableview data source method based on that, example:
func numberOfSections(in tableView: UITableView) -> Int {
return transfomredArray.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
let date = transfomredArray[section][0].start!
label.text = "\(date)"
return label
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return transfomredArray[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TC_Arbeitszeit
cell.label.text = transfomredArray[indexPath.section][indexPath.row]
return cell
}

Related

dictionary for tableView datasource

I am trying to use a dictionary for a tableView datasource, I am getting an object back from the database that contains a key and an array of values, so a [String: [String]]
var requestedList = [String]()
var keyArr = [String]()
var requestedDictionary = [String: [String]]()
let tQuery = PFQuery(className: "MyClass")
tQuery.whereKey("username", equalTo: PFUser.current()?.username as Any)
tQuery.selectKeys(["descContent", "header"])
do {
let returnedObjects = try tQuery.findObjects()
for object in returnedObjects {
let header = object["header"] as! String
keyArr.append(header)
if let arr = object["descContent"] as! [String]? {
requestedDictionary[header] = arr
requestedList += arr
}
}
} catch {
}
I can't seem to correspond the values correctly to the rows of the tableView however, I was suggested to use an array to store the values which is what I have done with the keyArr. My problem is how do I access the contents of the keys and the corresponding values in the datasource methods?? This is what I have so far but I haven't been able to link the keys and values accordingly
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return requestedList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RequestViewCell", for: indexPath) as! RequestViewCell
cell.descLbl.text = "Your ticket has been requested by \(requestedList[indexPath.row])"
cell.refLbl.text = "for: \(keyArr[indexPath.row])"
cell.leftBtn.tag = (indexPath.section * 100) + indexPath.row
cell.leftBtn.addTarget(self, action: #selector(leftClick(sender:)), for: .touchUpInside)
cell.rightBtn.tag = (indexPath.section * 100) + indexPath.row
cell.rightBtn.addTarget(self, action: #selector(rightClick(sender:)), for: .touchUpInside)
return cell
}
You can turn dictionary into tableView representable data this way.
let requestedDictionary:[String: [String]] = [
"Key-1":["Value-1","Value-2","Value-3","Value-4"],
"Key-A":["Value-X","Value-Y","Value-Z"],
"Key-a":["Value-x","Value-y"],
]
lazy var data:[(key:String,values:[String])] = requestedDictionary.compactMap({(key:$0,values:$1)})
func numberOfSections(in tableView: UITableView) -> Int {
data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data[section].values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = data[indexPath.section].values[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return data[section].key
}
Hope it helps.

How can I query my data & store them by category in an array so as to display them by category in a TableView?

I wish to display my events by category in my TableView where each TableViewCell is a category, with its events found in a CollectionView embedded within the TableViewCell.
Here is a sample initialised prototype of what I want to achieve.
var events = [Events]()
var eventCategory = [EventCategory]()
var testEvent1 = Events(id: 1, event_name: "PrototypeEvent", event_category: "Party", event_date: "10/06/19", event_img_url: "null")
var testEvent2 = Events(id: 2, event_name: "PrototypeEvent2", event_category: "Music", event_date: "11/06/19", event_img_url: "null")
override func viewDidLoad() {
super.viewDidLoad()
var eventArray = [testEvent1]
var eventArray1 = [testEvent2]
var category1 = EventCategory(title: "Party", events: eventArray)
var category2 = EventCategory(title: "Music", events: eventArray1)
eventCategory.append(category1)
eventCategory.append(category2)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventCategory.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 245
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PopularCell", for: indexPath) as! PopularCell
let category = eventCategory[indexPath.row]
for event in category.events! {
print("Event Name:\(event.event_name)")
}
cell.eventCategory = category
return cell
}
make an enum for event_category:
enum EventCategory {
case music
case party
...
}
then use the grouping init of Dictionary
let eventsDictionary = Dictionary(grouping: events) { (element) -> EventCategory in
return element.event_category
}
you will have a dictionary like this [EventCategory: [Events]]; it's will be an easy use for tableView indexPath.row to show a category for every row
You can group the Events based on category using Dictionary's init, i.e.
init(grouping:by:)
Creates a new dictionary whose keys are the groupings returned by the
given closure and whose values are arrays of the elements that
returned each key.
var groupedEventsDict = Dictionary(grouping: events) { $0.event_category }
groupedEventsDict will be of type [String:[Events]], where key is the event_category and value is the array of Events lying under that event_category.
Now, since you need an array for UITableViewDataSource, you need to create an array from groupedEventsDict.
var groupedEventsArr = Dictionary(grouping: events) { $0.event_category }.compactMap({( $0.key, $0.value )})
So, your dataSource methods look something like:
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupedEventsArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PopularCell", for: indexPath)
let (category, events) = self.groupedEventsArr[indexPath.row]
print("Category: \(category)")
events.forEach {
print("Event Name:\($0.event_name)")
}
return cell
}

Regarding TableViews: How can I manage section titles and rows through classes?

So I have been studying Swift and trying to use a TableView with two sections. The thing is:
I have successfully developed an application using TableViewController with just one section and used data from a class called "Opcao" to populate the rows.
So I decided to create another section by setting return 2 on override func numberOfSections(in tableView: UITableView) -> Int and it worked, I only really needed two sections.
My problem: both of the sections are presenting the same number of rows and the same content on it. How could I change it? I mean, I would like the second section called "Teste" to have its own cell fields (different from the first section) but also populated with info of Opcao class.
The sections names on my TableView should be actually the attribute called "section", and the rows content should be the the number of rows in a cell is how many objects there are with which kind of "section". What should I do?
Opcao.swift:
class Opcao {
var nome:String
var descricao: String
var section: String
var segueIdentifier: String
init(nome: String, descricao: String, section: String, segueIdentifier: String){
self.nome = nome //displayed as main value of the cell
self.descricao = descricao //description which goes bellow the cell title
self.section = section // what I though it could be the section tittle which the option belongs to
self.segueIdentifier = segueIdentifier //used for other stuff, not relevant to this situation
}
Parts of TableViewController.swift:
class TableViewController: UITableViewController {
var opcoes: [Opcao] = []
var titulos: [String] = ["1a Habilitação", "Teste"]
override func viewDidLoad() {
super.viewDidLoad()
gerarOpcoes()
}
func gerarOpcoes(){
//criando opcao 1
var opcao1: Opcao
opcao1 = Opcao(nome: "Novo simulado", descricao: "Clique para começar um novo simulado.", section: "phab", segueIdentifier: "A")
self.opcoes.append(opcao1)
//criando opcao 2
var opcao2: Opcao
opcao2 = Opcao(nome: "Responder livremente", descricao: "Responda diversas perguntas sem tempo limite.", section: "phab", segueIdentifier: "B")
self.opcoes.append(opcao2)
//criando opcao 3
var opcao3: Opcao
opcao3 = Opcao(nome: "Histórico", descricao: "Veja seus últimos resultados.", section: "phab", segueIdentifier: "C")
self.opcoes.append(opcao3)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return opcoes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "celula", for: indexPath)
cell.textLabel?.text = self.opcoes[indexPath.row].nome
cell.detailTextLabel?.text = self.opcoes[indexPath.row].descricao
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return titulos[section]
}
You can do it in a variety of ways. Easiest way would be having different arrays for different sections (Although it might not be the best approach). Then altering numberofRowsInSection depending on that too. Lets see:
Create another array:
var opcoesSecond: [Opcao] = []
Another deployment method for second array, this time lets put two objects only:
func gerarOpcoesForSecond(){
var opcao1: Opcao
opcao1 = Opcao(nome: "Novo simulado", descricao: "Clique para começar um novo simulado.", section: "phab", segueIdentifier: "A")
self.opcoesSecond.append(opcao1)
//criando opcao 2
var opcao2: Opcao
opcao2 = Opcao(nome: "Responder livremente", descricao: "Responda diversas perguntas sem tempo limite.", section: "phab", segueIdentifier: "B")
self.opcoesSecond.append(opcao2)
}
Call both array deployment methods in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
gerarOpcoes()
gerarOpcoesForSecond()
}
Then in your numberofRowsInSection method:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return opcoes.count
} else {
return opcoesSecond.count
}
}
In your cellForRowAt method:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "celula", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = self.opcoes[indexPath.row].nome
cell.detailTextLabel?.text = self.opcoes[indexPath.row].descricao
} else {
cell.textLabel?.text = self.opcoesSecond[indexPath.row].nome
cell.detailTextLabel?.text = self.opcoesSecond[indexPath.row].descricao
}
return cell
}
Again as mentioned in the comments, two-dimensional array might be better to prevent code repetition like we have in cellForRowAt method.
But this should solve your problem.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return opcoes.count
}
you should return count based on the section.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "celula", for: indexPath)
cell.textLabel?.text = self.opcoes[indexPath.row].nome
cell.detailTextLabel?.text = self.opcoes[indexPath.row].descricao
return cell
}
again, you should set cell based on the section.

Swift - Populate uitableview with dictionary of [String: [String: String]]

I'm new to Swift, and I am currently creating a diary app that asks the user questions. I'm storing the user's input like this:
dict = ["date": ["question1": "answer", "question2": "answer"]]
Now I need to display this data back to the user in a tableview, where "date" is a title and "question1" is the description.
I've looked online, but answers seem to reference "indexPath.row" for inputting information into a cell, but since this is a dictionary of strings, I can't do that.
Thank you for your help!
Rather than using an array of dictionaries, you should consider using objects that better represent your data.
struct Question: {
let question: String
let answer: String
}
struct DiaryDay {
let date: Date // Note this is a Date object, not a String
let questions: [Question]
}
then you have
let diaryDays = DiaryDay(date: <date>, questions:
[Question(question: "question1": answer: "answer"),
Question(question: "question2": answer: "answer")])
while there's a bit more code, going forward you'll find it easier to see what's happening.
It looks like you should have a section per diary day…
override func numberOfSections(in tableView: UITableView) -> Int {
return diaryDays.count
}
and then one row per question…
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let diaryDay = diaryDays[section]
return diaryDay.questions.count
}
and then configure your cell…
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// dequeue cell
let diaryDay = diaryDays[indexPath.section]
let question = diaryDay.questions[indexPath.row]
cell.question = question
return cell
}
and show the date in the section header…
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let diaryDay = diaryDays[section]
return // formatted diaryDay.date
}
you will have to do a little preparation before you can display data from the dictionary type you are using. Also remember the dictionary is not order list so which order the data will be printed solely depends on system. One approach would be the following
var data = ["date1":["q1":"A1","q2":"A2","q3":"A3"],"date2":["q1":"A1","q2":"A2","q3":"A3"]] . //This is data from your example
var displayableData = [(title: String, qAndA: [(question: String, answer: String)])]() //this is what we will be needing
override func viewDidLoad() {
super.viewDidLoad()
//convert the whole dictionary to tuple
displayableData = data.map { ($0.key, $0.value.map{ ($0.key, $0.value)})}
//here we have converted the dictionary to what we need
}
override func numberOfSections(in tableView: UITableView) -> Int {
return displayableData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return displayableData[section].qAndA.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 55.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let currentQA = displayableData[indexPath.section].qAndA[indexPath.row]
cell.textLabel?.text = "\(currentQA.question) -> \(currentQA.answer)"
return cell
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30.0
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 30.0))
view.backgroundColor = UIColor.lightGray
let label = UILabel(frame: CGRect(x: 10, y: 0, width: view.bounds.width - 20, height: 30.0))
label.text = displayableData[section].title
view.addSubview(label)
return view
}
You can use the dictionary as it is without changing
You should sort before use, remember
let dict = ["date": ["question1": "answer", "question2": "answer"]]
Number of sections
override func numberOfSections(in tableView: UITableView) -> Int {
return dict.count
}
Title of the header
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return Array(dict)[section].key
}
Number of rows in section
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let key = Array(dict)[section].key
return dict[key]?.count ?? 0
}
Cell for row at
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let key = Array(dict)[section].key
if let questionsDict = dict[key] {
let keyValue = Array(questionsDict)[indexPath.row]
print("Question: \(keyValue.key), Answer: \(keyValue.value)")
}
return cell
}
You can try out using map. here Dictionary converts into Array of Dictionary.
let dict = ["date": ["question1": "answer", "question2": "answer"]]
if let value = dict["date"] {
let v = value.map {
["question": $0.key, "answer": $0.value]
}
debugPrint(v)
}

How do I append a TableViewCell to a specific Section in Swift

Lets say that I have three arrays in my ViewController. Two of them represent section cells and one represents the sections.
How do I append a TableViewCell to a specific Section?
ViewController.swift:
// represents my 2 sections
var sectionNames = ["Switches","Settings"]
// data for each section
var switchData = ["switch1","switch2", "switch3"]
var settingData = ["setting1", "setting2"]
A better approach would be to use a dictionary instead of separate arrays:
let data: Dictionary<String,[String]> = [
"Switches": ["switch1","switch2","switch3"],
"Settings": ["setting1","setting2"]
]
Here the dictionary keys are the sections and the values arrays are the data for each section.
So, a tableViewController might look like this:
class MyTableViewController: UITableViewController {
let data: Dictionary<String,[String]> = [
"switches": ["switch1","switch2","switch3"],
"settings": ["setting1","setting2"]
]
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return data.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
let sectionString = Array(data.keys)[section]
return data[sectionString]!.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionString = Array(data.keys)[section]
return sectionString
}
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as! UITableViewCell
// Configure the cell...
let sectionString = Array(data.keys)[indexPath.section]
cell.textLabel?.text = data[sectionString]![indexPath.row]
return cell
}
}
Result:

Resources