What I want to do:
Order all my TableViewCells from most recent to the oldest.
What is my problem:
I can order Cells from the same type by Time, though I fail at ordering them all in the same section by one common value (the time).
Here is my code:
import UIKit
import Firebase
class popularViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet var table: UITableView!
// var models = [PhotoPost]()
var texttt = [TextPost]()
var phots = [PhotoPost]()
var mixed = [MixedPhoto]()
var switchy = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
gettingPosts()
table.register(popularTableViewCell.nib(), forCellReuseIdentifier: popularTableViewCell.identifier)
table.register(featuredTableViewCell.nib(), forCellReuseIdentifier: featuredTableViewCell.identifier)
table.register(textTableViewCell.nib(), forCellReuseIdentifier: textTableViewCell.identifier)
table.register(mixedTableViewCell.nib(), forCellReuseIdentifier: mixedTableViewCell.identifier)
table.delegate = self
table.dataSource = self
self.table.estimatedRowHeight = 225
self.table.rowHeight = UITableView.automaticDimension
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return mixed.count
}
else if section == 1{
return phots.count
}
else{
return texttt.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: mixedTableViewCell.identifier, for: indexPath) as! mixedTableViewCell
cell.configure(with: self.mixed[indexPath.row])
return cell
}
else if indexPath.section == 1{
let cell = tableView.dequeueReusableCell(withIdentifier: popularTableViewCell.identifier, for: indexPath) as! popularTableViewCell
cell.configure(with: self.phots[indexPath.row])
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: textTableViewCell.identifier, for: indexPath) as! textTableViewCell
cell.configure(with: self.texttt[indexPath.row])
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "commentsVC")
vc.modalPresentationStyle = .fullScreen
self.navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func gettingPosts(){
let db = Firestore.firestore()
let postsRef = db.collection("posts")
postsRef.addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added){
let data = diff.document.data()
let Name = data["username"] as! String
let text = data["description"]
let likes = data["likes"] as! Int
let typ = data["postType"] as! Int
let pfp = data["profileImage"] as! String
let uid = data["uid"] as! String
let pic = data["picture"]
let time = data["time"] as! String
let pid = data["postID"] as! String
if typ == 0{ // Text post
let dasDing = TextPost(numberOfComments: 0, username: Name, timestampName: time, userImageName: pfp, textName: text as! String, postID: pid)
self.texttt.append(dasDing)
self.texttt.sort(by: { $0.timestampName > $1.timestampName })
}
if typ == 1{ // Text + Picture post
let Mixed = MixedPhoto(numberOfComments: 0, username: Name, timestampName: time, userImageName: pfp, textName: text as! String, postImageName: pic as! String, postID: pid)
self.mixed.append(Mixed)
self.mixed.sort(by: { $0.timestampName > $1.timestampName })
}
if typ == 2{ // Picture Post
let Foto = PhotoPost(numberOfComments: 0, username: Name, timestampName: time, userImageName: pfp, postImageName: pic as! String, postID: pid)
self.phots.append(Foto)
self.phots.sort(by: { $0.timestampName > $1.timestampName })
}
if typ == 3{ // Text + Video Post
}
if typ == 4{ // Video Post
}
}
}
self.table.reloadData()
}
}
}
struct PhotoPost {
let numberOfComments: Int
let username: String
let timestampName: String
let userImageName: String
let postImageName: String
let postID: String
}
struct TextPost {
let numberOfComments: Int
let username: String
let timestampName: String
let userImageName: String
let textName: String
let postID: String
}
struct MixedPhoto {
let numberOfComments: Int
let username: String
let timestampName: String
let userImageName: String
let textName: String
let postImageName: String
let postID: String
}
Note
Right now I ordered every Cell type in its own section to be able to display them all, but this isnt a long-term solution for me. I want to have them all in only one section.
Here is another solution:
After reading Paul & Rikh's answer, I thought about this other solution.
You can have one Post model defined as follows:
struct Post {
let numberOfComments: Int
let username: String
let timestampName: String
let userImageName: String
let postImageName: String?
let textName: String?
let postID: String
}
Notice how postImageName and textName are both optionals. That will help you distinguish among post types.
Next, you want to declare a variable allPosts of type [Post]:
var allPosts = [Post]()
And you need to feed it to your tableView:
func numberOfSections(in tableView: UITableView) -> Int {
return 1 // You only need one section. You can't omit this function btw.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allPosts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = allPosts[indexPath.row]
if post.postImageName != nil && post.textName != nil {
let cell = tableView.dequeueReusableCell(withIdentifier: mixedTableViewCell.identifier, for: indexPath) as! mixedTableViewCell
cell.configure(with: post)
return cell
}
else if post.postImageName != nil {
let cell = tableView.dequeueReusableCell(withIdentifier: popularTableViewCell.identifier, for: indexPath) as! popularTableViewCell
cell.configure(with: post)
return cell
}
else if post.textName != nil {
let cell = tableView.dequeueReusableCell(withIdentifier: textTableViewCell.identifier, for: indexPath) as! textTableViewCell
cell.configure(with: post)
return cell
}
return UITableViewCell() // default: return empty cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "commentsVC")
vc.modalPresentationStyle = .fullScreen
self.navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
Finally, inside your gettingPosts function, you can assign each item to allPosts, and before reloading your tableView, you can sort allPosts by timestamp (and don't forget to call reloadData from the main thread):
self.allPosts.sort(by: { $0.timestampName > $1.timestampName })
DispatchQueue.main.async {
self.tableView.reloadData()
}
EDIT: Inside your gettingPosts function you need to feed the values to your Post struct rather than the previous model you had.
let post = Post(numberOfComments: 0, username: Name, timestampName: time, userImageName: pfp, textName: text as? String, postImageName: pic as? String, postID: pid)
self.allPosts.append(dasDing)
One more remark: there has to be a better way to do this. Why not decode your JSON instead? This can become messy if you got several types of posts. If you post your JSON structure, maybe we can help you figure out a better solution.
You can do that, but you have to declare an array allPosts of type Any:
var allPosts = [Any]()
And you need to feed it to your tableView:
func numberOfSections(in tableView: UITableView) -> Int {
return 1 // You only need one section. You can't omit this function btw.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allPosts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let mixedPhoto = allPosts[indexPath.row] as? MixedPhoto {
let cell = tableView.dequeueReusableCell(withIdentifier: mixedTableViewCell.identifier, for: indexPath) as! mixedTableViewCell
cell.configure(with: mixedPhoto)
return cell
}
else if let photoPost = allPosts[indexPath.row] as? PhotoPost {
let cell = tableView.dequeueReusableCell(withIdentifier: popularTableViewCell.identifier, for: indexPath) as! popularTableViewCell
cell.configure(with: photoPost)
return cell
}
else if let textPost = allPosts[indexPath.row] as? TextPost {
let cell = tableView.dequeueReusableCell(withIdentifier: textTableViewCell.identifier, for: indexPath) as! textTableViewCell
cell.configure(with: textPost)
return cell
}
return UITableViewCell() // default: return empty cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "commentsVC")
vc.modalPresentationStyle = .fullScreen
self.navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
Finally, inside your gettingPosts function, you can assign each item to allPosts since it is of type any, and before reloading your tableView, you can sort allPosts by timestamp (and don't forget to call reloadData from the main thread):
self.allPosts.sort(by: { $0.timestampName > $1.timestampName })
DispatchQueue.main.async {
self.tableView.reloadData()
}
You have three different lists maintained. To order all of them, you will have to combine them in one list. Now all of your models have majority of the data common. You can combine them into one protocol and have all your subsequent models conform to that protocol. Something like this:
protocol Post{
var numberOfComments: Int { get set }
var username: String {get set}
var timestampName: String {get set}
var userImageName: String {get set}
}
struct PhotoPost : Post{
var numberOfComments: Int
var username: String
var timestampName: String
var userImageName: String
let postImageName: String
let postID: String
}
struct TextPost : Post { ... }
struct MixedPhoto : Post { ... }
And inside your UIViewController you should have one list.
var oneListToRuleThemAll = [Post]()
func gettingPosts(){
snapshot.documentChanges.forEach{
//blah blah blah
oneListToRuleThemAll.append(PhotoPost(...))
}
//sort by timestamp and reload the table!
}
Or as Paul suggests in his comment, you can go the class (inheritance) route too! To avoid having to write the variables again inside each subclass (But you cannot use structs with that and will have to change everything to classes).
Here I have a Realm Database which is have some data in it and I want to display it on my Stimulator but it turn out display some other thing. What's wrong in my code?
This is the data of my Realm Database and I also marked the data which I want to display it.
The stimulator which display something like this.
And here is my ViewController.swift code's.
import UIKit
import RealmSwift
class ViewController: UIViewController,UITableViewDataSource { //UITableViewDataSource
#IBOutlet weak var mytableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let realm = try! Realm()
let theItem = realm.objects(Item.self).filter("itemid >= 1")
return theItem.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let realm = try! Realm()
let theItem = realm.objects(Item.self).filter("itemid >= 1")
print(theItem)
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1")
//I suspect the problem is at here...
cell?.textLabel?.text = "\(theItem)"
return cell!
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
class Category: Object {
#objc dynamic var name: String?
#objc dynamic var caid: Int = 0
}
class Item: Object {
#objc dynamic var name: String?
#objc dynamic var itemid: Int = 0
#objc dynamic var cateid: Int = 0
}
Your problem is that you need to get the string from the Item object. try something like
"\(theItem.name)".
func getNames() -> [String]{
let items = realm.objects(Item.self).filter("itemid >= 1").toArray(ofType: Item.self ) as [Item]
return items.map { $0.name }
}
extension Results {
func toArray<T>(ofType: T.Type) -> [T] {
var array = [T]()
for i in 0 ..< count {
if let result = self[i] as? T {
array.append(result)
}
}
return array
}
}
I found a way to display the data already. I just need to add indexPath.row in my code and it can handle the data already.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let realm = try! Realm()
let theItem = realm.objects(Item.self).filter("itemid >= 1")
//I only add below this indexpath
let cellData = theItem[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1")
//and change this part and it's done.
cell?.textLabel?.text = cellData.name
print(theItem)
return cell!
}
I have fixed my earlier problem and have now worked out where the main problem is, I am pulling in a json array with alamofire but am not sure how to properly move the data from one viewcontroller to another. If I hardcode the array with var name = ["Hello", "Goodbye"] I can get it to work but am not sure how to do it with the json. Thank you to any and all help.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let URL_GET_DATA = "http://www.localnewsplus.com.au/ios/service.php"
#IBOutlet weak var tableViewHeroes: UITableView!
var heroes = [Hero]()
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return heroes.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let hero: Hero
hero = heroes[indexPath.row]
cell.labelName.text = hero.name
cell.labelTeam.text = hero.team
Alamofire.request(hero.imageUrl!).responseImage { response in
if let image = response.result.value {
cell.heroImage.image = image
}
}
//cell.labelName.text = name[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(URL_GET_DATA).responseJSON { response in
if let json = response.result.value {
let heroesArray : NSArray = json as! NSArray
for i in 0..<heroesArray.count{
self.heroes.append(Hero(
name: (heroesArray[i] as AnyObject).value(forKey: "st_heading") as? String,
team: (heroesArray[i] as AnyObject).value(forKey: "st_modified") as? String,
imageUrl: (heroesArray[i] as AnyObject).value(forKey: "imageurl") as? String
))
}
self.tableViewHeroes.reloadData()
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "articleViewController") as? articleViewController
vc?.article_st_heading = name[indexPath.row]
self.navigationController?.pushViewController(vc!, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
There are 2 ways to do this
Try to get data from the array which you used in cellForRow to populate data
Let text = someArray[indexPath. Row]
Get the cell instead of the create new one in didSelect method
Let cell = table. CellForRowAt[indexPath ]
Let text = cell.text
All the tableview functions are working except cell for row index path .
The problem maybe that foods array is empty so the number for rows is 0 so the cell for row at index path is not called
#IBOutlet weak var foooods: UITableView!
var databaseref = Database.database().reference()
var img : AnyObject?
var foods = [String?]()
override func viewDidLoad() {
super.viewDidLoad()
self.databaseref.child("basic food").observe(.childAdded, with: {( snap: DataSnapshot) in
let snapp = snap.value as! [String:AnyObject]
if let x = snapp["name"] as! String? {
self.foods.insert(x, at: 0)
//self.foods.append(x)
}
})
self.foooods.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.foods.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("difufuehf")
let cell : foodsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "aupa", for:indexPath) as! foodsTableViewCell
print("fufvksdfvysdgfvjdsgfdsygfvds,jhvjsdvsdjvguydsfgdsylfgdsyfgsdlygfsiygf")
if let foo = foods[indexPath.row] {
print(foo)
cell.food.text = foo
}
return cell
}
This must be a duplicate but I can't find one.
Your issue is that you call reloadData in the wrong place which results in it being called far too soon. You need to call it inside the completion block, after you update your data model.
And you need to make sure it gets called on the main queue.
override func viewDidLoad() {
super.viewDidLoad()
self.databaseref.child("basic food").observe(.childAdded, with: {( snap: DataSnapshot) in
if let snapp = snap.value as? [String:Any], let x = snapp["name"] as? String {
self.foods.insert(x, at: 0)
//self.foods.append(x)
DispatchQueue.main.async {
self.foooods.reloadData()
}
}
})
}
Note that I also fixed the way the value is obtained. You really need to avoid force unwrapping and force casting.
This is the code responsible for uploading text of a post for a blogging app the text of the post is retrieved correctly and saved in snapshot
struct postt {
let username : String!
let textofpost : String!
}
class TableViewController: UITableViewController {
var databaseref = FIRDatabase.database().reference()
var loggedinuser : AnyObject?
var posts = [postt]()
override func viewDidLoad() {
super.viewDidLoad()
self.loggedinuser = FIRAuth.auth()?.currentUser
self.databaseref.child("users").child(self.loggedinuser!.uid).observeSingle
Event(of: .value) {(snapshot:FIRDataSnapshot) in
let snapshot = snapshot.value as! [String:AnyObject]
let username = snapshot["name"] as? String
self.databaseref.child("posts").queryOrderedByKey().observe(.childAdded, with: {( snapshot: FIRDataSnapshot) in
let snapshot = snapshot.value as? NSDictionary
The next variable textofpost doesn't contain anything and i don't know what is the problem so when i represent the cell only the label appears which has a snapshot from the path name in the node users
let textofpost = snapshot?["text"] as? String
self.posts.insert(postt(username : username, textofpost : textofpost), at: 0)
// self.feeds.reloadData()
self.tableView.reloadData()
}
)}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let label = cell.viewWithTag(1) as! UILabel
label.text = posts[indexPath.row].username
let textview = cell.viewWithTag(2) as! UITextView
textview.text = posts[indexPath.row].textofpost
return cell
}
}