UITableView с разделами из локального файла JSON - PullRequest
0 голосов
/ 02 июля 2018

Я все еще застрял, и уже несколько недель бьюсь головой об стену. Итак, здесь я прошу еще раз, на этот раз в более полной форме. Я просто хочу сделать эту работу. Я пытался максимально использовать Swift 4 (так как я учусь, кажется, легче придерживаться одного набора правил / синтаксиса, но на данный момент мне все равно, какой язык используется, пока он работает так что я могу перейти к остальным вещам, которые мне нужно сделать с приложением.

Цель: Посмотрите на локальную версию JSON и сравните ее с размещенной версией. Если размещено более новое, замените локальную версию более новой. Затем проанализируйте локальный файл 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. Мне кажется, что я понимаю, что делают мои функции, но я не могу понять, почему это не работает, и почему, когда это произошло работать (используя старый метод), что разделы, где совершенно не в порядке. Извините, если вы видите мои задаваемые вопросы, я просто пытаюсь узнать, как это сделать, чтобы я мог быть самодостаточным, но этот один кусок просто не имеет смысла для меня.

Ответы [ 2 ]

0 голосов
/ 02 июля 2018

Разделите вашу цель на отдельные задачи и напишите функцию для каждого.

Вы должны уметь:

  • Скачивайте бонусы с сервера
  • Сохраните свои бонусы в локальном файле
  • Загрузка бонусов из локального файла

Ваша текущая downloadJSON функция близка к той, которую вы хотите для первой, но я немного ее изменил, чтобы она не работала с другими частями вашего контроллера напрямую, а вместо того, чтобы просто отправлять бонусы обратно в обработчик завершения :

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))
        }
    }
}

Теперь вы можете использовать эту новую функцию loadBonuses при загрузке контроллера вида:

override func viewDidLoad() {
    super.viewDidLoad()

    loadBonuses { [weak self] bonuses in
        self?.bonuses = bonuses ?? []
        self?.tableView.reloadData()
    }
}
0 голосов
/ 02 июля 2018

Прежде чем углубляться в то, как работает iOS UITableView, сделайте это прямо:

  • У вас есть табличное представление для указания нескольких элементов данных.
  • Эти элементы упорядочены в строки табличного представления.

Сейчас:

  • если их можно каким-то образом классифицировать, они группируются по разделам (видимым по заголовкам разделов).
  • Если их невозможно классифицировать, все они отображаются в одном (часто невидимом) разделе.

Итак, сначала подумайте, как вы отображаете бонусы. Это плоский список (массив) или они сгруппированы в несколько больших кусков?

Если нет классификации:

  • У вас есть один раздел, и ваш numberOfSections метод должен возвращать 1.
  • Ваш numberOfRowsInSection должен вернуть bonuses.count.
  • И самое главное, ваш cellForRowAt должен выглядеть следующим образом (обратите внимание, что массив бонусов индексируется по индексу строки, а не по индексу раздела):

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        cell.textLabel?.text = bonuses[indexPath.row].name.capitalized
        return cell
     }
    

Если он имеет классификацию, то вы должны думать о bonuses как о массиве массивов.

  • Ваш numberOfSections вернет bonuses.count - количество массивов.
  • Ваш numberOfRowsInSection будет извлекать элемент массива (скажем, x [] - обратите внимание, что сам x является массивом) из массива бонусов и возвращает x.count
  • Ваш cellForRowAt будет снова получать элемент массива из бонусов (скажем, x[]). Затем он извлечет элемент строки из с в x, например: x[indexPath.row], и ваш окончательный код будет выглядеть так (я пропустил распаковку и т. Д., Как все равно скажет компилятор):

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        let x = bonuses[indexPath.section]
        let bonusItem = x[indexPath.row]
        cell.textLabel?.text = bonusItem.name.capitalized
        return cell
     }
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...