分组数组或字典以在 Swift 中向 UITableView 显示部分

Group Array or Dictionary to show sections to UITableView in Swift

我有这个实现:

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) {
        [=13=].createdAt?.toRelative()
    }
        
    let invoiceGroups = grouped.map {
        InvoiceGroup(header: [=13=].key!, invoices: [=13=].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
        }
    }
}

当我从 CoreData 获取发票时,我将所有发票按 createdAt 字段降序排列

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
    }

但是 header 在 TableView 中随机显示所有发票,例如:

{
    "Today": [
        {
          "id": 3,
          "ncf": "A1",
          "status": "posted"
        }
    ],
    "Yesterday": [
        {
          "id": 2,
          "ncf": "B1",
          "status": "posted"
        }
    ],
    "3 days ago": [
        {
          "id": 1,
          "ncf": "C3",
          "status": "posted"
        }
    ]
}

如果我重新加载 TableView,顺序会发生变化,例如:

{
    "Today": [
        {
          "id": 3,
          "ncf": "A1",
          "status": "posted"
        }
    ],
    "3 days ago": [
        {
          "id": 1,
          "ncf": "C3",
          "status": "posted"
        }
    ],
    "Yesterday": [
        {
          "id": 2,
          "ncf": "B1",
          "status": "posted"
        }
    ]
}

或者

{
    "3 days ago": [
        {
          "id": 1,
          "ncf": "C3",
          "status": "posted"
        }
    ],
    "Today": [
        {
          "id": 3,
          "ncf": "A1",
          "status": "posted"
        }
    ],
    "Yesterday": [
        {
          "id": 2,
          "ncf": "B1",
          "status": "posted"
        }
    ]
}

我不知道为什么会这样。我需要 headers 按其元素的相对日期字符串降序排列。

您写道:

let grouped = Dictionary(grouping: invoices) {
    [=10=].createdAt?.toRelative()
}
    
let invoiceGroups = grouped.map {
    InvoiceGroup(header: [=10=].key!, invoices: [=10=].value)
}

让我们稍微修改一下以进行调试:

let invoiceGroups = grouped.map {
    print("Adding for key: \([=11=].key)")
    return InvoiceGroup(header: [=11=].key!, invoices: [=11=].value)
}

问题是 Dictionary 未排序,它是 Key-Value 访问权限,而不是 Index-Value 访问权限。所以不能保证第一个被映射的 key-value 将是较旧的日期,或者是最新的日期,后面的将保持该顺序。

相反,让我们帮助您对它们进行排序。 Sicne toRelative() 将创建一个 String,而“昨天”和“3 天前”“很难比较”,先保留日期吧:

让我们以dateTruncated(at: [.year,.month,.day])为例,使同一组中的所有日期均具有不同的时间。或者您可以使用 dateAtStartOf(.day)。我没有深入研究图书馆,但确实本地和时区可能会导致问题,所以请检查一下。

let grouped = Dictionary(grouping: invoices) {
    [=12=].createdAt?.dateTruncated(at: [.year,.month,.day])
}

然后,我们把它整理成元组=

let sortedTuples = grouped.sorted(by: { [=13=].key < .key }

然后,我们可以将元组映射到您的自定义结构中:

let invoiceGroups = sortedTuples.map {
    InvoiceGroupe(header: [=14=].key.toRelative, invoices: [=14=].values)
}