0

I am trying to make something like this but I am having trouble adding the date to my tableview. I have a Realm database with dates(Date), names(String) and numbers(Int).

I have successfully added the date to each section, but I am having trouble finding out how to add the names and numbers to the cells. There are multiple rows with the same date, but with different names and numbers.

This is my code so far:

import UIKit
import RealmSwift

class myViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    var data:Results<Objects>!
    var result:[Objects] = []

    let cellSpacingHeight: CGFloat = 5

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self

        retreiveData()
    }

    func retreiveData() {
        let realm = try! Realm()

        // Retreive data
        self.data = realm.objects(Objects.self).sorted(byKeyPath: "date",ascending: false)
        self.result = Array(self.data)

        print(result)

        tableView.reloadData()
    }

    // MARK: - Table View delegate methods

    func numberOfSections(in tableView: UITableView) -> Int {
        return self.result.count
    }

    // Set the spacing between sections
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return cellSpacingHeight
    }

    /*func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }*/

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return result.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)


        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "EEEE - dd.MM.yyyy"

        let stringGivenDate = dateFormatter.string(from: result[indexPath.section].date!)

        cell.textLabel?.text = "\(stringGivenDate)"
        cell.detailTextLabel?.text = "\(result[indexPath.section].name!)"

        return cell
    }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Trinity
  • 15
  • 7
  • Maybe I'm miss reading this, but maybe you should start by grouping the data by date into a dictionary, this would mean that each "key" would become a section and you could determine the number of rows through that "key". Maybe start by figuring out how to accomplish this - [for example](https://stackoverflow.com/questions/44242130/how-to-group-array-of-objects-by-date-in-swift) – MadProgrammer Feb 09 '19 at 22:35

2 Answers2

0

You can use these two predefined functions.

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return "Section \(section)"
}

override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let view = UIView()
    vw.backgroundColor = UIColor.grey
    return view
}
Aditya Malviya
  • 1,907
  • 1
  • 20
  • 25
0

What you need to do is group the data by date

let grouped: [Data: [Objects]]!

//...
self.result = Array(self.data)
grouped = Dictionary(grouping: result) { (element) -> Date in
    return element.date
}

This will produce a dictionary grouping all the elements with the same Date. Now, you might need to do some additional decision making, for example, grouping them only by month and year instead.

Once you have this, you basically have the structure of your table (sections are the dictionary keys and the rows data behind the keys.

For convince, I might also include...

sections = grouped.keys.sorted()

to make it faster to access the keys in a specified order.

Then you just need to apply the appropriate data to your delegate...

  func numberOfSections(in tableView: UITableView) -> Int {
    return sections.count
  }

  // Set the spacing between sections
  func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return cellSpacingHeight
  }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return grouped[sections[section]]!.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

    let rows = grouped[sections[indexPath.section]]!
    let row = rows[indexPath.row]

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "EEEE - dd.MM.yyyy"

    let stringGivenDate = dateFormatter.string(from: row.date!)

    cell.textLabel?.text = "\(stringGivenDate)"
    cell.detailTextLabel?.text = "\(row.name!)"

    return cell
  }

  func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    let date = sections[section]

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "EEEE - dd.MM.yyyy"

    return dateFormatter.string(from: date)
  }

As a side note, DateFormatter is expensive, so you might consider making this a instance property instead

Runnable example....

Example

//
//  ViewController.swift
//  QuickTest
//
//  Created by Shane Whitehead on 10/2/19.
//  Copyright © 2019 Swann Communications. All rights reserved.
//

import UIKit

struct Stuff: CustomDebugStringConvertible {
  let date: Date
  let name: String

  var debugDescription: String {
    return "\(date) = \(name)"
  }
}

extension Date {
  static func random(daysBack: Int)-> Date {
    let day = arc4random_uniform(UInt32(daysBack))+1
    let hour = arc4random_uniform(23)
    let minute = arc4random_uniform(59)

    let today = Date(timeIntervalSinceNow: 0)
    let calendar  = Calendar.current
    var offsetComponents = DateComponents()
    offsetComponents.day = -Int(day)
    offsetComponents.hour = Int(hour)
    offsetComponents.minute = Int(minute)

    let randomDate = calendar.date(byAdding: offsetComponents, to: today)
    return randomDate!
  }

  var startOfDay: Date {
    let calendar  = Calendar.current
    return calendar.startOfDay(for: self)
  }
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  @IBOutlet weak var tableView: UITableView!

  let cellSpacingHeight: CGFloat = 5

  var grouped: [Date: [Stuff]] = [:]
  var sections: [Date] = []

  var headerDateFormatter: DateFormatter = {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "MMM yyyy"
    return dateFormatter
  }()

  var cellDateFormatter: DateFormatter = {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "EEEE - dd.MM.yyyy"
    return dateFormatter
  }()

  override func viewDidLoad() {
    super.viewDidLoad()

    retreiveData()

    tableView.delegate = self
    tableView.dataSource = self

    tableView.rowHeight = UITableView.automaticDimension
    tableView.sectionHeaderHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 44
    tableView.estimatedSectionHeaderHeight = 44

    tableView.reloadData()
  }

  func retreiveData() {
    var data: [Stuff] = []
    for index in 0..<100 {
      let stuff = Stuff(date: Date.random(daysBack: 10), name: "\(index)")
      data.append(stuff)
    }

    grouped = Dictionary(grouping: data) { (element) -> Date in
      return element.date.startOfDay
    }
    sections = grouped.keys.sorted()

  }

  // MARK: - Table View delegate methods

  func numberOfSections(in tableView: UITableView) -> Int {
    return grouped.count
  }

  // Set the spacing between sections
//  func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
//    return cellSpacingHeight
//  }

  /*func numberOfSections(in tableView: UITableView) -> Int {
   return 1
   }*/

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return grouped[sections[section]]!.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

    let rows = grouped[sections[indexPath.section]]!
    let row = rows[indexPath.row]

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "EEEE - dd.MM.yyyy"

    let stringGivenDate = dateFormatter.string(from: row.date)

    cell.textLabel?.text = "\(stringGivenDate)"
    cell.detailTextLabel?.text = "\(row.name)"

    return cell
  }

  func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    let date = sections[section]
    return headerDateFormatter.string(from: date)
  }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • The `let grouped: [Data, [Workout]!` gives me 2 errors: Expected ']' in array type and xpected pattern. Any ideas? – Trinity Feb 09 '19 at 23:12
  • @Trinity `let grouped: [Data, [Workout]]!` – MadProgrammer Feb 10 '19 at 00:44
  • Still gives me the same error tho.. I am supposed to declare this at the same place as `var result:[Objects] = []`, right? – Trinity Feb 10 '19 at 00:48
  • @Trinity `var grouped: [Data: [Workout]]!` sorry, my bad – MadProgrammer Feb 10 '19 at 00:52
  • Check again, I updated the comment (and example it should be `:` not `,`) – MadProgrammer Feb 10 '19 at 00:54
  • Yea, nice! But where did the `sections = grouped.keys.sorted()` come from? I don't have that at all :O – Trinity Feb 10 '19 at 00:56
  • `sections` is another instance property `var sections: [Date]!`, declare along with `grouped` – MadProgrammer Feb 10 '19 at 00:58
  • Ok, added that now also! Getting error on line `grouped = Dictionary(grouping: result) { (element) -> Date in`, error message: `Cannot assign value of type 'Dictionary' to type '[Data : [Workout]]?'` – Trinity Feb 10 '19 at 01:00
  • What version of Swift are you using, seems to work okay for me – MadProgrammer Feb 10 '19 at 01:03
  • I am using Swift 4.2, what about you? EDIT: Tried to build with 4 and 3, but error still there – Trinity Feb 10 '19 at 01:04
  • I made it work without error when removing the `grouped = Dictionary ...` and simply change to `let group = Dictionary ...` When printing it gives me time: `Optional(2019-02-09 20:36:26 +0000)`. What now? I only have it available in viewDidLoad. – Trinity Feb 10 '19 at 01:19
  • @Trinity Okay, I've written a runnable example based on the idea/concepts, see updated example – MadProgrammer Feb 10 '19 at 01:44
  • Very nice! How will this work with my Realm database? I have a object file `Workout.swift` where I get the info from now, it looks like this: https://imgur.com/J9OHSPK . Can I easy implement this in the project you made? – Trinity Feb 10 '19 at 01:53
  • It should work just fine, just remove Stuff and replace it with Workflow and replace the random data generation with you realm loading – MadProgrammer Feb 10 '19 at 01:55
  • Sorry a lot of bother, but in that case it looks like I am back to the `Cannot assign value of type 'Dictionary' to type '[Date : [Stuff]]'` error on line `grouped = Dictionary(grouping: data) { (element) -> Date in`. – Trinity Feb 10 '19 at 02:02
  • Stuff is still shown, you need to replace it with object type – MadProgrammer Feb 10 '19 at 02:04
  • Made it sort of work now, but problem is that it does not display multiple cells when section has same date, look here: https://imgur.com/4x2q9Qj – Trinity Feb 10 '19 at 02:32
  • Okay, did you use the Date extension I have in my example, which sets the time part of the Date to the start of the day? – MadProgrammer Feb 10 '19 at 02:45
  • No, I removed the whole `extension Date ` part. Should I add it again, but only the `var startOfDay ` part? – Trinity Feb 10 '19 at 02:48
  • You just need to be ware that Date contains time values as well, so, if you want to group the data by “day”, you need to find a solution that discards the time value when generating the groups, “zeroing” out the time is just one solution – MadProgrammer Feb 10 '19 at 02:52
  • Ah, finally made it work! Thank you so much man :) You did good! – Trinity Feb 10 '19 at 03:03
  • How can I sort to get newest section on top of `tableView`? Now oldest is on the top. – Trinity Feb 21 '19 at 22:00
  • Provide custom logic for the `sections = grouped.keys.sorted()`. Remember, `Date` is comparable, so you can do `lhs < rhs` (or `==` or `>`). `sections = grouped.keys.sorted({$0 < $1})` (I've not tested this ;)) – MadProgrammer Feb 21 '19 at 22:02
  • Yea, that gives me error `Argument passed to call that takes no arguments `. Have been searching for this awhile, and tried a few different methods, but no luck yet :( – Trinity Feb 21 '19 at 22:11
  • `sections = grouped.keys.sorted { $0 < $1 }` seems to compile – MadProgrammer Feb 21 '19 at 22:17
  • Yea, that one does, but it still don't change the sorting in my `tableView`. Oldest is still on top somehow. EDIT: `sections = grouped.keys.sorted { $1 < $0 }` seems to work just fine with sorting. May this make any errors/problems in the future? – Trinity Feb 21 '19 at 22:20