I have a JSON file with Title, startDate, and endDate. I would like to group my sections in my UITableView by dates and to add the date of each group as the header. I am unsure of how to retrieve the data from my groupByDate function to populate the specific sections. Would I have to create a new array from my groupByDate function.
var eventList: [Event] = []
var eventGroup: [[Event]] = []
func numberOfSections(in tableView: UITableView) -> Int {
return eventGroup.count
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
v.backgroundColor = .darkGray
return v
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 3.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableCell
cell.titleLabel.textColor = .red
}else{
cell.titleLabel.textColor = .black
}
return cell
}
You need to get the idea first. Imagine you just have one section of all your events. What would you have as your model?
A title
An array of events
Right?
Okay if the above makes sense, you then would need multiple of that said models. Right again? Now break that into more specific terms. Like actual model type.
struct Event {
let title: String
let startDate: Date
// maybe an endDate object too
}
struct Section {
let title: String
let events: [Event]
}
So now you will need to provide an array of Section object to the table view.
But before that you will need to group your sections by the startDate object. Swift has a pretty convenience initializer of Dictionary type that allows us to group an array by choosing a particular property's value from the object in the array. Pay attention to the function below:
func groupedSectionsByDate(from events: [Event]) -> [Section] {
let grouped = Dictionary(grouping: events) { $0.startDate }
// here you will need a date formatter object that will be used to convert
// the Date type to String type. It's left as an assignment for the reader
let dateFormatter: DateFormatter // init and configure it yourself
let sections = grouped.map { Section(title: dateFormatter.string(from: $0.key), events: $0.value) }
return sections
}
The above function should get you the sections with titles as grouped by date.
Now how would you use it with the table view?
class TableViewController: UITableViewController {
. . .
let sections = [Section]()
. . .
. . .
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
. . .
let event = sections[indexPath.section].events[indexPath.row]
. . .
}
. . .
. . .
}
update
The initial data of the hypothesis is like this
[
{
"title": "Bicycling with Friends",
"start": "11-01-2018"
},
{
"title": "Yoga",
"start": "11-01-2018"
},
{
"title": "Yoga",
"start": "11-02-2018"
}
]
If you want to group display, you need to assemble the array into dates like this, each group of titles can use start
[
[
{
"title": "Bicycling with Friends",
"start": "11-01-2018"
},
{
"title": "Yoga",
"start": "11-01-2018"
}
],
[
{
"title": "Yoga",
"start": "11-02-2018"
}
]
]
I think your data source can hold a two-dimensional array eventGroup, or a dated dictionary [dateString : [event]]. Only use eventGroup can be modified to be like this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventGroup[section].count
}
func numberOfSections(in tableView: UITableView) -> Int {
return eventGroup.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let events = eventGroup[section]
let event = events.first
return event?.title //Replace with the header title you want
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableCell
let events = eventGroup[indexPath.section]
let dateRow = events[indexPath.row].start
let dateRowEnd = events[indexPath.row].end
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let myString = formatter.string(from: dateRow)
let yourDate = formatter.date(from: myString)
formatter.dateFormat = "MM-dd-yyyy h:mm a"
let myStringafd = formatter.string(from: yourDate!)
cell.titleLabel.text = eventList[indexPath.row].title
cell.dateStartLabel.text = "Start date: \(myStringafd)"
if events[indexPath.row].conflict == true {
cell.titleLabel.textColor = .red
}else{
cell.titleLabel.textColor = .black
}
return cell
}
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
}
}
just like title says, I would like to categorize my data by some property which I get from json.
Here is example of my json:
{
"rows": [
{
"id": 1,
"name": "Trip to London",
"status": "CURRENT",
"companyId": 395,
"approvedBy": null,
"start": "2021-01-12T00:00:00.000Z",
"end": "2021-01-13T00:00:00.000Z",
"approvedAt": null,
"updatedBy": null,
"createdAt": "2021-01-04T13:32:45.816Z",
"updatedAt": "2021-01-04T13:32:45.816Z",
"services": "Flight"
}
]
}
I have 3 states of my response, which is status: String in json response and possible values are: upcoming, current and previous. I would like to categorize my data by status property, if I get that trip is current, then put it under section title of current.
What I tried so far is this:
In numberOfRowsInSection:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching {
return self.filteredData.count
}
return bookedTrips.count
}
In viewForHeaderInSection:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: Cells.sectionTitle) as! TripsListHeaderCell
if section == 0 {
cell.configure(trips: "CURRENT")
} else if section == 1 {
cell.configure(trips: "UPCOMING")
} else {
cell.configure(trips: "PREVIOUS")
}
return cell
}
EDIT:
In cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.tripInfo) as! TripsListDetailCell
if isSearching {
cell.configure(trips: filteredData[indexPath.row])
} else {
cell.configure(trips: bookedTrips[indexPath.row])
}
return cell
}
And if I return number 3 in function numberOfSections, I have my section titles displayed, but since I returned count in numberOfRowsInSection, everything is duplicated. What should I do to categorize my data by status prop from json, since I don't have 2D array so I could work with sections?
And of course, if there is better way how to write viewForHeaderInSection, which I am pretty sure there is, please give me advice.
I can post some screenshots if needed if my question is not properly written and its hard to understand what I am trying to say.
Step 1 : You need to create struct for Trip like as below
struct TripStruct
{
var sectionName : String
var tripArray : [TripModel] = [] // Here TripModel == Your Trip Model
}
Step 2 : You need to group data into section
var tripStructList : [TripStruct] = [] //Here this Global Variable
var filterTripStructList : [TripStruct] = [] //Here this Global Variable
let groupedDictionary = Dictionary(grouping: allTripArray, by: { $0. status }) // Here allTripArray == Your trip array from json
let keys = groupedDictionary.keys.sorted()
self.tripStructList = keys.map({ TripStruct(sectionName: $0, tripArray: groupedDictionary[$0]!)})
Step 3 : Set data in tableView delegate and datasoure method like as below
func numberOfSections(in tableView: UITableView) -> Int
{
return isSearching ? self.filterTripStructList.count : self.tripStructList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return isSearching ? self.filterTripStructList[section].tripArray.count : self.tripStructList[section].tripArray.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: Cells.sectionTitle) as! TripsListHeaderCell
let sectionRow = isSearching ? filterTripStructList[section] : tripStructList[section]
cell.configure(trips: sectionRow.sectionName)
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.tripInfo) as! TripsListDetailCell
let sectionRows = isSearching ? filterTripStructList[indexPath.section] : tripStructList[indexPath.section]
let row = sectionRows.tripArray[indexPath.row]
cell.configure(trips: row)
return cell
}
Step 4 : Search your data
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText == ""
{
isSearching = false
filterTripStructList.removeAll()
self.tableView.reloadData()
return
}
else
{
isSearching = true
let filterAllTripArray = self.allTripArray.filter({$0.tripName.lowercased().contains(searchText.lowercased())}) // Here allTripArray == Your trip array from json
let groupedDictionary = Dictionary(grouping: filterAllTripArray, by: { $0.status })
let keys = groupedDictionary.keys.sorted()
self.filterTripStructList = keys.map({ TripStruct(sectionName: $0, tripArray: groupedDictionary[$0]!)})
self.tableView.reloadData()
}
}
i am building a table view with custom cells. in time the cell should contain various labels, but for the moment, there is only one label (serves as a test).
when I run the app, everything looks ok, however the cell.textLabel.text does not appear in the app. Each cell is blank.
for the data displayed, I built a model in a separate file visible here :
import Foundation
class CountryDataModel {
let countryName: String
let casesData: String
let deathsData: String
let recoveriesData: String
init(country: String, cases: String, deaths: String, recoveries: String) {
countryName = country
casesData = cases
deathsData = deaths
recoveriesData = recoveries
}
}
here is the view controller for the table view :
import UIKit
class PaysTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
listOfCountryAndRelatedData()
}
//MARK: - Relevant data
var countryList = [CountryDataModel]()
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return countryList.count
}
//
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Configure the cell...
let indexPath = countryList[indexPath.row]
cell.textLabel?.text = indexPath.countryName
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("success")
}
func listOfCountryAndRelatedData() {
let country1 = CountryDataModel(country: "Algeria", cases: "0", deaths: "0", recoveries: "0")
countryList.append(country1)
let country2 = CountryDataModel(country: "Bahamas", cases: "0", deaths: "0", recoveries: "0")
countryList.append(country2)
let country3 = CountryDataModel(country: "Seychelles", cases: "0", deaths: "0", recoveries: "0")
countryList.append(country3)
}
}
set the numberOfSections as 1
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
Then refresh the your Table after add data to your countryList
func listOfCountryAndRelatedData() {
//add your data to arry
self.tableView.reloadData()
}
I currently have a json response from json which is a [NSDictionary]. This is displayed in a tableview and I was able to set the sorted dates into headers however I am having a difficulties in setting the uilabel in the cellForRowAt function. The tableview I was looking to display has a header title of sorted dates(which I already have) and under the section are the names with the same date as the header. I have provided the code for this below. Thank you for your help and suggestions.
import UIKit
import Alamofire
class JSONTableViewController: UITableViewController
{
var responseValue:[NSDictionary] = []
var sortedResponsevalue:[NSDictionary] = []
var sectionHeaderArray:[String] = []
var rowTitle:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
for response in self.responseValue {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let responseDate = response.object(forKey: "date") as! String
let date = dateFormatter.date(from: responseDate)
print(date!.toString(dateFormat: "MMM d, yyyy"))
self.sectionHeaderArray.append(date!.toString(dateFormat: "MMM d, yyyy"))
self.rowTitle.append(response.object(forKey: "name") as! String)
}
print(self.sectionHeaderArray.count)
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sectionHeaderArray[section]
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return self.sectionHeaderArray.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sectionHeaderArray[section].count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jsonCell", for: indexPath) as! JSONTableViewCell
if let nameString = self.responseValue[indexPath.row].object(forKey: "name") as? String {
cell.jsonLabel.text = nameString
}
return cell
}
}
What I understand from your question is: You are getting the same title for "nameString" in cellForRow Method.
The problem is
if let nameString = self.responseValue[indexPath.row].object(forKey: "name") as? String {
cell.jsonLabel.text = nameString
}
You are passing the number of sections self.sectionHeaderArray.count. as the total section = 100 and each section has only 1 row, so title will always be remains the same.
TRY THIS : use indexPath.section instead of indexPath.row to get different titles
if let nameString = self.responseValue[indexPath.section].object(forKey: "name") as? String {
cell.jsonLabel.text = nameString
}
EDIT
class JSONTableViewController: UITableViewController
{
var responseValue:[NSDictionary] = []
var sortedResponsevalue:[NSDictionary] = []
var sectionHeaderArray = [[String: Any]]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
var currentDate: String? = nil
var nameArray = [String]()
for (index,response) in self.responseValue.enumerated() {
let responseDate = response.object(forKey: "date") as! String
let date = dateFormatter.date(from: responseDate)
let dateString = date!.toString(dateFormat: "MMM d, yyyy")
let nameString = response.object(forKey: "name") as! String
if currentDate == nil {
// FIRST TIME
currentDate = dateString
nameArray.append(nameString)
} else {
// CHECK IF DATES EQUAL, THEN KEEP ADDING
if currentDate == dateString {
nameArray.append(nameString)
if index == self.responseValue.count - 1 {
let dictToAppend: [String : Any] = ["date": currentDate!, "names": nameArray]
self.sectionHeaderArray.append(dictToAppend)
}
} else {
let dictToAppend: [String : Any] = ["date": currentDate!, "names": nameArray]
self.sectionHeaderArray.append(dictToAppend)
currentDate = dateString
nameArray.removeAll()
nameArray.append(nameString)
}
}
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let dictInfo = self.sectionHeaderArray[section]
let dateString = dictInfo["date"]
return dateString as? String
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return self.sectionHeaderArray.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let dictInfo = self.sectionHeaderArray[section]
let namesArray = dictInfo["names"] as! [String]
return namesArray.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jsonCell", for: indexPath) as! JSONTableViewCell
let dictInfo = self.sectionHeaderArray[indexPath.section]
let namesArray = dictInfo["names"] as! [String]
let nameString = namesArray[indexPath.row]
cell.jsonLabel.text = nameString
return cell
}
}
Try and share the results.
This is a different approach using the Decodable protocol to decode the JSON into a struct and the optimized initializer Dictionary(grouping:by:) to group the items by date.
Create a struct (can be outside the view controller)
struct Response : Decodable {
let id : Int
let date : Date
let name : String
}
Inside the view controller create a dateformatter for the header titles
let dateFormatter : DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "MMM d, yyyy"
return formatter
}()
and a native dictionary (never NSDictionary) for the data source and an array for the sections which are Date instances. The benefit of using Date is the ability to be sorted (the string date format cannot be sorted properly)
var sections = [Date]()
var dataSource = [Date:[Response]]()
Assuming data is the JSON data in viewDidLoad decode data into the struct and populate the data source. The method Dictionary(grouping:by:) groups the array of Response into a dictionary with the date as key.
override func viewDidLoad() {
super.viewDidLoad()
// put here the code to retrieve the JSON data in the variable `data`
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do {
let result = try decoder.decode([Response].self, from: data)
dataSource = Dictionary(grouping: result, by: {$0.date})
sections = dataSource.keys.sorted()
self.tableView.reloadData()
} catch {
print(error)
}
}
The datasource and delegate methods are
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return dateFormatter.string(from: sections[section])
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionDate = sections[section]
return dataSource[sectionDate]!.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jsonCell", for: indexPath) as! JSONTableViewCell
let sectionDate = sections[indexPath.section]
let item = dataSource[sectionDate]![indexPath.row]
cell.jsonLabel.text = item.name
return cell
}
I'm a complete rookie at Swift and iOS programming so you'll have to forgive the perhaps simple question.
I've created a tableView which displays the contents of an array (strings) at the press of a button.
Now, I'd like to "group" these strings in tableView sections, sorted by date.
In more detail: When the user taps the button, the string should be inserted at index 0 of the array and be displayed in a section with a header of todays date. If there's values older than today's date in the array, these should be displayed in a separate section for that date. Each section should correspond to a 24 hour day and display all the strings added during that day.
Here's some sample code of what I've achieved so far:
var testArray[String]()
var sectionsInTable[String]()
#IBOutlet weak var testTable: UITableView!
#IBAction func saveButton(sender: AnyObject) {
testArray.insert("\(strTest)", atIndex: 0)
testTable.reloaddata()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionsInTable.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return testArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = String(testArray[indexPath.row])
return cell
}
I really don't know how to manage the sections part. Hopefully someone can point me in the right direction. Thanks!
I was in need for something similar, and while Ron Fessler's solution works, when there's a lot of sections/rows, it took a very long time for table to load data, and even after that it wasn't much responsive. Main issue there I think is getSectionItems function as it will always go through all of items...
My solution:
struct TableItem {
let title: String
let creationDate: NSDate
}
var sections = Dictionary<String, Array<TableItem>>()
var sortedSections = [String]()
#IBAction func saveButton(sender: AnyObject) {
let date:String = "your date in string..."
//if we don't have section for particular date, create new one, otherwise we'll just add item to existing section
if self.sections.indexForKey(date) == nil {
self.sections[date] = [TableItem(title: name, creationDate: date)]
}
else {
self.sections[date]!.append(TableItem(title: name, creationDate: date))
}
//we are storing our sections in dictionary, so we need to sort it
self.sortedSections = self.sections.keys.array.sorted(>)
self.tableView.reloadData()
}
tableView dataSource methods:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[sortedSections[section]]!.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
let tableSection = sections[sortedSections[indexPath.section]]
let tableItem = tableSection![indexPath.row]
cell.titleLabel?.text = tableItem.title
return cell
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sortedSections[section]
}
I would usually do this with Core Data and NSFetchedResultsController since it has built-in methods for getting sections.
However, I'll answer the question without using Core Data. The code is a little messier but here we go...
First, you have to create an object that will store both the date and the text. The testArray will be an array of these objects, instead of a String array. For example:
class DateTextItem: NSObject {
var text: String = ""
var insertDate: NSDate = NSDate()
}
var testArray = [DateTextItem]()
Then when the saveButton is hit, we'll create and add the DateTextItem object. We'll also add the date to the sectionsInTable if it doesn't already exist.
#IBAction func saveButton(sender: AnyObject) {
let newItem = DateTextItem()
newItem.text = "Test \(testArray.count)"
// this is for development only
// increment the date after 2 records so we can test grouping by date
if testArray.count >= (testArray.count/2) {
let incrementDate = NSTimeInterval(86400*(testArray.count/2))
newItem.insertDate = NSDate(timeIntervalSinceNow:incrementDate)
}
testArray.append(newItem)
// this next bit will create a date string and check if it's in the sectionInTable
let df = NSDateFormatter()
df.dateFormat = "MM/dd/yyyy"
let dateString = df.stringFromDate(newItem.insertDate)
// create sections NSSet so we can use 'containsObject'
let sections: NSSet = NSSet(array: sectionsInTable)
// if sectionsInTable doesn't contain the dateString, then add it
if !sections.containsObject(dateString) {
sectionsInTable.append(dateString)
}
self.tableView.reloadData()
}
Next, I created a function to get the items in a section since we need it in a couple places.
func getSectionItems(section: Int) -> [DateTextItem] {
var sectionItems = [DateTextItem]()
// loop through the testArray to get the items for this sections's date
for item in testArray {
let dateTextItem = item as DateTextItem
let df = NSDateFormatter()
df.dateFormat = "MM/dd/yyyy"
let dateString = df.stringFromDate(dateTextItem.insertDate)
// if the item's date equals the section's date then add it
if dateString == sectionsInTable[section] as NSString {
sectionItems.append(dateTextItem)
}
}
return sectionItems
}
Finally, here is what the Table View Data Source methods look like
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionsInTable.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.getSectionItems(section).count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
// get the items in this section
let sectionItems = self.getSectionItems(indexPath.section)
// get the item for the row in this section
let dateTextItem = sectionItems[indexPath.row]
cell.textLabel.text = dateTextItem.text
return cell
}
// print the date as the section header title
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionsInTable[section]
}
You have to make an array for each day (called dayArray[] for example) and add it to sectionInTable[] and do something like that :
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionsInTable.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return sectionsInTable.objectAtIndex(section).count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = String(sectionInTable.objectAtIndex(indexPath.section).objectAtIndex(indexPath.row))
return cell
}
Sorry if I did mistakes, I'm not familiar with swift but I think the idea can help.
I implemented generic algorithm to sort out any objects which can by identified by some day. I guess would be helpful in such cases:
protocol DayCategorizable {
var identifierDate: Date { get }
}
extension Array where Element: DayCategorizable {
var daySorted: [Date: [Element]] {
var result: [Date: [Element]] = [:]
let calendar = Calendar.current
self.forEach { item in
let i = calendar.startOfDay(for: item.identifierDate)
if result.keys.contains(i) {
result[i]?.append(item)
} else {
result[i] = [item]
}
}
return result
}
}