Swift中如何用SpreadsheetView展示JSON数据并实现行点击跳转
问题描述
我正在使用SpreadsheetView组件,希望将从JSON接口获取的数据展示为网格形式,但不清楚如何将数据对应到各列;同时需要实现点击行内单元格时跳转到详情页展示相关数据。我已通过ConsultaModelo完成JSON数据的下载并存储到feedItems数组中,以下是我已编写的代码:
用于展示数据的视图
import UIKit import SpreadsheetView class TiendasViewController: UIViewController, SpreadsheetViewDataSource, SpreadsheetViewDelegate, ConsultaModeloProtocol{ let headers = ["Sucursal", "Venta total", "Tickets", "Piezas", "Pzs/Ticket", "Ticket prom.", "Utilidad", "Última venta"] var feedItems = [DetallesConsulta]() func itemConsulta(LaConsulta: [DetallesConsulta]) { feedItems = LaConsulta self.tablaTiendas.reloadData() } var selectDato : DetallesConsulta = DetallesConsulta() private let tablaTiendas = SpreadsheetView() override func viewDidLoad() { super.viewDidLoad() let consultaModelo = ConsultaModelo() consultaModelo.ElDelegado = self consultaModelo.downloadConsulta() tablaTiendas.dataSource = self tablaTiendas.delegate = self tablaTiendas.contentInset = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0) tablaTiendas.intercellSpacing = CGSize(width: 4, height: 1) tablaTiendas.gridStyle = .none tablaTiendas.gridStyle = .solid(width: 1, color: .blue) tablaTiendas.register(SucursalesCell.self, forCellWithReuseIdentifier: String(describing: SucursalesCell.self)) tablaTiendas.register(DateCell.self, forCellWithReuseIdentifier: String(describing: DateCell.self)) view.addSubview(tablaTiendas) print("Imprimiendo los feeditems: ", feedItems) // Do any additional setup after loading the view. } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() tablaTiendas.frame = CGRect(x: 0, y:216, width: view.frame.size.width, height: view.frame.size.height-100) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) tablaTiendas.flashScrollIndicators() } func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? { if case (0...(headers.count), 0) = (indexPath.column, indexPath.row) { let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: DateCell.self), for: indexPath) as! DateCell cell.label.text = headers[indexPath.column - 0] return cell } else if case(0, 1...(sucursales.count + 1)) = (indexPath.column, indexPath.row){ let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: SucursalesCell.self), for: indexPath) as! SucursalesCell cell.label.text = sucursales[indexPath.row - 1] return cell } /* let cell = tablaTiendas.dequeueReusableCell(withReuseIdentifier: MyLabelCell.identifier, for: indexPath) as! MyLabelCell if indexPath.row == 0 { cell.setup(with: headers[indexPath.column]) cell.backgroundColor = .systemBlue } return cell */ return nil } func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int { return headers.count } func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int { return 1 + sucursales.count } func spreadsheetView(_ spreadsheetView: SpreadsheetView, widthForColumn column: Int) -> CGFloat { return 200 } func spreadsheetView(_ spreadsheetView: SpreadsheetView, heightForRow row: Int) -> CGFloat { if case 0 = row{ return 24 }else{ return 55 } } func frozenColumns(in spreadsheetView: SpreadsheetView) -> Int { return 1 } } class MyLabelCell: Cell { private let label = UILabel() public func setup(with text: String){ label.text = text label.textAlignment = .center contentView.addSubview(label) } override func layoutSubviews() { super.layoutSubviews() label.frame = contentView.bounds } } class DateCell: Cell { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) label.frame = bounds label.autoresizingMask = [.flexibleWidth, .flexibleHeight] label.font = UIFont.boldSystemFont(ofSize: 15) label.textAlignment = .center contentView.addSubview(label) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } } class SucursalesCell: Cell { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) label.frame = bounds label.autoresizingMask = [.flexibleWidth, .flexibleHeight] label.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: UIFont.Weight.medium) label.textAlignment = .center contentView.addSubview(label) } override var frame: CGRect { didSet { label.frame = bounds.insetBy(dx: 6, dy: 0) } } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } }
下载JSON数据的模型
import UIKit import Foundation protocol ConsultaModeloProtocol: AnyObject { func itemConsulta (LaConsulta: [DetallesConsulta]) } var fechaPresente: String = "" var fechaPasada: String = "" let elToken : String = UserDefaults.standard.string(forKey: "token")! let helper = Helper() class ConsultaModelo: NSObject { weak var ElDelegado : ConsultaModeloProtocol! let URLPath = helper.host+"tiendas" func downloadConsulta(){ var request = URLRequest(url: URL(string: URLPath)!) request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("Bearer \(elToken)", forHTTPHeaderField: "Authorization") request.httpMethod = "POST" let SessionDefault = Foundation.URLSession(configuration: URLSessionConfiguration.ephemeral) URLCache.shared.removeAllCachedResponses() let task = SessionDefault.dataTask(with: request){ (data, response, error)in if error != nil { print("Error al descargar la consulta") }else{ print("Datos descargados") self.parseJSON(data!) } } task.resume() } func parseJSON(_ data: Data){ var jsonResult = NSArray() do{ jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray }catch let error as NSError{ print(error) } var jsonElement = NSDictionary() var detalles = [DetallesConsulta]() for i in 0 ..< jsonResult.count{ jsonElement = jsonResult[i] as! NSDictionary let detalle = DetallesConsulta() let Fecha = jsonElement["Fecha"] let Sucursal = jsonElement["Sucursal"] let Suc = jsonElement["Suc"] let VentaTotal = jsonElement["Venta_Total"] let NoFolios = jsonElement["N_Folios"] let Piezas = jsonElement["Piezas"] let PzaxTicket = jsonElement["PzaxTicket"] let TicketPromedio = jsonElement["TicketPromedio"] detalle.Fecha = Fecha as? String detalle.Sucursal = Sucursal as? String detalle.Suc = Suc as? String detalle.VentaTotal = VentaTotal as? String detalle.NoFolios = NoFolios as? Int detalle.Piezas = Piezas as? String detalle.PzaxTicket = PzaxTicket as? String detalle.TicketPromedio = TicketPromedio as? String detalles.append(detalle) } DispatchQueue.main.async(execute: { ()-> Void in self.ElDelegado.itemConsulta(LaConsulta: detalles) }) } }
数据模型类
import UIKit class DetallesConsulta: NSObject { var Fecha: String? var Sucursal: String? var Suc: String? var VentaTotal: String? var NoFolios: Int? var Piezas: String? var PzaxTicket: String? var TicketPromedio: String? override init(){ } init(Fecha: String, Sucursal: String, Suc: String, VentaTotal: String, NoFolios: Int, Piezas: String, PzaxTicket: String, TicketPromedio: String){ self.Fecha = Fecha self.Sucursal = Sucursal self.Suc = Suc self.VentaTotal = VentaTotal self.NoFolios = Int(NoFolios) self.Piezas = Piezas self.PzaxTicket = PzaxTicket self.TicketPromedio = TicketPromedio } override var description: String{ return "Fecha: \(Fecha), Sucursal: \(Sucursal), Suc: \(Suc), VentaTotal: \(VentaTotal), NoFolios: \(NoFolios), Piezas: \(Piezas), PzaxTicket: \(PzaxTicket), TicketPromedio: \(TicketPromedio)" } }
JSON响应示例
[ { "Fecha": "2022-11-17", "Sucursal": "SCALPERS PUEBLA", "Suc": "004", "Venta_Total": "xxxxxx.xxxxxxxxx", "N_Folios": 12, "Piezas": "xx.000", "PzaxTicket": "x.x", "TicketPromedio": "xxxx.x" }, { "Fecha": "2022-11-17", "Sucursal": "SCALPERS SATELITE", "Suc": "005", "Venta_Total": "xxxxx.xxx", "N_Folios": xx, "Piezas": "xx.000", "PzaxTicket": "x.x", "TicketPromedio": "xxxxx.xxxx" }, { "Fecha": "2022-11-17", "Sucursal": "SCALPERS OUTLET QUERETARO", "Suc": "006", "Venta_Total": "xxx.xxxxxxxxxx", "N_Folios": 4, "Piezas": "6.000", "PzaxTicket": "1.5", "TicketPromedio": "1419.5" }, { "Fecha": "2022-11-17", "Sucursal": "SCALPERS ONLINE", "Suc": "xxxx", "Venta_Total": "xxxxx.xxxxxxx", "N_Folios": 15, "Piezas": "45.000", "PzaxTicket": "3.0", "TicketPromedio": "1930.5" } ]
解决方案
1. 实现数据与表格列的对应展示
首先修正原代码中的几个问题:
- 移除未定义的
sucursales数组,改用feedItems.count计算行数 - 在
cellForItemAt方法中补充数据行的字段映射逻辑
修正行数计算
func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int { // 表头1行 + 数据行数 return 1 + feedItems.count }
完整的cellForItemAt实现
func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? { // 表头行 if indexPath.row == 0 { let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: DateCell.self), for: indexPath) as! DateCell cell.label.text = headers[indexPath.column] return cell } // 数据行 let dataIndex = indexPath.row - 1 guard dataIndex < feedItems.count else { return nil } let item = feedItems[dataIndex] let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: SucursalesCell.self), for: indexPath) as! SucursalesCell // 根据列索引匹配对应字段 switch indexPath.column { case 0: cell.label.text = item.Sucursal case 1: cell.label.text = item.VentaTotal case 2: cell.label.text = item.NoFolios?.description case 3: cell.label.text = item.Piezas case 4: cell.label.text = item.PzaxTicket case 5: cell.label.text = item.TicketPromedio case 6: // 原headers中的"Utilidad"字段,JSON中未返回,可留空或补充逻辑 cell.label.text = "-" case 7: // "Última venta"对应Fecha字段 cell.label.text = item.Fecha default: cell.label.text = "" } return cell }
2. 实现点击单元格跳转详情页
实现SpreadsheetViewDelegate的didSelectItemAt方法,获取对应的数据模型后跳转至详情页:
添加点击事件处理
func spreadsheetView(_ spreadsheetView: SpreadsheetView, didSelectItemAt indexPath: IndexPath) { // 跳过表头行 guard indexPath.row > 0 else { return } let dataIndex = indexPath.row - 1 guard dataIndex < feedItems.count else { return } let selectedItem = feedItems[dataIndex] // 假设详情页为DetalleViewController,需提前创建该页面 let detailVC = DetalleViewController() // 将选中的数据传递给详情页 detailVC.selectedData = selectedItem // 跳转页面(可根据需求用push或present) navigationController?.pushViewController(detailVC, animated: true) }
详情页示例(参考)
class DetalleViewController: UIViewController { var selectedData: DetallesConsulta! override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white // 这里添加UI组件展示selectedData的详细信息 let sucursalLabel = UILabel(frame: CGRect(x: 20, y: 100, width: view.frame.width-40, height: 30)) sucursalLabel.text = "Sucursal: \(selectedData.Sucursal ?? "")" view.addSubview(sucursalLabel) // 其他字段同理添加展示 } }
内容的提问来源于stack exchange,提问作者Rodrigo
相关产品推荐
相关产品推荐

