将 3 种不同的信息加载到 3 种不同类型的细胞
Loading 3 Different Information to 3 Different types of Cells
我在 tableViewController 中有三种不同类型的单元格。我从不同的 class 获得了要拥有的单元格类型和项目的 objectId。然后我转到 cellForRowAt
方法中的每个单元格并加载任何数据。这种方法使我遇到了 2 个问题:1) 其中一个单元格的动态高度不起作用,因为它的标签文本在创建单元格之后才被发现。 2) 所有单元格都有一个 "jumpy" (我可以看到在我向下滚动时填充的行,我猜是因为它在每次滚动时加载内容)在我向下滚动 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)
}
})
}
查看您的项目,我看到多个数组具有不同的数据。使用这种结构很难编辑代码。
我会这样做:
1) 创建对象来存储值,例如 structs/classes 动物、人类、元素。如果它们具有相同的值,例如 ids 或其他值,您可以创建一个 super class 对象并将其他对象设置为 subclasses
2) 创建一个数组作为您 table 的数据源,对象不是值
//if there is no super class
var objects:[AnyObject] = []
或
//for the superclass
var objects:[YourSuperClass] = []
在下面的代码中,我将使用 Superclass,但您可以将其更改为 AnyObject
3) 在更新 table:
之前创建一个方法来填充这个对象数组
//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)我们可以使用向下转换到特定的class来设置单元格:
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()
}
所以主要想法:
在单独的队列中更新之前进行完全加载(可能会有例外,比如如果您必须加载图像而不是在显示之前等待所有图像被下载 table,最好使用简单的值填充单元格,然后在每个单元格中加载图像并为每个单元格显示一些 activity 指示器)
创建一个带有参数的对象数组,而不是用简单的值创建多个数组
使用对象数组来确定 table 中的单元格类型。
============编辑================
笔记!我在没有导入 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()
}
}
一个简单的解决方案,在加载新闻时分配 table 视图的数据源。您可以在 loadNews 方法的末尾执行此操作
tableView.dataSource = self
确保数据源没有分配到其他地方,比如故事板
我在 tableViewController 中有三种不同类型的单元格。我从不同的 class 获得了要拥有的单元格类型和项目的 objectId。然后我转到 cellForRowAt
方法中的每个单元格并加载任何数据。这种方法使我遇到了 2 个问题:1) 其中一个单元格的动态高度不起作用,因为它的标签文本在创建单元格之后才被发现。 2) 所有单元格都有一个 "jumpy" (我可以看到在我向下滚动时填充的行,我猜是因为它在每次滚动时加载内容)在我向下滚动 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)
}
})
}
查看您的项目,我看到多个数组具有不同的数据。使用这种结构很难编辑代码。
我会这样做:
1) 创建对象来存储值,例如 structs/classes 动物、人类、元素。如果它们具有相同的值,例如 ids 或其他值,您可以创建一个 super class 对象并将其他对象设置为 subclasses
2) 创建一个数组作为您 table 的数据源,对象不是值
//if there is no super class
var objects:[AnyObject] = []
或
//for the superclass
var objects:[YourSuperClass] = []
在下面的代码中,我将使用 Superclass,但您可以将其更改为 AnyObject
3) 在更新 table:
之前创建一个方法来填充这个对象数组//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)我们可以使用向下转换到特定的class来设置单元格:
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()
}
所以主要想法:
在单独的队列中更新之前进行完全加载(可能会有例外,比如如果您必须加载图像而不是在显示之前等待所有图像被下载 table,最好使用简单的值填充单元格,然后在每个单元格中加载图像并为每个单元格显示一些 activity 指示器)
创建一个带有参数的对象数组,而不是用简单的值创建多个数组
使用对象数组来确定 table 中的单元格类型。
============编辑================ 笔记!我在没有导入 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()
}
}
一个简单的解决方案,在加载新闻时分配 table 视图的数据源。您可以在 loadNews 方法的末尾执行此操作
tableView.dataSource = self
确保数据源没有分配到其他地方,比如故事板