将3种不同的信息加载到3种不同类型的细胞中

时间:2017-08-20 18:58:08

标签: ios swift uitableview pfquery

tableViewController中有三种不同类型的单元格。我得到了哪种类型的单元格以及来自不同类的项目的objectId。然后我转到cellForRowAt方法中的每个单元格并加载数据。这个方法让我遇到了两个问题:1)其中一个单元格的动态高度不起作用,因为直到制作单元格之后才找到标签的文本。 2)所有单元格都有一个“跳跃”(我可以看到在我向下滚动时填充的行,我猜因为它在每个滚动时加载内容)看起来我向下滚动了tableview。

所以我希望之前预先加载所有数据并将其放在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)
        }
    })
}

2 个答案:

答案 0 :(得分:1)

查看您的项目,我看到多个数组具有不同的数据。使用这种结构编辑代码非常困难。

我会这样做:

1)创建对象来存储值,比如结构/类Animal,Human,Element。如果它们具有相同的值(如ids或其他),则可以创建超类Object并将其他对象作为子类

2)创建一个数组作为表的数据源,对象不是值

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

或者

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

答案 1 :(得分:0)

一个简单的解决方案,在你的新闻加载时分配表视图的数据源..你可以在loadNews方法结束时这样做

tableView.dataSource = self

确保数据源未分配到storyboard等其他地方