I have this implementation:
Invoice - Hold info about Invoices
import Foundation
class Invoice {
var content: String?
var createdAt: Date?
var dateA: Date?
var dateB: Date?
var employee: String?
var expenseCategory: String?
var from: Date?
var id: String?
var ncf: String?
var ncfA: String?
var ncfB: String?
var numeration: Int64
var paymentMethod: String?
var rejectReason: String?
var sessionId: String?
var status: String?
var subtotal: Double
var sync: Bool
var taxExempt: Bool
var taxType: String?
var taxValue: Double
var to: Date?
var total: Double
var totalInvoice: Int64
var typeA: String?
var typeB: String?
var updatedAt: Date?
var user: String?
var toCompany: Company?
var toDocument: [Document]?
var storeId: String?
var incomeType: String?
var expenseType: String?
var toOtherCompany: Company?
var invoiceDate: Date?
var otherTax: Double
var companyId: String?
init(_content: String?, _storeId: String?, _createdAt: Date?, _dateA: Date?, _dateB: Date?, _employee: String?, _expenseCategory: String?,
_from: Date?, _id: String?, _ncf: String?, _ncfA: String?, _ncfB: String?, _numeration: Int64, _paymentMethod: String?,
_rejectReason: String?, _sessionId: String?, _status: String?, _subtotal: Double, _sync: Bool, _taxExempt: Bool, _taxType: String?,
_taxValue: Double, _to: Date?, _total: Double, _totalInvoice: Int64, _typeA: String?, _typeB: String?, _updatedAt: Date?,
_user: String?, _incomeType: String?, _expenseType: String?, _invoiceDate: Date?, _otherTax: Double, _companyId: String?, _toCompany: Company? = nil, _toDocument: [Document]? = nil,
_toOtherCompany: Company? = nil) {
self.content = _content
self.createdAt = _createdAt
self.dateA = _dateA
self.dateB = _dateB
self.employee = _employee
self.expenseCategory = _expenseCategory
self.from = _from
self.id = _id
self.ncf = _ncf
self.ncfA = _ncfA
self.ncfB = _ncfB
self.numeration = _numeration
self.paymentMethod = _paymentMethod
self.rejectReason = _rejectReason
self.sessionId = _sessionId
self.status = _status
self.subtotal = _subtotal
self.sync = _sync
self.taxExempt = _taxExempt
self.taxType = _taxType
self.taxValue = _taxValue
self.to = _to
self.total = _total
self.totalInvoice = _totalInvoice
self.typeA = _typeA
self.typeB = _typeB
self.updatedAt = _updatedAt
self.user = _user
self.toCompany = _toCompany
self.toDocument = _toDocument
self.storeId = _storeId
self.incomeType = _incomeType
self.expenseType = _expenseType
self.toOtherCompany = _toOtherCompany
self.invoiceDate = _invoiceDate
self.otherTax = _otherTax
self.companyId = _companyId
}
// Get the descriptive status
func getDescriptiveStatus() -> String {
switch self.status {
case InvoiceStatus.waiting.toString():
return InvoiceStatus.waiting.description.uppercased()
case InvoiceStatus.processing.toString():
return InvoiceStatus.processing.description.uppercased()
case InvoiceStatus.processed.toString():
return InvoiceStatus.processed.description.uppercased()
case InvoiceStatus.pending_rejection.toString():
return InvoiceStatus.pending_rejection.description.uppercased()
case InvoiceStatus.rejected.toString():
return InvoiceStatus.rejected.description.uppercased()
case InvoiceStatus.posted.toString():
return InvoiceStatus.posted.description.uppercased()
case InvoiceStatus.reviewing.toString():
return InvoiceStatus.reviewing.description.uppercased()
case InvoiceStatus.pending_upload.toString():
return InvoiceStatus.pending_upload.description.uppercased()
default:
return "UNKNOW STATUS"
}
}
// Get status (based in enum)
func getStatus() -> InvoiceStatus? {
switch self.status {
case InvoiceStatus.waiting.toString():
return InvoiceStatus.waiting
case InvoiceStatus.processing.toString():
return InvoiceStatus.processing
case InvoiceStatus.processed.toString():
return InvoiceStatus.processed
case InvoiceStatus.pending_rejection.toString():
return InvoiceStatus.pending_rejection
case InvoiceStatus.rejected.toString():
return InvoiceStatus.rejected
case InvoiceStatus.posted.toString():
return InvoiceStatus.posted
case InvoiceStatus.reviewing.toString():
return InvoiceStatus.reviewing
case InvoiceStatus.pending_upload.toString():
return InvoiceStatus.pending_upload
default:
return nil
}
}
}
InvoiceGroup - Hold info about invoices grouped by header
import Foundation
struct InvoiceGroup {
var header: String
var invoices: [Invoice] = []
}
This function group all invoices by their relative date string using https://github.com/malcommac/SwiftDate framework
func groupInvoicesByDate(Invoices invoices: [Invoice]) -> [InvoiceGroup] {
let grouped = Dictionary(grouping: invoices) {
$0.createdAt?.toRelative()
}
let invoiceGroups = grouped.map {
InvoiceGroup(header: $0.key!, invoices: $0.value)
}
return invoiceGroups
}
To display headers and cells I do this:
extension HistoryViewController : UITableViewDataSource, UITableViewDelegate
{
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.invoicesGroups[section].header
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.invoicesGroups.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.invoicesGroups[section].invoices.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentInvoice = self.invoicesGroups[indexPath.section].invoices[indexPath.row]
switch currentInvoice.getStatus() {
case .posted:
let cell = (tableView.dequeueReusableCell(withIdentifier: "HistoryItem", for: indexPath) as? HistoryViewCell)!
cell.data(item: currentInvoice, page: self, hidden: !self.isTab0(), selected: self.invoiceSelected)
cell.setCallbackListener(callback: self)
return cell
case .waiting:
let cell = (tableView.dequeueReusableCell(withIdentifier: "HistoryItem", for: indexPath) as? HistoryViewCell)!
cell.data(item: currentInvoice, page: self, hidden: !self.isTab0(), selected: self.invoiceSelected)
cell.setCallbackListener(callback: self)
return cell
case .rejected, .pending_rejection:
let cell = (tableView.dequeueReusableCell(withIdentifier: "HistoryItemReturn", for: indexPath) as? HistoryItemReturn)!
cell.data(item: currentInvoice, page: self)
return cell
case .pending_upload:
let cell = (tableView.dequeueReusableCell(withIdentifier: "HistoryItem", for: indexPath) as? HistoryViewCell)!
cell.data(item: currentInvoice, page: self)
return cell
default:
let cell = (tableView.dequeueReusableCell(withIdentifier: "HistoryItem", for: indexPath) as? HistoryViewCell)!
cell.data(item: currentInvoice, page: self)
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let currentInvoice = self.invoicesGroups[indexPath.section].invoices[indexPath.row]
switch currentInvoice.getStatus() {
case .posted:
// HistoryItem cell
return 95
case .waiting:
// HistoryItem cell
return 75
case .rejected:
// HistoryItemReturn cell
return 110
case .pending_upload:
return 75
default:
return 100
}
}
}
When I get the invoices from CoreData, I take all of them ordered descending by the createdAt field
func histories(sessionId: String, types: [InvoiceType], status : [InvoiceStatus], companyId: String, sortDate: String = "createdAt") -> [Invoice] {
var invoiceTypeList: [String] = []
var invoiceStatusList: [String] = []
for invTp in types {
invoiceTypeList.append(invTp.toString())
}
for invSt in status {
invoiceStatusList.append(invSt.toString())
}
var predicates: [NSPredicate] = []
let predicate1 = NSPredicate(format: "status IN %@", invoiceStatusList)
predicates.append(predicate1)
if !types.isEmpty {
let predicate2 = NSPredicate(format: "typeA IN %@", invoiceTypeList)
predicates.append(predicate2)
}
let predicate3 = NSPredicate(format: "sessionId = %@", sessionId as CVarArg)
predicates.append(predicate3)
let predicate4 = NSPredicate(format: "companyId = %@", companyId as CVarArg)
predicates.append(predicate4)
let compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
let sortDescriptor = NSSortDescriptor(key: sortDate, ascending: false)
let arrays = _invoiceDataRepository.getAll(predicate: compoundPredicate, sort: [sortDescriptor])
return arrays
}
But the header is displayed random in TableView with all their Invoices, for example:
{
"Today": [
{
"id": 3,
"ncf": "A1",
"status": "posted"
}
],
"Yesterday": [
{
"id": 2,
"ncf": "B1",
"status": "posted"
}
],
"3 days ago": [
{
"id": 1,
"ncf": "C3",
"status": "posted"
}
]
}
If I reload the TableView, order change, for example:
{
"Today": [
{
"id": 3,
"ncf": "A1",
"status": "posted"
}
],
"3 days ago": [
{
"id": 1,
"ncf": "C3",
"status": "posted"
}
],
"Yesterday": [
{
"id": 2,
"ncf": "B1",
"status": "posted"
}
]
}
Or
{
"3 days ago": [
{
"id": 1,
"ncf": "C3",
"status": "posted"
}
],
"Today": [
{
"id": 3,
"ncf": "A1",
"status": "posted"
}
],
"Yesterday": [
{
"id": 2,
"ncf": "B1",
"status": "posted"
}
]
}
I don't know why this is happening. I need the headers ordered descending by the relative date string off their elements.
CodePudding user response:
You wrote:
let grouped = Dictionary(grouping: invoices) {
$0.createdAt?.toRelative()
}
let invoiceGroups = grouped.map {
InvoiceGroup(header: $0.key!, invoices: $0.value)
}
Let's change it a little to debug:
let invoiceGroups = grouped.map {
print("Adding for key: \($0.key)")
return InvoiceGroup(header: $0.key!, invoices: $0.value)
}
The issue is that a Dictionary
isn't ordered, it's a Key-Value access, not Index-Value access. So there is no guarantee that the first key-value to be mapped will be the older date, or the latest one, and the following ones will keep that order.
Instead, let's help you sort them. Sicne toRelative()
will create a String
, and "Yesterday" and "3 days ago" are "hard to compare", just let keep the dates first:
Let's use dateTruncated(at: [.year,.month,.day])
for instance to make all the dates even with different hours in the same group. Or you could use dateAtStartOf(.day)
. I didn't digged into the library, but indeed local & timezone could causes issues, so to check on your end.
let grouped = Dictionary(grouping: invoices) {
$0.createdAt?.dateTruncated(at: [.year,.month,.day])
}
Then, let's sort it into tuples=
let sortedTuples = grouped.sorted(by: { $0.key < $1.key }
And then, we can just map the tuples into you custom struct:
let invoiceGroups = sortedTuples.map {
InvoiceGroupe(header: $0.key.toRelative, invoices: $0.values)
}