我仍然卡住了,几周来我一直在用头撞墙。所以我在这里再问一次,这次是以更完整的方式。我只想完成这项工作。我一直在尝试尽可能多地使用 Swift 4(因为我正在学习坚持一套规则/语法似乎更容易,但在这一点上我不关心使用什么语言,只要它有效这样我就可以继续处理我需要对应用程序执行的其他操作。
目标:查看本地版本的 JSON,并将其与托管版本进行比较。如果 hosted 较新,请将本地版本替换为较新的版本。然后解析本地的JSON文件创建UITableView,并按状态分片。
问题:它有点像从网站上实时解析它的旧方法,但显示重复项和错误计数的部分。它现在似乎将本地与正确托管进行了比较,但现在根本没有填充 UITableView。我怀疑我所有的问题都在 tableView 部分,但我已经尝试了 10 万亿种不同的方法,但没有一种有效。我假设我没有正确地将它指向本地 JSON 文件。
代码: 这是我的整个 ViewController:
import UIKit
import os.log
import Foundation
class BonusListViewController: UITableViewController {
var bonuses = [JsonFile.JsonBonuses]()
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
// MARK: Data Structures
// Settings Struct
struct Constants {
struct RiderData {
let riderNumToH = "riderNumToH"
let pillionNumToH = "pillionNumToH"
}
struct RallyData {
let emailDestinationToH = "emailDestinationToH"
}
}
//MARK: Check for updated JSON file
checkJSON()
//MARK: Trigger JSON Download
/*
downloadJSON {
print("downloadJSON Method Called")
}
*/
}
// MARK: - Table View Configuration
// MARK: Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
print("Found \(bonuses.count) sections.")
return bonuses.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Found \(bonuses.count) rows in section.")
return bonuses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = bonuses[indexPath.section].name.capitalized
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetail", sender: self)
}
// MARK: - Table View Header
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return bonuses[section].state
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 3
}
// MARK: Functions
// MARK: - Download JSON from ToH webserver
func downloadJSON(completed: @escaping () -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")
URLSession.shared.dataTask(with: url!) { [weak self] (data, response, error) in
if error == nil {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data!)
DispatchQueue.main.async {
completed()
}
print("Downloading Updated JSON (Version \(posts.meta.version))")
print(posts.bonuses.map {$0.bonusCode})
print(posts.bonuses.map {$0.state})
self?.bonuses = posts.bonuses
self?.defaults.set("downloadJSON", forKey: "jsonVersion") //Set version of JSON for comparison later
DispatchQueue.main.async {
//reload table in the main queue
self?.tableView.reloadData()
}
} catch {
print("JSON Download Failed")
}
}
}.resume()
}
func checkJSON() {
//MARK: Check for updated JSON file
let defaults = UserDefaults.standard
let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
let jsonURL = URL(string: hostedJSONFile)
var hostedJSONVersion = ""
let jsonData = try! Data(contentsOf: jsonURL!)
let jsonFile = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String : Any]
let metaData = jsonFile["meta"] as! [String : Any]
hostedJSONVersion = metaData["version"] as! String
let localJSONVersion = defaults.string(forKey: "jsonVersion")
if localJSONVersion != hostedJSONVersion {
print("L:\(localJSONVersion!) / H:\(hostedJSONVersion)")
print("Version Mismatch: Retrieving lastest JSON from server.")
updateJSONFile()
} else {
//Retrieve the existing JSON from documents directory
print("L:\(localJSONVersion!) / H:\(hostedJSONVersion)")
print("Version Match: Using local file.")
let fileURL = defaults.url(forKey: "pathForJSON")
do {
let localJSONFileData = try Data(contentsOf: fileURL!, options: [])
let myJson = try JSONSerialization.jsonObject(with: localJSONFileData, options: .mutableContainers) as! [String : Any]
//Use my downloaded JSON file to do stuff
print(myJson)
DispatchQueue.main.async {
//reload table in the main queue
self.tableView.reloadData()
}
} catch {
print(error)
}
}
}
func updateJSONFile() {
print("updateJSONFile Method Called")
let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
let jsonURL = URL(string: hostedJSONFile)
let itemName = "BonusData.json"
let defaults = UserDefaults.standard
do {
let directory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let fileURL = directory.appendingPathComponent(itemName)
let jsonData = try Data(contentsOf: jsonURL!)
let jsonFile = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as? [String : Any]
let metaData = jsonFile!["meta"] as! [String : Any]
let jsonVersion = metaData["version"]
print("JSON VERSION ", jsonVersion!)
try jsonData.write(to: fileURL, options: .atomic)
defaults.set(fileURL, forKey: "pathForJSON") //Save the location of your JSON file to UserDefaults
defaults.set(jsonVersion, forKey: "jsonVersion") //Save the version of your JSON file to UserDefaults
DispatchQueue.main.async {
//reload table in the main queue
self.tableView.reloadData()
}
} catch {
print(error)
}
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? BonusDetailViewController {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
}
这里是 JsonFile.swift,它提供了 JSON 解析的结构:
import Foundation
struct JsonFile: Codable {
struct Meta: Codable {
let fileName: String
let version: String
}
struct JsonBonuses: Codable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
let imageName: String
}
let meta: Meta
let bonuses: [JsonBonuses]
}
我需要有人来解释它,就像我才 5 岁一样。我觉得我明白我的功能在做什么,但我无法终生弄清楚为什么它不起作用,也不知道为什么它什么时候起作用工作(使用旧方法),这些部分完全不正常。如果您看到我提出的问题,我很抱歉,我只是想学习如何做到这一点,这样我就可以自给自足,但是这篇文章对我来说没有意义。
最佳答案
将您的目标分解为单独的任务,并为每个任务编写一个函数。
您需要能够:
- 从服务器下载您的奖金
- 将奖金保存到本地文件
- 从本地文件加载奖金
您当前的 downloadJSON
函数与您想要的第一个函数很接近,但我对其进行了轻微修改,因此它不会直接处理 Controller 的其他部分,而不仅仅是发送奖金返回完成处理程序:
func downloadJSON(completed: @escaping ([JsonFile.JsonBonuses]?) -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil, let data = data {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data)
completed(posts.bonuses)
} catch {
print("JSON Download Failed")
}
} else {
completed(nil)
}
}.resume()
}
将 json 保存到文件很简单,因为您的对象实现了 Codable
:
func saveBonuses(_ bonuses: [JsonFile.JsonBonuses], to url: URL) {
try? FileManager.default.removeItem(at: url)
do {
let data = try JSONEncoder().encode(bonuses)
try data.write(to: url)
} catch {
print("Error saving bonuses to file:", error)
}
}
类似于从文件加载:
func loadBonusesFromFile(_ url: URL) -> [JsonFile.JsonBonuses]? {
do {
let data = try Data(contentsOf: url)
let bonuses = try JSONDecoder().decode([JsonFile.JsonBonuses].self, from: data)
return bonuses
} catch {
print("Error loading bonuses from file:", error)
return nil
}
}
这些部分都是独立的,所以现在您需要另一个具有将它们联系在一起的逻辑的函数。我们想尝试从服务器获取 json 并将其保存到文件中,或者如果失败则加载之前保存到文件中的任何 json 并使用它:
func loadBonuses(completion: @escaping ([JsonFile.JsonBonuses]?) -> Void) {
let localBonusesURL = try! FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Bonuses.json")
downloadJSON { bonuses in
if let bonuses = bonuses {
completion(bonuses)
saveBonuses(bonuses, to: localBonusesURL)
} else {
completion(loadBonusesFromFile(localBonusesURL))
}
}
}
现在您可以在加载 View Controller 时使用这个新的 loadBonuses
函数:
override func viewDidLoad() {
super.viewDidLoad()
loadBonuses { [weak self] bonuses in
self?.bonuses = bonuses ?? []
self?.tableView.reloadData()
}
}
关于ios - 包含来自本地 JSON 文件的部分的 UITableView,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51140690/