ios - 包含来自本地 JSON 文件的部分的 UITableView

标签 ios swift xcode uitableview

我仍然卡住了,几周来我一直在用头撞墙。所以我在这里再问一次,这次是以更完整的方式。我只想完成这项工作。我一直在尝试尽可能多地使用 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/

相关文章:

iOS 应用程序位置权限目前无法正常工作

ios - 如何在 iOS 中将一系列图像转换为电影?

ios - AES 加密和解密

swift - 向上或向下滚动时增加或减少 imageView 的高度

ios - ARKit——Xcode 9 Beta 6 中的错误

iOS Swift - 调用另一个类的函数并查看空变量

iphone - 链接器找不到 CTFontCreateWithNameAndOptions

ios - UiTabController 应用程序设置

ios - Swift中JSON解析返回数据

xcode - IOS/Cordova 无效签名 - 密封资源丢失或无效。路径中的文件