0

An overview of what I am trying to achieve I am trying to make a notifications tableview and each notification is group by its created date, so the tableview sections will be the number of created date, each section with the notifications created at this date in the section title. I have searched a lot but didn't get an absolute answer how to make with RxDataSource the array is dynamic get loaded with dates received through an API?

class T : UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return array.count
    }
}

All what I have found is to set the sections static like so

       ViewModel.AllNotificationsObservable
                .map({ [NotificationSectionViewModel(header: "Yet", items: $0.filter{$0.createAt.toDate()!.toString(format: "yyyy-MM-dd") == Date().toString(format: "yyyy-MM-dd") }),
                        NotificationSectionViewModel(header: "Yesterday", items: $0)
                ]
                })
                .bind(to: NotificationTableView.rx.items(dataSource: ViewModel.dataSource))
                .disposed(by: notificationDisposeBag)

this is my struct

struct NotificationSectionViewModel {
    var header: String
    var items: [AllNotificationModel] 
}
extension NotificationSectionViewModel: SectionModelType {
    typealias NotificationItem = AllNotificationModel
    
    init(original: NotificationSectionViewModel, items: [AllNotificationModel]) {
        self = original
        self.items = items
    }
}

and this the data model

class AllNotificationModel : Codable {
    
    let id, userID : Int
    let title, body, createAt: String
    
    enum CodingKeys: String, CodingKey {
        case id, title, body
        case userID = "user_id"
        case createAt = "create at"
    }
}

what I am trying to achieve

enter image description here

need header to be like this

“Today”: [
        {
            "id": 2421,
            "user_id": 39,
            "title": "todayNotification",
            "body": "test",
            "create at": "2021-02-26 17:33:44"
        },
        {
            "id": 2349,
            "user_id": 39,
            "title": "check",
            "body": "test",
            "create at": "2021-02-26 09:36:05"
        },
        {
            "id": 2206,
            "user_id": 39,
            "title": "New Deal",
            "body": "new Deal 2",
            "create at": "2021-02-26 13:43:16"
        } ]
“Yesterday”: [
        {
            "id": 2134,
            "user_id": 39,
            "title": "Closed Deal",
            "body": “deal deal”,
            "create at": "2021-02-25 13:21:30"
        } ]

“2021-02-24”: [
        {
            "id": 2134,
            "user_id": 39,
            "title": "Closed Deal",
            "body": “deal”,
            "create at": "2021-02-24 13:21:30"
        },
        {
            "id": 2063,
            "user_id": 39,
            "title": "New Deal",
            "body": "new Deal",
            "create at": "2021-02-24 13:21:16"
        }]
Adam
  • 13
  • 7

2 Answers2

0

In the example of RxDataSources we have:

Observable.just(sections)
  .bind(to: tableView.rx.items(dataSource: dataSource))
  .disposed(by: disposeBag)

All you need to do is to replace Observable.just(sections) with an Observable that is bound to your data. Let's assume that notifications is an Observable<[Notifications]>. Then you do something like this:

notifications.map { sections(from: $0) }
  .bind(to: tableView.rx.items(dataSource: dataSource))
  .disposed(by: disposeBag)

sections(from: $0) is a transformation of [Notification] array to sections array, define it somewhere. Your section struct must conform to a protocol SectionModelType.

struct SectionOfNotification {
  var header: String    
  var items: [Item]
}

extension SectionOfNotification : SectionModelType {
  typealias Item = Notification

   init(original: SectionOfNotification, items: [Item]) {
    self = original
    self.items = items
  }
}

My example:

public lazy var appSections: Driver<[AppSection]> = {
    Driver.combineLatest(chatAppCollectionData, functionAppCollectionData) { ($0, $1) }
        .map { (chatAppCollectionData, functionAppCollectionData) -> [AppSection] in
            let appSection1 = AppSection(header: NSLocalizedString("DASHBOARD_RECENT_CHATS", comment: ""),
                                         items: chatAppCollectionData)

            let appSection2 = AppSection(header: NSLocalizedString("DASHBOARD_OTHERS", comment: ""),
                                         items: functionAppCollectionData)

            return [
                appSection1,
                appSection2
            ]
        }
}()

This is section:

import RxDataSources

struct AppSection {
    var header: String
    var items: [Item]
}

extension AppSection: SectionModelType {
    typealias Item = EONApp

    init(original: AppSection, items: [Item]) {
        self = original
        self.items = items
    }
}
Denis Kutlubaev
  • 15,320
  • 6
  • 84
  • 70
  • I don't get your answer how to get sections from the [AllNotificationModel] look this is my struct. ` struct NotificationSectionViewModel { var header: String var items: [AllNotificationModel] } extension NotificationSectionViewModel: SectionModelType { typealias NotificationItem = AllNotificationModel init(original: NotificationSectionViewModel, items: [AllNotificationModel]) { self = original self.items = items } } ` @Denis – Adam Feb 25 '21 at 17:42
  • @Adam, added an example from a real project. You just need to initialize SectionModelType objects from your model objects. – Denis Kutlubaev Feb 26 '21 at 15:59
  • You have just put two sections with their data, What I am asking is a dynamic section depends on the date of the notifications let's say all notification in 26-02-2021 and then all notification in 20-02-2021 and so on it's not just today and yesterday it's a dynamic if I have today notifications I will put them all under section called today if not I will create another section with the other received dates. – Adam Feb 27 '21 at 06:47
  • I have added what I mean after the Image check it please @Denis – Adam Feb 27 '21 at 06:56
0

I figured out the answer

override func bind(ViewModel: NotificationViewModel) {

        
        ViewModel.dataSource.configureCell = { [unowned self] (dataSource, tableview, indexPath, item)  in
            let cell = tableview.dequeueReusableCell(withIdentifier: self.CellIdentifier, for: indexPath) as! NotificationTableViewCell
            cell.setDataToUI(notificationData: item)
            return cell
        }

        ViewModel.dataSource.titleForHeaderInSection = { (dataSource, index) in
            let section = dataSource[index]
            return section.header
        }
    
        var finalSections = [NotificationSectionViewModel]()
        var sortedFinal = [NotificationSectionViewModel]()
        var result = [String : [AllNotificationModel]]()
            ViewModel.AllNotificationsObservable
                .map({ section in
      for (i, dict) in section.enumerated() {
                        result[(section[i].createAt.toDate()?.toString(format: "yyyy-MM-dd"))!, default: []].append(dict)
                    }
                    
                    for (key, value) in result {
                        finalSections.append(NotificationSectionViewModel(header: key, items: value))
                    }
                    
                    sortedFinal = finalSections.sorted(by: >)
                    
                    for final in 0...sortedFinal.count - 1 {
                        if self.getTodayDate() == sortedFinal[final].header {
                            sortedFinal[final].header = "Today"
                        }
                        else if self.getYesterDay() == sortedFinal[final].header {
                            sortedFinal[final].header = "Yesterday"
                        }
                        else {
                                sortedFinal[final].header = convertDateFormater(sortedFinal[final].header)
                            
                        }
                    }
                    
                    return sortedFinal
                })
                .bind(to: NotificationTableView.rx.items(dataSource: ViewModel.dataSource))
                .disposed(by: notificationDisposeBag)
}

This is my cell class

class NotificationTableViewCell: UITableViewCell {

    @IBOutlet weak var notificationImageIcon: UIImageView!
    @IBOutlet weak var notificationBodyMessage: UILabel!
    @IBOutlet weak var notificationTime: UILabel!
    @IBOutlet weak var seenNotificationView: UIView!
    

    override func awakeFromNib() {
          super.awakeFromNib()
          // Initialization code
          selectionStyle = .none
      }

    func setDataToUI(notificationData: AllNotificationModel) {
        DispatchQueue.main.async {
            self.seenNotificationView.isHidden = true
            self.notificationBodyMessage.text = notificationData.body
            self.notificationTime.text = self.convertDateFormater(notificationData.createAt)
        }
    }
    
    func convertDateFormater(_ date: String) -> String
        {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            let date = dateFormatter.date(from: date)
            dateFormatter.dateFormat = "h:mm a"
            return  dateFormatter.string(from: date!)

        }

}

I used these two function to get the today and the yesterday dates

extension UIViewController {
    func getTodayDate() -> String {
        let currentDate = Date()
        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd"
        let dateString = df.string(from: currentDate)
        return dateString
    }

    func getYesterDay() -> String {
        let currentDate = Date.yesterday
        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd"
        let dateString = df.string(from: currentDate)
        return dateString
    }
}
Adam
  • 13
  • 7