在 Swift 4.2 中将 JSON 显示为 TableView 部分和 TableView 行而不可解码?
Show JSON as TableView Section and TableView row in Swift 4.2 without Decodable?
我有以下 JSON 响应数组,键为 "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"
}
]
}
我想在 TableView 中将上面的 JSON 响应显示为:
"date" key 应该是 TableView Section grouped and Rows as "name" key
有人能帮我吗我该怎么做提前谢谢。
首先,您需要创建一个模型,以便您可以将 JSON 转换为可用对象。为此,好的做法是使用 Codable 协议,它会自动将您的 JSON 键映射到 struct/class 变量
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]!
}
现在您已经有了将从 JSON 生成的模型,您需要将 JSON 解码到您的模型中。有很多方法可以做到,我喜欢用JSONEncoder/JSONDecoder。
因此,假设您的 JSON 字符串存储在变量 myJsonString
中,您需要
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!")
}
我们现在可以将该代码块转换为一个函数,该函数 returns 事件,如果无法解码字符串,则为 nil。
func getEvents(from jsonString: String) -> Events? {
guard let jsonData = myJsonString.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode(Events.self, from: jsonData)
}
酷!我们现在可以轻松地根据 JSON 字符串获取我们的事件!接下来我们要做的就是填充 tableView!
为此,我们可以在我们的 Events
结构中创建一些函数,这些函数将 return 正是我们需要填充我们的部分的内容。第一个函数将为我们的 tableView return 部分,第二个函数将为给定部分的所有项目 return 。让我们修改 Events
结构
struct Events: Codable {
var events: [Event]!
func getSections() -> [String] {
return Array(Set(self.events.map { [=13=].date }))
}
func getItems(forSection dateSection: String) -> [Section] {
return self.events.filter {[=13=].date == dateSection}
}
}
现在,在您的 TableView 数据源 class 中,您需要使用我们创建的模型。我给你举个例子
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
}
}
您可能是从网络上获取这些 JSON 的,因此您必须处理 "loading" 状态,您正在从 returning JSON API。这可以很容易地通过将 events
var 变成一个可选的,当你得到你的 JSON 并从你的 tableView
重新加载数据时设置它
我使用 Alamofire 5.0
完成了上述解决方案,下面是我的完整解决方案,以便它可以帮助某人:
// 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)
}
}
}
}
// 最后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 {[=12=].sectionName == event.date}
let index = tableViewData.firstIndex { [=12=].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
}
}
我有以下 JSON 响应数组,键为 "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"
}
]
}
我想在 TableView 中将上面的 JSON 响应显示为: "date" key 应该是 TableView Section grouped and Rows as "name" key
有人能帮我吗我该怎么做提前谢谢。
首先,您需要创建一个模型,以便您可以将 JSON 转换为可用对象。为此,好的做法是使用 Codable 协议,它会自动将您的 JSON 键映射到 struct/class 变量
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]!
}
现在您已经有了将从 JSON 生成的模型,您需要将 JSON 解码到您的模型中。有很多方法可以做到,我喜欢用JSONEncoder/JSONDecoder。
因此,假设您的 JSON 字符串存储在变量 myJsonString
中,您需要
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!")
}
我们现在可以将该代码块转换为一个函数,该函数 returns 事件,如果无法解码字符串,则为 nil。
func getEvents(from jsonString: String) -> Events? {
guard let jsonData = myJsonString.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode(Events.self, from: jsonData)
}
酷!我们现在可以轻松地根据 JSON 字符串获取我们的事件!接下来我们要做的就是填充 tableView!
为此,我们可以在我们的 Events
结构中创建一些函数,这些函数将 return 正是我们需要填充我们的部分的内容。第一个函数将为我们的 tableView return 部分,第二个函数将为给定部分的所有项目 return 。让我们修改 Events
结构
struct Events: Codable {
var events: [Event]!
func getSections() -> [String] {
return Array(Set(self.events.map { [=13=].date }))
}
func getItems(forSection dateSection: String) -> [Section] {
return self.events.filter {[=13=].date == dateSection}
}
}
现在,在您的 TableView 数据源 class 中,您需要使用我们创建的模型。我给你举个例子
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
}
}
您可能是从网络上获取这些 JSON 的,因此您必须处理 "loading" 状态,您正在从 returning JSON API。这可以很容易地通过将 events
var 变成一个可选的,当你得到你的 JSON 并从你的 tableView
我使用 Alamofire 5.0
完成了上述解决方案,下面是我的完整解决方案,以便它可以帮助某人:
// 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)
}
}
}
}
// 最后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 {[=12=].sectionName == event.date}
let index = tableViewData.firstIndex { [=12=].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
}
}