ios - Loading 3 Different Information to 3 Different types of Cells -
i have 3 different types of cells in tableviewcontroller. type of cell have , objectid of item different class. go each cell in cellforrowat
method , load whatever data is. method has led me 2 problems: 1) dynamic height of 1 of cell not work because it's label's text not found until after cell made. 2) cells have "jumpy" (i can see rows being populated scroll down, guess because loading content every scroll) scroll down tableview.
so i want preload of data before , put in cellforrowat
instead of searching data in cellforrowat
. fix both problems, have no idea how this. based on coding knowledge place information go in each cell in arrays populate cells accordingly, not know how when using 3 different cells because put information in cells array need use indexpath.row
; can not because loading 3 different types of data , adding them different arrays indexpaths not aligned properly. way can think of doing , it's wrong. how can fix problem?
i have copied code @ bottom can see how loading cells , maybe can understanding of how fix issue:
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 following object in objects!{ self.followarray.append(object.object(forkey: "following") as! string) } self.followarray.append(pfuser.current()?.objectid! ?? string()) //so can see our own post //getting related news post let newsquery = pfquery(classname: "news") newsquery.wherekey("user", containedin: self.followarray) //find info we're following newsquery.limit = 30 newsquery.adddescendingorder("createdat") //get recent newsquery.findobjectsinbackground(block: { (objects, error) in if error == nil { //clean self.newstypearray.removeall(keepingcapacity: false) self.objectidarray.removeall(keepingcapacity: false) self.newsdatearray.removeall(keepingcapacity: false) object in objects! { self.newstypearray.append(object.value(forkey: "type") as! string) //get type (animal / human / elements) self.objectidarray.append(object.value(forkey: "id") as! string) //get object id corresponds different class 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 { 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 { 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 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 { object in objects! { let caption = (object.object(forkey: "caption") as! string) //large description of animal (can 2 - 8 lines) cell.captionlabel.text = caption } } else { print(error?.localizeddescription ?? string()) } }) return cell } }
----- edit ------
implementation of @woof's logic:
in separate swift file:
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? }
in 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 following object in objects!{ self?.followarray.append(object.object(forkey: "following") as! string) } self?.followarray.append(pfuser.current()?.objectid! ?? string()) //so can see our own post //this custom additional method make query self?.querynews(name: "news", followarray: self?.followarray ?? [], completionhandler: { (results) in //if block called in background queue, need return main 1 before making update dispatchqueue.main.async { //check array not nil if let objects = results { self?.tableobjects = objects self?.tableview.reloaddata() }else{ //objects nil //do nothing or additional stuff } } }) } else { print(error?.localizeddescription ?? string()) } } } //i've made code separated, make easy 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 info we're following newsquery.limit = 30 newsquery.adddescendingorder("createdat") //get recent newsquery.findobjectsinbackground(block: { [weak self] (objects, error) in if error == nil { //now important thing //we need create dispatch group make possible load additional data before updating table //note! if data large, maybe need show kind of activity indicator, otherwise user won't understand going on table let dispathgroup = dispatchgroup() object in objects! { //detecting type of object guard let type = object.value(forkey: "type") as? string else{ //wrong value or type, don't check other fields of object , start check next 1 continue } let userid = object.value(forkey: "user") as? string let id = object.value(forkey: "id") as? string let date = object.createdat //so can check type , create objects //and entering our group dispathgroup.enter() switch type { case "element": //now make query type self?.queryelementclass(name: "element", id: id!, completionhandler: { (name, objectid, owner, type, ability, strength) in //i've added check parameters, , if nil, won't add objects table //but can change wish if let objectname = name, let objectsid = objectid { //now can create 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 leave dispatchgroup dispathgroup.leave() }) case "human": //same 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 leave dispatchgroup dispathgroup.leave() }) case "animal": //same 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 leave dispatchgroup dispathgroup.leave() }) default: //unrecognized type //don't forget leave dispatchgroup dispathgroup.leave() } } //we need wait tasks entered group //you can add timeout here, like: user should wait 5 seconds maximum, if queries in group not finished somehow dispathgroup.wait() //so finished queries, , can return finished array completionhandler(temporaryarray) } else { print(error?.localizeddescription ?? string()) //we got error, return nil completionhandler(nil) } }) } //the method making query of 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 making query of 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 making query of 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) } }) }
looking @ projects see multiple arrays different data. hard edit code kind of structure.
i make in way:
1) create objects store values, structs/classes animal, human, element. if have same values ids or whatever, can create super class object , make other objects subclasses
2) create 1 array data source table objects not values
//if there no super class var objects:[anyobject] = []
or
//for superclass var objects:[yoursuperclass] = []
in code below use superclass, can change anyobject
3) make method fill array of objects before updating table:
//i think better use clousures , make data fetching in different queue func loadnews(completionhandler: @escaping (_ objects: [yoursuperclass]) -> void){ yourbackgroundqueue.async{ var objects = // fill here array objects // important return data in main thread make update dispatchqueue.main.async{ completion(objects) } } }
and fill our datasourse array, call method when need:
func updatetable(){ loadnews(){ [weak self] objects in self?.objects = objects self?.tablewview.reloaddata() }
so have array of objects
4)we can use downcast specific class set cells:
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 can fill cell properties animal object has //return cell return cell } if let human = object as? human, let cell = tableview.dequeuereusablecell(withidentifier: "humancell") as? humancell //do stuff humancell //return cell return cell } //same way can detect , fill other cells //this return empty cell if there object in array wasn't recognized. in case app won't crash, see wrong return uitableviewcell() }
so main thoughts:
make full loading before updating in separated queue (there may exceptions, if have load images , don't wait images downloaded before showing table, better fill cells using simple values , make image loading inside each cell , show activity indicator each one)
create array of objects parameters, instead of making several arrays simple values
use array of objects determine cell type in table.
============edit================ note! i've made code in playground without importing pfquery if there errors, let me know. if stuck, let me know, maybe check project directly
so, new code
//declaring objects in separated file class queryobject { var id: string? var date: date? //change of date object.createdat has different type var caption: string? var name: string? // var type: string? //use var don't need have subclasses } //if subclasses not have unique parameters, can left 1 class queryobject, without subclasses //in case uncomment "type" variable in queryobject, can check var in cellforrowat class animal: queryobject { //add additional properties } class human: queryobject { //add additional properties } class element: queryobject { //add 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 following object in objects!{ self?.followarray.append(object.object(forkey: "following") as! string) } self?.followarray.append(pfuser.current()?.objectid! ?? string()) //so can see our own post //this custom additional method make query self?.querynews(name: "news", followarray: self?.followarray ?? [], completionhandler: { (results) in //if block called in background queue, need return main 1 before making update dispatchqueue.main.async { //check array not nil if let objects = results { self?.tableobjects = objects self?.tableview.reloaddata() }else{ //objects nil //do nothing or additional stuff } } }) } else { print(error?.localizeddescription ?? string()) } } } //i've made code separated, make easy 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 info we're following newsquery.limit = 30 newsquery.adddescendingorder("createdat") //get recent newsquery.findobjectsinbackground(block: { [weak self] (objects, error) in if error == nil { //now important thing //we need create dispatch group make possible load additional data before updating table //note! if data large, maybe need show kind of activity indicator, otherwise user won't understand going on table let dispathgroup = dispatchgroup() object in objects! { //detecting type of object guard let type = object.value(forkey: "type") as? string else{ //wrong value or type, don't check other fields of object , start check next 1 continue } let id = object.value(forkey: "id") as? string let date = object.createdat //so can check type , create objects //and entering our group dispathgroup.enter() switch type { case "animal": //now make query type self?.queryadditionalclass(name: "animals", id: id, completionhandler: { (name, caption) in //i've added check parameters, , if nil, won't add objects table //but can change wish if let objectname = name, let objectcaption = caption { //now can create object let newanimal = animal() newanimal.id = id newanimal.date = date temporaryarray.append(newanimal) } //don't forget leave dispatchgroup dispathgroup.leave() }) case "human": //same 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 leave dispatchgroup dispathgroup.leave() }) case "elements": //same 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 leave dispatchgroup dispathgroup.leave() }) default: //unrecognized type //don't forget leave dispatchgroup dispathgroup.leave() } } //we need wait tasks entered group //you can add timeout here, like: user should wait 5 seconds maximum, if queries in group not finished somehow dispathgroup.wait() //so finished queries, , can return finished array completionhandler(temporaryarray) } else { print(error?.localizeddescription ?? string()) //we got error, return nil completionhandler(nil) } }) } //the method making query of 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 can detect object have , 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 won't use subclasses, check type variable using switch case 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 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 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 elementcell //return cell return cell } return uitableviewcell() } }
Comments
Post a Comment