当每个部分的行数不同时,如何获取 numberOfRows 的计数?

Posted

技术标签:

【中文标题】当每个部分的行数不同时,如何获取 numberOfRows 的计数?【英文标题】:How do I get the count for numberOfRows when number of rows in each section varies? 【发布时间】:2018-10-03 01:45:48 【问题描述】:

我正在尝试使用 XML 文件中的条目填充 tableview。到目前为止,我已经将 XML 文件解析为 Struct 并编写了 titleForHeaderInSection 和 numberOfSections 的方法,但我正在努力通过计算每个 <calendarevent> 中的 <holiday> 条目来计算每个部分的 numberOfRows。我认为我的主要困难来自不了解如何使用 [section]。这是我到目前为止所做的:

我已经解析了一个 xml 文件,其中包含这样的日历日期和事件。您将看到第一个 calendarevent 有 2 个假期和描述条目,但第二个有 1 个。:

<calendar>
    <calendarevent>
        <month>October</month>
        <dateevent>2018 10 01</dateevent>
        <datenumber>01</datenumber>

        <holiday>First Holiday</holiday>
        <description>aaaaaaaaaa</description>

        <holiday>Second Holiday</holiday>
        <description>bbbbbbbbbb</description>

    </calendarevent>
    <calendarevent>
        <month>October</month>
        <dateevent>2018 10 10</dateevent>
        <datenumber>10</datenumber>

        <holiday>Third Holiday</holiday>
        <description>ccccccccccc</description>

    </calendarevent>
    .... and so on

到一个看起来像这样的结构:

struct CalendarDates 
    struct CalendarEvents 
        var month = ""
        var eventdate = ""
        var eventdatenumber = ""
        var holiday = ""
        var description = ""
        

这是我的 XML 解析器代码:

class CalendarViewController 
    var myCalendarDatesFromStrut = [CalendarDates]()
    var myCalendarEventsFromStrut = [CalendarDates.CalendarEvents]()


extension CalendarViewController 
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) 
        calendarEventsElementFromXML = elementName
    

    func parser(_ parser: XMLParser, foundCharacters string: String) 
        let data = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        if data.count != 0 
            switch calendarEventsElementFromXML
            
            case "month": monthsFromXML = data
            case "dateevent": eventdatesFromXML = data
            case "datenumber": eventdatenumbersFromXML = data
            case "holiday": holidaysFromXML = data
            default: break
            
        
    

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) 
        if elementName == "calendarevent" 
            var myCalendarDates = CalendarDates.CalendarEvents()
            myCalendarDates.month = monthsFromXML
            myCalendarDates.eventdate = eventdatesFromXML
            myCalendarDates.eventdatenumber = eventdatenumbersFromXML
            myCalendarDates.holiday = holidaysFromXML
            myCalendarEventsFromStrut.append(myCalendarDates)
        
    

所以在解析 XML 并将其附加到结构之后,我开始获取 tableview 的值。

如果&lt;month&gt; 与当前日期月份相同,则每个部分由一个&lt;calendarevent&gt; 组成。 XML 有来自其他月份的&lt;calendarevent&gt;,所以我不想要所有这些。我检查当前月份是否在 XML 中为 == 月份,如果是,则我计算 numberOfSections 的 #of dateevents 并将 dateevents 格式化为这些部分的标题。

那么一节中的每一行代表对应&lt;calendarevent&gt;中的一个&lt;holiday&gt;

extension CalendarViewController: UITableViewDelegate, UITableViewDataSource 
    //Calculates # of <dateevents> in current month
    func numberOfSections(in tableView: UITableView) -> Int 
        formatter.dateFormat = "MMMM"
    let currentMonthShown = formatter.string(from: selectedDate)
    let allEventsInVisibleMonth = myCalendarEventsFromStrut.filter( $0.month == currentMonthShown ).map $0.eventdate.count
    return allEventsInVisibleMonth
    

    // Prints <dateevent> reformatted as section header
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? 
        formatter.dateFormat = "MMMM"
    let currentMonthShown = formatter.string(from: selectedDate)
    let allEventsInVisibleMonth = myCalendarEventsFromStrut.filter( $0.month == currentMonthShown ).map $0.eventdate
    formatter.dateFormat = "yyyy-MM-dd"
    let eventStringsToDate = allEventsInVisibleMonth.map formatter.date(from: $0) 
    formatter.dateFormat = "yyyy-MM-dd"
    formatter.dateStyle = .long
    formatter.timeStyle = .none
    let eventDatesBackToString = eventStringsToDate.map formatter.string(from: $0 as! Date )
    return eventDatesBackToString[section]
    

现在,一节中的每一行都需要在对应的&lt;calendarevent&gt; 中代表一个&lt;holiday&gt; - 这就是我遇到的问题。

对于 tableview 的 numberOfRowsInSection,我需要计算每个 &lt;calendarevent&gt; 中的 # of &lt;holiday&gt; 条目并显示每个部分中的 # 行。然后在 cellForRowAt 中打印。有人可以给我这里的逻辑提示吗?

这是我尝试过的以及具体失败的地方:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
     formatter.dateFormat = "MMMM"
     let currentMonthShown = formatter.string(from: selectedDate)
    let allEventsInMonth = myCalendarEventsFromStrut.filter( $0.month == currentMonthShown ).map $0.holiday

    return allEventsInMonth.count //This gets me same number of rows in all sections = total # of events in current month.

//I'm struggling to figure out how to get # of events at a particular section, so I can make that the # of rows
    

【问题讨论】:

您的section 似乎没有明确定义。您想在每个部分中显示什么?在您当前的numberOfSections(in:) 中,您返回特定月份的日历事件数。这意味着每个部分都包含一个日历事件,这真的是您想要的吗?请说明您希望在每个部分中显示的内容。 在 numberOfSections 中,我检查 是否与当前日期月份相同,然后找到 并显示与当前月份 一样多的部分。然后我将 设置为部分的标题。对于 numberOfRows,我想获得每个 的 # of 并显示该行数。因此,在示例 XML 中,我为那个 有 2 个 条目,所以我想要 2 行。 @OOPer 好的,有些事情已经弄清楚了:每个部分由一个/每个组成> in a section 代表对应中的一个,对吧? @OOPer 是的,就是这样。我使用 来计算 numOfsection 因为我只需要从当前月份获取月份,而不是所有包含其他月份条目的 。是的,一个部分中的每一行代表每个对应的 中的一个 那么您可能需要更改您对CalendarEvents 的定义,因为它只能容纳一个假期,它还需要对您的XMLParserDelegate 方法进行重大更新。您最好将我上面写的两件事包括在您的问题中。有人会比我回答得更快,给出了一些明确的定义。 【参考方案1】:

首先,XML 可能在单个calendarevent 中包含多个holiday 元素,因此您需要更新您的CalendarEvents 以能够容纳多个假期。

struct Holiday 
    var title: String = ""
    var description: String = ""


//You should better avoid plural form for something which represents a single object
struct CalendarEvent 
    var month: String = ""
    var eventdate: String = ""
    var eventdatenumber: String = ""
    //Better use plural form for the name representing multiple objects
    var holidays: [Holiday] = []

(我删除了外部结构CalendarDates,因为我找不到任何需要嵌套类型的原因。另外我建议您避免使用复数形式来命名代表单个对象的东西。)


要使用结构解析 XML,您需要在 XMLParserDelegate 中使用更多属性:

class CalendarViewController: UIViewController 
    //`myCalendarEventsFromStrut` is too long and `FromStrut` does not make sense
    var myCalendarEvents: [CalendarEvent] = []

    //Properties needed for parsing your XML
    var textCurrentlyParsed: String? = nil
    var monthFromXML: String = ""
    var dateeventFromXML: String = ""
    var datenumberFromXML: String = ""
    var holidaysFromXML: [Holiday] = []

    //Your table view shows some selected evnets in `myCalendarEvents`,
    //To improve response, you should beter keep the filtered result, when `selectedDate` is updated.
    var selectedDate: Date? 
        didSet 
            if let date = selectedDate 
                let currentMonthShown = monthFormatter.string(from: date)
                allEventsInVisibleMonth = myCalendarEvents.filter( $0.month == currentMonthShown )
             else 
                allEventsInVisibleMonth = [] //Or you prefer `allEventsInVisibleMonth = myCalendarEvents`?
            
        
    
    var allEventsInVisibleMonth: [CalendarEvent] = []

    //You may have this sort of constants somewhere, this is just an example
    let TheCellID = "cell" //Change this according to your actual setups

    //
    // Date formatters.
    //  Better keep distinct DateFormaters accoding to the format to avoid simple mistakes
    //

    let monthFormatter: DateFormatter = 
       let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "MMMM"
        return formatter
    ()

    let dateeventFormatter: DateFormatter = 
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy MM dd"
        return formatter
    ()

    let sectionHeaderFormatter: DateFormatter = 
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter
    ()

    //...

(上面的代码添加了一些UITableViewDataSource方法的例子。)

使用上面的属性,您可以编写XMLParserDelegate 方法如下:

extension CalendarViewController: XMLParserDelegate 
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) 
        switch elementName 
        case "month", "dateevent", "datenumber", "holiday", "description":
            //Reset the text content for the element
            textCurrentlyParsed = ""
        case "calendarevent":
            //Reset all variables which may contain the result of previous element
            monthFromXML = ""
            dateeventFromXML = ""
            datenumberFromXML = ""
            holidaysFromXML = []
        case "calendar":
            //Can be ignored
            break
        default:
            print("Unexpected start tag:", elementName)
            break
        
    

    func parser(_ parser: XMLParser, foundCharacters string: String) 
        //`parser(_:foundCharacters:)` may be called several times for a seemingly single text content,
        //So you need to add the `string` to the currently parsed text
        textCurrentlyParsed? += string
    

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) 
        switch elementName 
        case "calendarevent":
            var calendarEvent = CalendarEvent()
            calendarEvent.month = monthFromXML
            calendarEvent.eventdate = dateeventFromXML
            calendarEvent.eventdatenumber = datenumberFromXML
            calendarEvent.holidays = holidaysFromXML
            myCalendarEvents.append(calendarEvent)
        case "month":
            if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) 
                monthFromXML = parsedText
            
        case "dateevent":
            if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) 
                dateeventFromXML = parsedText
            
        case "datenumber":
            if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) 
                datenumberFromXML = parsedText
            
        case "holiday":
            if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) 
                var holiday = Holiday()
                holiday.title = parsedText
                holidaysFromXML.append(holiday)
            
            break
        case "description":
            if
                let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines),
                case let lastIndexOfHoliday = holidaysFromXML.count - 1, lastIndexOfHoliday >= 0
            
                //You need to modify the last entry in `holidaysFromXML`
                holidaysFromXML[lastIndexOfHoliday].description = parsedText
            
        default:
            print("Unexpected end tag:", elementName)
            break
        
    

(如果你不需要trim文字,上面的代码可以简化一点。)


包含这些代码后,您的 UITableViewDataSource 方法将如下所示:

extension CalendarViewController: UITableViewDelegate, UITableViewDataSource 
    func numberOfSections(in tableView: UITableView) -> Int 
        return allEventsInVisibleMonth.count
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return allEventsInVisibleMonth[section].holidays.count
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let eventForTheSection = allEventsInVisibleMonth[indexPath.section]
        let holidayForTheRow = eventForTheSection.holidays[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: TheCellID, for: indexPath)
        //... setup the cell using `eventForTheSection` and `holidayForTheRow`
        return cell
    

    // Prints <dateevent> reformatted as section header
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? 
        let eventForTheSection = allEventsInVisibleMonth[section]
        if let eventdateAsDate = dateeventFormatter.date(from: eventForTheSection.eventdate) 
            return sectionHeaderFormatter.string(from: eventdateAsDate)
         else 
            return "Broken eventdate: \(eventForTheSection.eventdate)"
        
    


(未经测试,您可能需要一些修复。)

一般情况下,UITableViewDataSource 方法会被非常频繁地调用,因此您应该高效地实现它们。

因此,不建议在每次调用时创建过滤数组。 您最好只在以下情况下更新allEventsInVisibleMonth

myCalendarEvents 已更新(未包含在我上面的代码中) selectedDate 已更新(didSet in selectedDate 会这样做)

有点长,但我相信值得一试。

【讨论】:

这非常有用,尤其是解释。非常感谢!我自己理解的一个问题:在 XML Parsing 中,我看到一行写着“case let lastIndexOfHoliday = holidayFromXML.count - 1, lastIndexOfHoliday >= 0”。我真的不明白这是做什么的?我看到您如何首先解析 CalendarEvents,然后再解析 Holidays。在 CalendarEvents 中,您似乎不检查是否有最后一个条目,但在 Holidays 中您会检查吗? 第二个问题:我想更好地重用,就像你对 textCurrentlyParsed += 所做的那样,但它看起来很有效。你知道有什么好的教程/指南可以在这方面做得更好吗?我还在写每一个很长的表格,我想变得更有效率 @smarttrams, A1: (first)if-case 是 introduced in Swift 2,但在 Swift 书籍中没有太多描述。我测试模式匹配,如switch-caseif-case-let 在匹配时建立一个新的局部变量。在我上面的代码case let lastIndexOfHoliday 匹配任何模式,它只是建立了一个新变量lastIndexOfHoliday, 只是if 语句中条件子句之间的分隔符。 @smarttrams, A1:(后者)holiday 元素只是创建了一个Holiday 的实例并将其添加到holiday。添加是一个总是成功的操作,所以不需要检查。但是description元素修改了最后创建的Holiday,所以需要检查是否真的存在至少一个Holiday @smarttrams, A2:很难说这样的话,因为我已经写代码 40 多年了,而你不想花 40 年时间学习编程。最近,你可以在网上找到很多资源。找到尽可能多的好代码并从中学习。

以上是关于当每个部分的行数不同时,如何获取 numberOfRows 的计数?的主要内容,如果未能解决你的问题,请参考以下文章

Excel 中一列中有多个数据,每个数据间隔的行数不一样,用啥方法可以快速填充?

java中poi怎么获取指定列的行数?

Navicat:显示的行数与表中实际的行数不一致

Navicat:显示的行数与表中实际的行数不一致

sql server查询结果集复制出来的行数和在SSMS上的行数不一致

phpmyadmin:MySQL 的表行数不正确