Show JSON as TableView Section and TableView row in Swift 4.2 without Decodable?

天涯浪子 提交于 2020-06-23 14:16:06

问题


I have below JSON response array with key "events"

{
  "events": [
 {
  "name": "event foo",
  "date": "2020-05-22",
  "time": "7:00",
  "am_or_pm": "PM",
  "day": "Saturday",
  "description": "test "
},
{
  "name": "event bar",
  "date": "2020-05-22",
  "time": "7:00",
  "am_or_pm": "PM",
  "day": "Saturday",
  "description": "test2"
},
{
  "name": "event foobar",
  "date": "2020-05-24",
  "time": "11:00",
  "am_or_pm": "PM",
  "day": "Saturday",
  "description": "test3"
}, 
{
  "name": "event foobar",
  "date": "2020-05-24",
  "time": "11:00",
  "am_or_pm": "PM",
  "day": "Saturday",
  "description": "test3"
 }
]
}

I want to show above JSON response in TableView as : "date" key should be TableView Section grouped and Rows as "name" key

Could some help me how will i do that thanks in advance.


回答1:


First things first, you'll need to create a Model, so that you can transform your JSON into usable objects. To do that, good practice is to use the Codable protocol, which will automatically map your JSON keys to a struct/class variable

struct Event: Codable {
    var name: String!
    var date: String!
    var time: String!
    var am_or_pm: String!
    var day: String!
    var description: String!
}

struct Events: Codable {
    var events: [Event]!
}

Now that you have the model that will be generated from the JSON, you need to decode your JSON into your model. There are plenty of ways to do it, I like to use JSONEncoder/JSONDecoder. So, let's say your JSON string is stored in the variable myJsonString, you would need to

if let jsonData = myJsonString.data(using: .utf8) {
    do {
        let events = try JSONDecoder().decode(Events.self, from: jsonData)
    } catch {
        print("Error decoding json!", error.localizedDescription)
    }
} else {
    print("Failed to get bytes from string!")
}

We can, now turn that code block into a function that returns Events, or nil, if it fails to decode the string.

func getEvents(from jsonString: String) -> Events? {
    guard let jsonData = myJsonString.data(using: .utf8) else { return nil }
    return try? JSONDecoder().decode(Events.self, from: jsonData)
}

Cool! We can easily get our events based on a JSON string now! All we gotta do next is populate the tableView! To do that, we can create a few functions in our Events struct that will return just what we need to populate our section. The first function will return the sections for our tableView, and the second will return all items for a given section. Let's modify the Events struct

struct Events: Codable {
    var events: [Event]!

    func getSections() -> [String] {
        return Array(Set(self.events.map { $0.date }))
    }

    func getItems(forSection dateSection: String) -> [Section] {
        return self.events.filter {$0.date == dateSection}
    }
}

Now, in your TableView datasource class, you need to use the model we created. I`ll do an example for you

class YourTableView: UIViewController, UITableViewDataSource, UITableViewDelegate {
    // This is loaded from the getEvents function!
    var events: Events!

   func numberOfSections (in tableView: UITableView) -> Int {
        return self.events.getSections().count
    }

   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let dateSection = self.events.getSections()[section]
        return self.events.getItems(forSection: dateSection ).count
    }


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let dateSection = self.events.getSections()[indexPath.section]
        let currentEvent = self.getItems(forSection: dateSection)

         // Build your cell using currentEvent
    }
}

You are probably getting those JSONs from the web, so you have to handle a "loading" state, where you are returning the JSON from the API. That can easily be done by turning the events var into a optional, setting it when you get your JSON and reloading data from your tableView




回答2:


I did above solutions using Alamofire 5.0 and below is my complete solutions so that it can help someone:

// Create a Modal Class of name "Event"

class Event {
    var name: String?
    var date: String?
    var time: String?
    var amOrPm: String?
    var day: String?
    var description: String?



init(dic: [String: Any]) {
    if let name = dic["name"] as? String {
        self.name = name
    }
    if let date = dic["date"] as? String {
        self.date = date
    }
    if let time = dic["time"] as? String {
        self.time = time
    }
    if let amOrPm = dic["am_or_pm"] as? String {
        self.amOrPm = amOrPm
    }
    if let day = dic["day"] as? String {
        self.day = day
    }
    if let description = dic["description"] as? String {
        self.description = description
    }
    }
}

// Creating a Global Class for URL

import Alamofire

class HttpManager {
    struct Constants {
        static let baseUrl = "https://my-json-server.typicode.com/JCkshone/jsonTest"
    }

    func getEvents(handledResponse: @escaping (_ data: [[String: Any]])->()) {
        let request = AF.request("\(Constants.baseUrl)/db")

        request.responseJSON { (response: AFDataResponse<Any>) in
            guard let data = response.value as? [String: Any] else { return }
            if let events: [[String: Any]] = data["events"] as? [[String: Any]] {
                handledResponse(events)
            }
        }
    }
}

// Finally ViewController.swift

struct SectionEvent {
    var sectionName: String
    var evenst: [Event]
}

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    let http  = HttpManager()
    var events: [Event] = []
    var tableViewData: [SectionEvent] = []
    let cell = "cellId"

    override func viewDidLoad() {
        super.viewDidLoad()
        fetchData()
        initTableView()
    }

    func fetchData() {
        http.getEvents { (data: [[String : Any]]) in
            data.forEach { item in
                self.events.append(Event(dic: item))
            }
            self.buildData()
        }
    }

    func buildData() {
        events.forEach { event in

        let validation = validateEventExist(event: event)
        if !validation.exist {
            tableViewData.append(SectionEvent(sectionName: event.date ?? "", evenst: [event]))
        } else {
            tableViewData[validation.position].evenst.append(event)
        }
    }
    self.tableView.reloadData()
}

func validateEventExist(event: Event) -> (exist: Bool, position: Int) {
    let filterData = tableViewData.filter {$0.sectionName == event.date}
    let index = tableViewData.firstIndex { $0.sectionName == event.date}
    return (filterData.count > 0, index ?? 0)
}

    func initTableView() {
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cell)
        tableView.tableHeaderView = UIView()
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {


func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    tableViewData[section].sectionName
}

func numberOfSections(in tableView: UITableView) -> Int {
    tableViewData.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    tableViewData[section].evenst.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
    if let name = tableViewData[indexPath.section].evenst[indexPath.row].name, let description = tableViewData[indexPath.section].evenst[indexPath.row].description {
        cell.textLabel?.text = "\(name) \n\(description)"
        cell.textLabel?.numberOfLines = 0
    }

    return cell
   }
}


来源:https://stackoverflow.com/questions/62084188/show-json-as-tableview-section-and-tableview-row-in-swift-4-2-without-decodable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!