Swift 3 Parse JSON в UITableView с использованием URLSession

Я пытаюсь разобрать JSON с помощью URLSession и без использования Alamofire или чего-либо еще.

Я просто хочу взять JSON и поместить его в UITableView.

Я пытаюсь собрать воедино то, что я узнал, изучая, как анализировать JSON с помощью Alamofire, с тем, что я могу найти в Google. Многие ответы на YouTube или Stack и т. Д. Используют NS для всего. NSURL, NSDictionary и т. Д. И т. Д. Или просто набирают код, не объясняя, что и почему.

Я ДУМАЮ, что почти достиг цели, но мне нужна помощь, чтобы понять, что мне осталось сделать.

SO.

Разрешил произвольные загрузки в plst

В файле Swift у меня есть следующее

class Potter {

private var _title: String!
private var _author: String!
private var _imageURL: String!

let POTTER_URL = "http://de-coding-test.s3.amazonaws.com/books.json"

var title: String {
  if _title == nil {
    _title = ""
  }
  return _title
}

var author: String {
  if _author == nil {
    _author = ""
  }
  return _author
}

var imageURL: String {
  if _imageURL == nil {
    _imageURL = ""
  }
  return _imageURL
}

  func downloadJSON() {


    let url = URL(string: POTTER_URL)
    let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in

      if error != nil {
        print("Error")

      } else {

        if let content = data {
          do {
            if let jDict = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject> {

              if let title = jDict["title"] as? String {
                self._title = title.capitalized

              }

              if let author = jDict["author"] as? String {
                self._author = author.capitalized
              }

              if let imgURL = jDict["imageURL"] as? String {
                self._imageURL = imgURL
              }
            }
          }
          catch {  
          }
        }
      }
    }
    task.resume()
  }
}

В моем Main.Storyboard я добавил табличное представление и настроил весь пользовательский интерфейс, а в своем ViewController я настроил делегатов табличного представления.

Я создал свойство

var potters = [Potter]()

Теперь я застрял в том, как заполнить этот массив и как настроить правильную поточность.


person RubberDucky4444    schedule 17.04.2017    source источник


Ответы (3)


Во-первых, ваша модель inane довольно странная.

В Swift никогда не используйте резервные закрытые переменные для получения свойств только для чтения. И никогда не объявляйте свойства как неявно развернутые необязательные, потому что вам лень писать инициализатор.

Всю модель можно свести к

class Potter {

    let title, author, imageURL: String

    init(title: String, author: String, imageURL : String) {
        self.title = title
        self.author = author
        self.imageURL = imageURL
    }
}

Если бы вы использовали struct, это даже

struct Potter {
    let title, author, imageURL: String
}

потому что вы получаете членский инициализатор бесплатно.


Во-вторых, вынесите метод downloadJSON() из модели, поместите его в контроллер и вызовите в viewDidLoad().

В контроллере объявите URL-адрес загрузки и массив источника данных.

let POTTER_URL = "http://de-coding-test.s3.amazonaws.com/books.json"

var books = [Potter]()

Ваш метод downloadJSON() не может работать, поскольку объект JSON представляет собой массив ([]), а не словарь ({}). Вам нужен цикл для перебора элементов, получения значений, создания элемента Potter соответственно и добавления его в источник данных. Если значение не существует, присваивается пустая строка. Наконец, перезагрузите представление таблицы в основном потоке.

func downloadJSON() {

    let url = URL(string: POTTER_URL)
    let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in

        if error != nil {
            print("DataTask error", error!)

        } else {
            do {
                if let bookData = try JSONSerialization.jsonObject(with: data!) as? [[String:String]] {
                    books.removeAll() // clear data source array
                    for book in bookData {
                        let title = book["title"] ?? ""
                        let author = book["author"] ?? ""
                        let imgURL = book["imageURL"] ?? ""
                        books.append(Potter(title: title, author: author, imageURL: imgURL))
                    }
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                }
            }
            catch {
                print("Serialization error", error)
            }
        }

    }
    task.resume()
}

Два примечания:

  • Стандартный словарь JSON в Swift 3 — [String:Any], в данном конкретном случае — даже [String:String].
  • .mutableContainers бесполезен, если контейнеры только читаются и в любом случае бесполезны в Swift, потому что объект не может быть приведен к NSMutableArray / -Dictionary, и вы получаете изменчивость бесплатно, используя variable.
person vadian    schedule 17.04.2017
comment
@vadian ... хороший ответ, но полегче с беднягой. Он просто просит научиться :) - person Unis Barakat; 17.04.2017
comment
Я имел в виду, что нет необходимости в грубых выражениях, например: безумный, ленивый и т. Д. Но это не так уж важно. :) - person Unis Barakat; 17.04.2017
comment
@UnisBarakat Я смягчил безумие ;-) - person vadian; 17.04.2017

  1. Веб-службы возвращают массив объектов: [Dictionary<String, AnyObject>].

  2. Будет проще, если вы создадите метод init со словарем в качестве параметра.

  3. downloadJSON — это асинхронная задача, использование completionHandler — лучший способ. И если вы хотите поместить downloadJSON в класс Potter, это должна быть функция static.

  4. Наконец, вы должны обработать результат следующим образом:

    Potter.downloadJSON { potters in
    
        self.potters = potters
    
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
    

Окончательный код:

class ViewController: UIViewController {

    var potters = [Potter]()

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        Potter.downloadJSON { potters in

            self.potters = potters

            DispatchQueue.main.async {

                self.tableView.reloadData()
            }
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return potters.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!

        let potter = potters[indexPath.row]
        cell.textLabel?.text = potter.title
        cell.detailTextLabel?.text = potter.author

        return cell
    }
}

class Potter {

    private var _title: String!
    private var _author: String!
    private var _imageURL: String!

    static let POTTER_URL = "http://de-coding-test.s3.amazonaws.com/books.json"

    var title: String {
        if _title == nil {
            _title = ""
        }
        return _title
    }

    var author: String {
        if _author == nil {
            _author = ""
        }
        return _author
    }

    var imageURL: String {
        if _imageURL == nil {
            _imageURL = ""
        }
        return _imageURL
    }

    init(dict: Dictionary<String, AnyObject>) {
        self._title = dict["title"] as? String
        self._imageURL = dict["imageURL"] as? String
        self._author = dict["author"] as? String
    }

    class func downloadJSON(completion: @escaping (_ potters: [Potter]) -> Void) {

        let url = URL(string: POTTER_URL)
        let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in

            if error != nil {
                print("Error")

            } else {

                if let content = data {

                    do {
                        if let jArray = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? [Dictionary<String, AnyObject>] {

                            var potters = [Potter]()
                            for jDict in jArray {
                                let potter = Potter(dict: jDict)
                                potters.append(potter)
                            }
                            completion(potters)
                        }
                    }
                    catch {
                    }
                }
            }
        }
        task.resume()
    }
}

введите здесь описание изображения

person Danh Huynh    schedule 17.04.2017

Метод downloadJSON() должен быть реализован в ViewController, поскольку он возвращает массив данных Potter. Затем в ответе URLSession вы должны создать один массив, который будет действовать как источник данных tableview. (т.е. self.arrTableData = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? [[String : AnyObject]])

Затем для tableView

func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int {

        return self.arrTableData.count
}

и в ячейке для строки по пути индекса

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   //create `potters` object with the value and use it else you can direcly use the value of objects as below.
     let dictPotters = self.arrTableData[indexPath.row]
      let title = dictPotters["title"]
  }

Спасибо

person Nishant Bhindi    schedule 17.04.2017