Загрузка 3 разных данных в 3 разных типа ячеек

У меня есть три разных типа ячеек в tableViewController. Я получаю тип ячейки и objectId элемента из другого класса. Затем я перехожу к каждой ячейке в методе cellForRowAt и загружаю все данные. Этот метод привел меня к двум проблемам: 1) динамическая высота одной из ячеек не работает, потому что текст ее метки не найден до тех пор, пока ячейка не будет создана. 2) Все ячейки имеют «дерганый» вид (я вижу, что строки заполняются при прокрутке вниз, я думаю, потому что он загружает содержимое при каждой прокрутке), когда я прокручиваю таблицу вниз.

Поэтому я хочу предварительно загрузить все данные и поместить их в cellForRowAt вместо поиска данных в cellForRowAt. Это решит обе проблемы, но я понятия не имею, как это сделать. Основываясь на своих знаниях в области кодирования, я бы поместил информацию, которая будет находиться в каждой ячейке в массивах, а затем соответствующим образом заполнил ячейки, но я не знаю, как это сделать при использовании 3 разных ячеек, потому что для помещения информации в ячейки из массива я нужно будет использовать indexPath.row; что я не могу сделать, потому что я загружаю 3 разных типа данных и добавляю их в разные массивы, поэтому indexPaths не будут правильно выровнены. Это единственный способ, которым я могу думать об этом, и это неправильно. Как решить эту проблему?

Я скопировал свой код внизу, чтобы вы могли видеть, как я сейчас загружаю ячейки, и, возможно, вы сможете понять, как исправить мою проблему:

func loadNews() {        
        //start finding followers
        let followQuery = PFQuery(className: "Follow")
        followQuery.whereKey("follower", equalTo: PFUser.current()?.objectId! ?? String())
        followQuery.findObjectsInBackground { (objects, error) in
            if error == nil {
                //clean followArray
                self.followArray.removeAll(keepingCapacity: false)

                //find users we are following
                for object in objects!{
                    self.followArray.append(object.object(forKey: "following") as! String)
                }
                self.followArray.append(PFUser.current()?.objectId! ?? String()) //so we can see our own post


                //getting related news post
                let newsQuery = PFQuery(className: "News")
                newsQuery.whereKey("user", containedIn: self.followArray) //find this info from who we're following
                newsQuery.limit = 30
                newsQuery.addDescendingOrder("createdAt") //get most recent
                newsQuery.findObjectsInBackground(block: { (objects, error) in
                    if error == nil {     
                        //clean up
                        self.newsTypeArray.removeAll(keepingCapacity: false)
                        self.objectIdArray.removeAll(keepingCapacity: false)
                        self.newsDateArray.removeAll(keepingCapacity: false)

                        for object in objects! {                            
                            self.newsTypeArray.append(object.value(forKey: "type") as! String) //get what type (animal / human / elements)

                            self.objectIdArray.append(object.value(forKey: "id") as! String) //get the object ID that corresponds to different class with its info
                            self.newsDateArray.append(object.createdAt) //get when posted
                        }
                        self.tableView.reloadData()
                    } else {
                        print(error?.localizedDescription ?? String())
                    }
                })
            } else {
                print(error?.localizedDescription ?? String())
            }
        }   
    }



    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let type = newsTypeArray[indexPath.row]

if type == "element" {
let cell = tableView.dequeueReusableCell(withIdentifier: "ElementCell") as! ElementCell
                        let query = query(className: "Element")
                        query.whereKey("objectId", equalTo: self.objectIdArray[indexPath.row])
                        query.limit = 1
                        query.findObjectsInBackground(block: { (objects, error) in
                            if error == nil {                            
                                for object in objects! {

                                    let name =  (object.object(forKey: "type") as! String)
                                    let caption = (object.object(forKey: "caption") as! String) //small description (usually 2 lines)
                                    cell.captionLabel.text = caption

                                }                               
                            } else {
                                print(error?.localizedDescription ?? String())
                            }
                        })
return cell
} else if type == "human" {
let cell = tableView.dequeueReusableCell(withIdentifier: "HumanCell") as! HumanCell
                        let query = query(className: "Human")
                        query.whereKey("objectId", equalTo: self.objectIdArray[indexPath.row])
                        query.limit = 1
                        query.findObjectsInBackground(block: { (objects, error) in
                            if error == nil {                            
                                for object in objects! {

                                    let name =  (object.object(forKey: "name") as! String)
                                    let caption = (object.object(forKey: "caption") as! String) //small description (1 line)
                                    cell.captionLabel.text = caption

                                }                               
                            } else {
                                print(error?.localizedDescription ?? String())
                            }
                        })
return cell

} else { //its an animal cell

                        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! AnimalCell
                        let query = query(className: "Animals")
                        query.whereKey("objectId", equalTo: self.objectIdArray[indexPath.row])
                        query.limit = 1
                        query.findObjectsInBackground(block: { (objects, error) in
                            if error == nil {                            
                                for object in objects! {

                                    let caption = (object.object(forKey: "caption") as! String) //large description of animal (can be 2 - 8 lines)
                                    cell.captionLabel.text = caption

                                }                               
                            } else {
                                print(error?.localizedDescription ?? String())
                            }
                        })
return cell
    }
}

----- Редактировать ------

Реализация логики @Woof:

В отдельном файле Swift:

class QueryObject {
    var id: String?
    var date: Date?
    var userID : String?
    var name: String?    
}

class Element: QueryObject {
    var objectID : String?
    var owner : String?
    var type : String?
    var ability : String?
    var strength : String?
}

class Human: QueryObject {
    var objectID : String?
    var follower : String?
    var leader : String?
}

class Animal: QueryObject {
    var objectID : String?
    var type: String?
    var owner : String?
    var strength : String?
    var speed : String?
    var durability : String?
}

В TableviewController:

var tableObjects: [QueryObject] = []

func loadNews() {

    //start finding followers
    let followQuery = PFQuery(className: "Follow")
    followQuery.whereKey("follower", equalTo: PFUser.current()?.objectId! ?? String())

    followQuery.findObjectsInBackground { [weak self](objects, error) in
        if error == nil {
            //clean followArray
            self?.followArray.removeAll(keepingCapacity: false)

            //find users we are following
            for object in objects!{
                self?.followArray.append(object.object(forKey: "following") as! String)
            }
            self?.followArray.append(PFUser.current()?.objectId! ?? String()) //so we can see our own post
            //this is a custom additional method to make a query
            self?.queryNews(name: "News", followArray: self?.followArray ?? [], completionHandler: { (results) in
                //if this block is called in a background queue, then we need to return to the main one before making an update
                DispatchQueue.main.async {

                    //check that array is not nil
                    if let objects = results {
                        self?.tableObjects = objects
                        self?.tableView.reloadData()
                    }else{
                        //objects are nil
                        //do nothing or any additional stuff
                    }

                }
            })

        } else {
            print(error?.localizedDescription ?? String())
        }
    }
}

//I've made the code separated, to make it easy to read
private func queryNews(name: String, followArray: [String], completionHandler: @escaping (_ results: [QueryObject]?) -> Void) {

    //making temp array
    var temporaryArray: [QueryObject] = []

    //getting related news post
    let newsQuery = PFQuery(className: "News")
    newsQuery.whereKey("user", containedIn: followArray) //find this info from who we're following
    newsQuery.limit = 30
    newsQuery.addDescendingOrder("createdAt") //get most recent
    newsQuery.findObjectsInBackground(block: { [weak self] (objects, error) in
        if error == nil {

            //now the important thing
            //we need to create a dispatch group to make it possible to load all additional data before updating the table
            //NOTE! if your data are large, maybe you need to show some kind of activity indicator, otherwise user won't understand what is going on with the table

            let dispathGroup = DispatchGroup()
            for object in objects! {

                //detecting the type of the object
                guard let type = object.value(forKey: "type") as? String else{
                    //wrong value or type, so don't check other fields of that object and start to check the next one
                    continue
                }

                let userID = object.value(forKey: "user") as? String
                let id = object.value(forKey: "id") as? String
                let date = object.createdAt

                //so now we can check the type and create objects

                //and we are entering to our group now
                dispathGroup.enter()

                switch type {
                case "element":
                    //now we will make a query for that type
                    self?.queryElementClass(name: "element", id: id!, completionHandler: { (name, objectID, owner, type, ability, strength) in

                        //I've added a check for those parameters, and if they are nil, I won't add that objects to the table
                        //but you can change it as you wish
                        if let objectName = name, let objectsID = objectID {
                            //now we can create an object
                            let newElement = Element()
                            newElement.userID = userID
                            newElement.id = id
                            newElement.date = date

                            newElement.objectID = objectID
                            newElement.owner = owner
                            newElement.type = type
                            newElement.ability = ability 
                            newElement.strength = strength

                            temporaryArray.append(newElement)
                        }

                        //don't forget to leave the dispatchgroup

                        dispathGroup.leave()
                    })

                case "human":
                    //same for Human
                    self?.queryHumanClass(name: "human", id: id!, completionHandler: { (name, objectID, follower, leader) in

                        if let objectName = name, let objectsID = objectID {
                            let newHuman = Human()
                            newHuman.userID = userID
                            newHuman.id = id
                            newHuman.date = date
                            temporaryArray.append(newHuman)
                        }

                        //don't forget to leave the dispatchgroup

                        dispathGroup.leave()
                    })

                case "animal":

                    //same for animal
                    self?.queryAnimalClass(name: "animal", id: id!, completionHandler: { (name, objectID, type, owner, strength, speed, durability) in
                        if let objectName = name, let objectCaption = caption {
                            let newAnimal = Animal()
                            newAnimal.userID = userID
                            newAnimal.id = id
                            newAnimal.date = date
                            temporaryArray.append(newAnimal)
                        }

                        //don't forget to leave the dispatchgroup

                        dispathGroup.leave()
                    })

                default:
                    //unrecognized type
                    //don't forget to leave the dispatchgroup

                    dispathGroup.leave()
                }
            }
            //we need to wait for all tasks entered the group
            //you can also add a timeout here, like: user should wait for 5 seconds maximum, if all queries in group will not finished somehow
            dispathGroup.wait()

            //so we finished all queries, and we can return finished array
            completionHandler(temporaryArray)

        } else {
            print(error?.localizedDescription ?? String())

            //we got an error, so we will return nil
            completionHandler(nil)
        }
    })
}

//the method for making query of an additional class
private func queryElementClass(name: String, id: String, completionHandler: @escaping (_ name: String?, _ objectID: String?, _ owner: String?,  _ type: String?,  _ ability: String?, _ strength: String?) -> Void) {

    let query = PFQuery(className: "Elements")
    query.whereKey("objectId", equalTo: id)
    query.limit = 1
    query.findObjectsInBackground { (objects, error) in
        if error == nil {
            if let object = objects?.first {
                let name =  object.object(forKey: "type") as? String
                let objectID = object.object(forKey: "objectID") as? String
                let owner = object.object(forKey: "owner") as? String
                let type = object.object(forKey: "type") as? String
                let ability = object.object(forKey: "ability") as? String
                let strength = object.object(forKey: "strength") as? String

                completionHandler(name, objectID, owner, type, ability, strength)
            } else {
                print(error?.localizedDescription ?? String())
                completionHandler(nil, nil, nil, nil, nil, nil)
            }
        } else {
            print(error?.localizedDescription ?? String())

        }
    }
}

//the method for making query of an additional class
private func queryHumanClass(name: String, id: String, completionHandler: @escaping (_ name: String?, _ objectID: String?, _ follower: String?,  _ leader: String?) -> Void) {
    let query = PFQuery(className: "Human")
    query.whereKey("objectId", equalTo: id)
    query.limit = 1
    query.findObjectsInBackground(block: { (objects, error) in

        if let object = objects?.first {

            let name =  object.object(forKey: "type") as? String
            let objectID = object.object(forKey: "objectID") as? String
            let follower = object.object(forKey: "follower") as? String
            let leader = object.object(forKey: "leader") as? String

            completionHandler(name, objectID, follower, leader)

        } else {
            print(error?.localizedDescription ?? String())

            completionHandler(nil, nil, nil, nil)
        }
    })
}

//the method for making query of an additional class
private func queryAnimalClass(name: String, id: String, completionHandler: @escaping (_ name: String?, _ objectID: String?, _ owner: String?, _ type: String?,  _ strength: String?,  _ speed: String?, _ durability: String?) -> Void) {

    let query = PFQuery(className: "Animals")
    query.whereKey("objectId", equalTo: id)
    query.limit = 1
    query.findObjectsInBackground(block: { (objects, error) in

        if let object = objects?.first {

            let name =  object.object(forKey: "type") as? String
            let objectID = object.object(forKey: "objectID") as? String
            let owner = object.object(forKey: "owner") as? String
            let strength = object.object(forKey: "strength") as? String
            let type = object.object(forKey: "type") as? String
            let speed = object.object(forKey: "speed") as? String
            let durability = object.object(forKey: "durability") as? String

            completionHandler(name, objectID, owner, type, strength, speed, durability)

        } else {
            print(error?.localizedDescription ?? String())

            completionHandler(nil, nil, nil, nil, nil, nil, nil)
        }
    })
}

person fphelp    schedule 20.08.2017    source источник


Ответы (2)


Глядя на ваши проекты, я вижу несколько массивов с разными данными. Очень сложно редактировать код с такой структурой.

Я бы сделал это так:

1) создавать объекты для хранения значений, например структуры/классы Animal, Human, Element. Если они имеют одинаковые значения, такие как идентификаторы или что-то еще, вы можете создать объект суперкласса и сделать другие объекты в качестве подклассов.

2) создайте один массив в качестве источника данных для вашей таблицы с объектами, а не значениями

//if there is no super class
var objects:[AnyObject] = []

Or

//for the superclass
var objects:[YourSuperClass] = []

В приведенном ниже коде я буду использовать Superclass, но вы можете изменить его на AnyObject.

3) сделать метод для заполнения этого массива объектов перед обновлением таблицы:

//I think it is better to use clousures and make data fetching in different queue

func loadNews(completionHandler: @escaping (_ objects: [YourSuperClass]) -> Void){
      yourBackgroundQueue.async{
               var objects = // fill here the array with objects
               // it is important to return data in the main thread to make an update
              DispatchQueue.main.async{
                     completion(objects)
              }
      }
}

И чтобы заполнить наш массив источника данных, вызовите этот метод, когда вам нужно:

func updateTable(){
        loadNews(){ [weak self] objects in
       self?.objects = objects
       self?.tablewView.reloadData()
}

Итак, теперь у вас есть массив объектов

4) Мы можем использовать приведение к определенному классу для установки ячеек:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

         let object = objects[indexPath.row]
        //making downcast 
        if let animal = object as? Animal, 
let cell = tableView.dequeueReusableCell(withIdentifier: "AnimalCell") as? AnimalCell

       //now you can fill the cell by properties than Animal object has
       //return cell
       return cell
 }

if let human = object as? Human, 
let cell = tableView.dequeueReusableCell(withIdentifier: "HumanCell") as? HumanCell

       //do stuff with HumanCell
       //return cell
       return cell
 }


//same way you can detect and fill any other cells

//this will be return an empty cell if there will be an object in the array that wasn't recognized. In this case the app won't crash, but you will see that something is wrong 

return UITableViewCell()
}

Итак, основные мысли:

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

  • создать массив объектов с параметрами, вместо создания нескольких массивов с простыми значениями

  • используйте массив объектов для определения типа ячейки в таблице.

============EDIT================= ПРИМЕЧАНИЕ! Я сделал этот код на игровой площадке без импорта PFQuery. Если будут ошибки, дайте мне знать. Если вы застряли, дайте мне знать, возможно, я проверю ваш проект напрямую

Итак, новый код

//declaring Objects in separated file
class QueryObject {
    var id: String?
    var date: Date? //change of your date for object.createdAt has different type
    var caption: String?
    var name: String?
//    var type: String? //use this var you don't need to have subclasses
}

//If your subclasses will not have unique parameters, you can left only one class QueryObject, without subclasses
//In this case just uncomment the "type" variable in the QueryObject, then you can check that var in cellForRowAt
class Animal: QueryObject {
    //add any additional properties
}

class Human: QueryObject {
    //add any additional properties
}

class Element: QueryObject {
    //add any additional properties
}

class YourController: UITableViewController {
    //allocate var inside ViewController
    var tableObjects: [QueryObject] = []

    func loadNews() {

        //start finding followers
        let followQuery = PFQuery(className: "Follow")
        followQuery.whereKey("follower", equalTo: PFUser.current()?.objectId! ?? String())

        followQuery.findObjectsInBackground { [weak self](objects, error) in
            if error == nil {
                //clean followArray
                self?.followArray.removeAll(keepingCapacity: false)

                //find users we are following
                for object in objects!{
                    self?.followArray.append(object.object(forKey: "following") as! String)
                }
                self?.followArray.append(PFUser.current()?.objectId! ?? String()) //so we can see our own post
                //this is a custom additional method to make a query 
                self?.queryNews(name: "News", followArray: self?.followArray ?? [], completionHandler: { (results) in
                    //if this block is called in a background queue, then we need to return to the main one before making an update
                    DispatchQueue.main.async {

                        //check that array is not nil
                        if let objects = results {
                            self?.tableObjects = objects
                            self?.tableView.reloadData()
                        }else{
                            //objects are nil
                            //do nothing or any additional stuff
                        }

                    }
                })

            } else {
                print(error?.localizedDescription ?? String())
            }
        }
    }

    //I've made the code separated, to make it easy to read
    private func queryNews(name: String, followArray: [String], completionHandler: @escaping (_ results: [QueryObject]?) -> Void) {

        //making temp array
        var temporaryArray: [QueryObject] = []

        //getting related news post
        let newsQuery = PFQuery(className: "News")
        newsQuery.whereKey("user", containedIn: followArray) //find this info from who we're following
        newsQuery.limit = 30
        newsQuery.addDescendingOrder("createdAt") //get most recent
        newsQuery.findObjectsInBackground(block: { [weak self] (objects, error) in
            if error == nil {

                //now the important thing
                //we need to create a dispatch group to make it possible to load all additional data before updating the table
                //NOTE! if your data are large, maybe you need to show some kind of activity indicator, otherwise user won't understand what is going on with the table

                let dispathGroup = DispatchGroup()

                for object in objects! {

                    //detecting the type of the object
                    guard let type = object.value(forKey: "type") as? String else{
                        //wrong value or type, so don't check other fields of that object and start to check the next one
                        continue
                    }

                    let id = object.value(forKey: "id") as? String
                    let date = object.createdAt

                    //so now we can check the type and create objects

                    //and we are entering to our group now
                    dispathGroup.enter()

                    switch type {
                    case "animal":

                        //now we will make a query for that type
                        self?.queryAdditionalClass(name: "Animals", id: id, completionHandler: { (name, caption) in

                            //I've added a check for those parameters, and if they are nil, I won't add that objects to the table
                            //but you can change it as you wish
                            if let objectName = name, let objectCaption = caption {
                                //now we can create an object

                                let newAnimal = Animal()
                                newAnimal.id = id
                                newAnimal.date = date

                                temporaryArray.append(newAnimal)
                            }

                            //don't forget to leave the dispatchgroup

                            dispathGroup.leave()

                        })
                    case "human":

                        //same for Human
                        self?.queryAdditionalClass(name: "Human", id: id, completionHandler: { (name, caption) in

                            if let objectName = name, let objectCaption = caption {
                                let newHuman = Human()
                                newHuman.id = id
                                newHuman.date = date
                                temporaryArray.append(newHuman)
                            }

                            //don't forget to leave the dispatchgroup

                            dispathGroup.leave()

                        })
                    case "elements":

                        //same for Element
                        self?.queryAdditionalClass(name: "Element", id: id, completionHandler: { (name, caption) in

                            if let objectName = name, let objectCaption = caption {
                                let newElement = Element()
                                newElement.id = id
                                newElement.date = date
                                temporaryArray.append(newElement)
                            }

                            //don't forget to leave the dispatchgroup

                            dispathGroup.leave()

                        })
                    default:
                        //unrecognized type
                        //don't forget to leave the dispatchgroup

                        dispathGroup.leave()
                    }

                }

                //we need to wait for all tasks entered the group
                //you can also add a timeout here, like: user should wait for 5 seconds maximum, if all queries in group will not finished somehow
                dispathGroup.wait()

                //so we finished all queries, and we can return finished array
                completionHandler(temporaryArray)

            } else {
                print(error?.localizedDescription ?? String())

                //we got an error, so we will return nil
                completionHandler(nil)
            }
        })
    }

    //the method for making query of an additional class
    private func queryAdditionalClass(name: String, id: String, completionHandler: @escaping (_ name: String?, _ caption: String?) -> Void) {

        let query = PFQuery(className: name)
        query.whereKey("objectId", equalTo: id)
        query.limit = 1
        query.findObjectsInBackground(block: { (objects, error) in

            if let object = objects?.first {

                let name =  object.object(forKey: "type") as? String
                let caption = object.object(forKey: "caption") as? String

                completionHandler(name, caption)

            }else{
                print(error?.localizedDescription ?? String())

                completionHandler(nil, nil)
            }
    }

    //now we can detect what object we have and show correct cell depending on object's type
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let object = tableObjects[indexPath.row]

        //making downcast or if you won't use subclasses, then check type variable using switch case as I made in loadNews()
        if let animal = object as? Animal,
            let cell = tableView.dequeueReusableCell(withIdentifier: "AnimalCell") as? AnimalCell {

            cell.captionLabel.text = animal.caption

            //do additional stuff for the animal cell

            //return cell
            return cell
        }

        if let human = object as? Human,
            let cell = tableView.dequeueReusableCell(withIdentifier: "HumanCell") as? HumanCell {

            cell.captionLabel.text = human.caption

            //do stuff with HumanCell

            //return cell
            return cell
        }

        if let element = object as? Element,
            let cell = tableView.dequeueReusableCell(withIdentifier: "ElementCell") as? ElementCell {

            cell.captionLabel.text = element.caption

            //do stuff with ElementCell

            //return cell
            return cell
        }

        return UITableViewCell()
    }
}
person Woof    schedule 21.08.2017
comment
Я не уверен, что полностью понимаю ваше решение, но я хочу понять и попробовать его. В имеющемся у меня коде я использую функцию loadNews(), чтобы получить тип (просто строку, в которой указано, что элемент, человек или животное) и objectId (строка с идентификатором объекта в другом классе), который соответствует классу в Parse, содержащему больше информации. Затем в моем cellForRow я создаю ячейку на основе типа, а затем запрашиваю данные на основе полученного идентификатора объекта. - person fphelp; 22.08.2017
comment
Можете ли вы показать мне пример того, как я буду использовать это? Может быть, просто возьмите мой тип элемента, например, и покажите мне, как включить это - person fphelp; 22.08.2017
comment
Привет, извините, что беспокою вас снова, но это чрезвычайно важно для моего проекта, и я последние 5 часов пытался понять это. Мне действительно нужно это выяснить, поэтому любая дополнительная помощь, которую вы можете мне оказать, будет очень признательна. - person fphelp; 23.08.2017
comment
большой! Большое спасибо, я очень ценю это - person fphelp; 23.08.2017
comment
Я внес изменения, проверьте это - person Woof; 23.08.2017
comment
И не забудьте установить tableObjects в качестве источника данных для вашего tableView. Все остальные массивы в моем коде не использовались - person Woof; 23.08.2017
comment
Большой! Я попробую это и дам вам знать, как это происходит! Благодарю вас! - person fphelp; 23.08.2017
comment
Спасибо за вашу постоянную помощь, я очень ценю это. Я потратил последние более 2 часов на ваше редактирование, пытаясь правильно реализовать его в моем коде, но табличное представление по какой-то причине не загружает информацию. Я не уверен, что я делаю неправильно. К концу queryNews temporaryArray оказывается пустым. Таким образом, не будет строк для добавления. Каковы следующие шаги? Еще раз спасибо за вашу помощь - person fphelp; 23.08.2017
comment
Кроме того, я разработал ячейки с учетом того, что они будут показывать, поэтому я изменил ваш код queryAdditionalClass и отдельные коды для каждого типа ячеек, чтобы улавливать больше данных, относящихся к этим типам. Я отредактирую свой исходный вопрос и скопирую свой новый код, чтобы показать вам, что я имею в виду. - person fphelp; 23.08.2017
comment
Вы пробовали проверять обработчики? Были ли вызваны методы запроса? Что они возвращают? - person Woof; 23.08.2017
comment
Почему-то методы запроса в моих версиях queryAdditionalClass не вызывались. Хотя я не знаю, что это значит. - person fphelp; 23.08.2017
comment
Давайте продолжим обсуждение в чате. - person Woof; 23.08.2017
comment
Я только что отредактировал вопрос с новым кодом и ответил на ваш вопрос в обсуждении. - person fphelp; 23.08.2017
comment
Наконец-то это сработало! У меня просто есть еще один вопрос о загрузке большего количества данных во время прокрутки пользователя. Комментируйте в чате мои новые разработки, когда сможете! - person fphelp; 15.09.2017

одно простое решение, назначьте источник данных табличного представления при загрузке новостей. Вы можете сделать это в конце метода loadNews

tableView.dataSource = self

Убедитесь, что источник данных не был назначен где-то еще, например, в раскадровке.

person Abdul Waheed    schedule 21.08.2017