swiftuitableviewobjectmapperuitableviewsectionheaderxmlmapper

UITableViewSections from Array of Objects


i'm trying to achieve the following. i'm mapping an xml string into a tableview. This part is working fine, however i want to make sections in the tableview as well. I need the LINENAME to be the SectionName and the NODENAMES should be underneath those sections as rows. Below is an example of how it should look like.

etc...

Could somebody give me some more information on how to achieve this?

Thanks in advance.

var LineRows: [LineRow] = []

override func viewDidLoad() {
    super.viewDidLoad()

    let xmlString = """
    <?xml version="1.0" encoding="utf-8"?>
    <RESULT>
        <ROWSET>
            <ROW>
                <LINENAME>LINE1</LINENAME>
                <NODENAME>NODE1</NODENAME>
                <LINEID>1</LINEID>
            </ROW>
            <ROW>
                <LINENAME>LINE1</LINENAME>
                <NODENAME>NODE2</NODENAME>
                <LINEID>1</LINEID>
            </ROW>
            <ROW>
                <LINENAME>LINE2</LINENAME>
                <NODENAME>NODE1</NODENAME>
                <LINEID>2</LINEID>
            </ROW>
        </ROWSET>
    </RESULT>
    """

    let data = Data(xmlString.utf8) // Data for deserialization (from XML to object)
    do {
        let xml = try XMLSerialization.xmlObject(with: data, options: [.default, .cdataAsString])
        let food = XMLMapper<LineResult>().map(XMLObject: xml)

        print(food?.Linerowset?.Linerows?.first?.Linename ?? "nil")

        self.LineRows = food!.Linerowset?.Linerows ?? []
        self.tableView.reloadData()
        
       // debugPrint(LineRows)

    } catch {
        print(error)
    }

}

Map XML String to Array of Objects

// MAP NODE DETAILS //


    class LineResult: XMLMappable {
        var nodeName: String!

        var error: String?
        var Linerowset: LineRowset?

        required init?(map: XMLMap) {}

        func mapping(map: XMLMap) {
            error <- map.attributes["error"]
            Linerowset <- map["ROWSET"]
        }
    }

    class LineRowset: XMLMappable {
        var nodeName: String!

        var Linerows: [LineRow]?

        required init?(map: XMLMap) {}

        func mapping(map: XMLMap) {
            Linerows <- map["ROW"]
        }
    }

    class LineRow: XMLMappable {
        var nodeName: String!

        var Linename: String?
        var Nodename: String?
        var LineLineID: String?

        required init?(map: XMLMap) {}

        func mapping(map: XMLMap) {
            Linename <- map["LINENAME"]
            Nodename <- map["NODENAME"]
            LineLineID <- map["LINEID"]

        }
    }

tableview:

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let Cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let person = LineRows[indexPath.row]

        Cell.textLabel?.text = person.Nodename

        return Cell

    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard
            let indexPath = tableView.indexPathForSelectedRow,
            let vc = segue.destination as? LineDetails
        else {
            return
        }

        let person = LineRows[indexPath.row]
        vc.lineID = person.LineLineID!
    }

debugprint(LineRows) response:

[ApiApp.LineController.LineRow, ApiApp.LineController.LineRow, ApiApp.LineController.LineRow]

dump(LineRows) response:

LINE1
▿ 3 elements
  ▿ ApiApp.LineController.LineRow #0
    ▿ nodeName: Optional("ROW")
      - some: "ROW"
    ▿ Linename: Optional("LINE1")
      - some: "LINE1"
    ▿ Nodename: Optional("NODE1")
      - some: "NODE1"
    ▿ LineLineID: Optional("1")
      - some: "1"
  ▿ ApiApp.LineController.LineRow #1
    ▿ nodeName: Optional("ROW")
      - some: "ROW"
    ▿ Linename: Optional("LINE1")
      - some: "LINE1"
    ▿ Nodename: Optional("NODE2")
      - some: "NODE2"
    ▿ LineLineID: Optional("1")
      - some: "1"
  ▿ ApiApp.LineController.LineRow #2
    ▿ nodeName: Optional("ROW")
      - some: "ROW"
    ▿ Linename: Optional("LINE2")
      - some: "LINE2"
    ▿ Nodename: Optional("NODE1")
      - some: "NODE1"
    ▿ LineLineID: Optional("2")
      - some: "2"

Solution

  • I'd make a ViewModel that will be a better fit for your table view.

    struct LineSections {
        let lineId: String?
        let lineName: String?
        let nodes: [LineRow]
    }
    

    I guess that you have var LineRows: [LineRows] = [] somewhere, as a property of your ViewController. I'd change it with var lineSections: [LineSections] = [].

    Then,

    self.LineRows = food!.Linerowset?.Linerows ?? []
    
    let rows = food?.Linerowset?.Linerows ?? []
    self.lineSections = Dictionary(grouping: rows, by: { $0.lineId }).values.compactMap { LineSections(lineId: $0.first?.lineId, lineName: $0.first?.lineName, nodes: $0) }.sorted(by: { $0.lineId ?? "" < $1.lineId ?? "" }
    

    In your table view populating:

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return lineSections[section].node.count
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return lineSections.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let person = lineSections[indexPath.section].node[indexPath.row]
        cell.textLabel?.text = person.Nodename
        return cell
    }
    

    Not related : Name your var starting with a lowercase: let Cell => let cell etc.