Hi I'm having the issue that after refreshing my collectionView I am getting each cell twice on
my view. And after refreshing a second time I am getting each cell 3 times. I am also getting this issue when presenting the view with a segue a second time.
How can I fix that?
Can someone help me?
Thank you in advance!!!
import UIKit
import Firebase
class FeedViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let refreshControl = UIRefreshControl()
#IBOutlet weak var collectionview: UICollectionView!
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
collectionview.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(refreshWeatherData(_:)), for: .valueChanged)
refreshControl.tintColor = UIColor(hex: "#F17D32")
}
#objc private func refreshData(_ sender: Any) {
fetchData()
posts.removeAll()
collectionview.reloadData()
}
private func fetchData() {
getPost()
self.refreshControl.endRefreshing()
}
func getPosts(){
let ref = Database.database().reference()
ref.child("posts").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snap) in
let postsSnap = snap.value as! [String : AnyObject]
for (_,post) in postsSnap {
let posst = Post()
if let author = post["author"] as? String, late date = post["date"], let postID = post["postID"] as? String, let userID = post["userID"] as? String {
posst.date = date
posst.author = author
posst.postID = postID
posst.userID = userID
self.posts.sort(by: {$0.postdateprog > $1.postdateprog})
self.posts.append(posst)
}
self.collectionview.reloadData()
}
})
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(posts.count)
return self.posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostCell
cell.authorlabel.text = self.posts[indexPath.row].author
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: self.collectionview.frame.width / 2 - 20, height: self.collectionview.frame.width * 0.7)
}
}
Firebase functions are asynchronous and the code in the closure will be called after the synchronous code runs. In other words, you have this
fetchData()
posts.removeAll()
collectionview.reloadData()
but posts.removeAll() and collectionview.reloadData() will be called before the array is populated.
So here's a truncated overview of the order in which things should be called.
#objc private func refreshData(_ sender: Any) {
self.loadFirebaseData()
}
func loadFirebaseData() {
...
self.posts = []
for (_,post) in postsSnap {
self.posts.append(posst)
}
self.posts.sort(by: {$0.postdateprog > $1.postdateprog}
self.collectionview.reloadData()
}
Notice that I moved the sort and the reloadData outside of the for loop. There's no reason to call those functions over and over as the datasource is being populated. Do it after that task is complete.
Related
Can anybody help me with CollectionView? I parce JSON from some API, and get String values - title and image adress, then assign that Data to custom collectionViewCell in method cellForItem(). BUT than i fast scroll the collection view some cell can be with wrong images, or duplicate images. I also override method prepareForReuse in my custom cell class and set cell.image = UIImage() , but it didnt work. Please help me to understand what wrong)
Sorry for my poor English...
collectionViewController
'''
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Result",
for: indexPath) as! ResultCell
let film = results[indexPath.item]
cell.name.text = film.title
cell.imageView.layer.borderColor = UIColor(white: 0, alpha: 0.3).cgColor
cell.imageView.layer.borderWidth = 2
cell.imageView.layer.cornerRadius = 10
cell.layer.cornerRadius = 10
// setUp image for cell
cell.imageView.image = nil
contentManager.getImage(film.poster.image, cell.imageView)
return cell
}
'''
method get image
'''
func getImage(_ url_str:String, _ imageView:UIImageView) {
let url:URL = URL(string: url_str)!
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: {(data, response, error) in
if data != nil {
let image = UIImage(data: data!)
if(image != nil) {
DispatchQueue.main.async(execute: {
imageView.image = image
imageView.alpha = 0
UIView.animate(withDuration: 1, animations: {
imageView.alpha = 1.0
})
})
}
}
})
task.resume()
}
'''
Cell custom class
'''
class ResultCell: UICollectionViewCell {
#IBOutlet var imageView: UIImageView!
#IBOutlet var name : UILabel!
override func prepareForReuse() {
self.imageView.image = UIImage()
self.name.text = nil
super.prepareForReuse()
}
}
'''
Firebase Database
driver1 Main page
toyota car details
FInal Output
So the problem is that driver1 has 2 cars. how can i make the tableView show for toyota car information and mazda car information.
I was able to show driver 1's car list by this code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "driverRequestCell", for: indexPath)
if let email = Auth.auth().currentUser?.email {
Database.database().reference().child("Driver").queryOrdered(byChild: "email").queryEqual(toValue: email).observe(.childAdded, with: { (snapshot) in
let snapshot = self.driverRequests[indexPath.row]
if let driverRequestDictionary = snapshot.value as? [String:AnyObject] {
if let typeOfCar = driverRequestDictionary["car"] as? String {
cell.textLabel?.text = typeOfCar
}
}
})
}
return cell
}
So my current code for didSelectRowAt is:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let snapshot = driverRequests[indexPath.row]
performSegue(withIdentifier: "carDetailsSegue", sender: snapshot)
}
I think it has something to do with the snapshot, but I can't figure it out. Need help from the pro's
If you want to show all car information in the same cell, first you need a cell with three UILabel, one for all of the information you want to show, This is an example:
I suppose you have your own custom cell class, so you need to drag&drop label outlets into your class.
Now, you have to change you code into cellForRowAt: this way:
if let driverRequestDictionary = snapshot.value as? [String:AnyObject] {
if let typeOfCar = driverRequestDictionary["car"] as? String {
cell.modelLabel?.text = typeOfCar
}
if let colorOfCar = driverRequestDictionary["color"] as? String {
cell.colorLabel?.text = colorOfCar
}
if let plateOfCar = driverRequestDictionary["plate"] as? String {
cell.plateLabel?.text = plateOfCar
}
}
You can use a default value if color and plate doesn't exists adding else statements, or using three operand:
`if let plateOfCar = driverRequestDictionary["plate"] as? String {
cell.plateLabel?.text = plateOfCar
}else{
cell.plateLabel?.text = "Unknown"
}`
ADVICE: Avoid to do a request into cellForRowAt:, Instead of this, make an asynchronous request (on viewDidLoad: for example) using a closure. Then use the result of the closure to fill an array and then reload you table view.
EDIT:
You should struct your nodes this way, where giuseppesapienza is user ID, while note is your car objext:
'[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key text.'
I'm getting an NSUnknownKeyException at the text variable of my Message model when I attempt to access it in my second tableView() function (at the end of this code). I am still pretty new to swift and don't know how to resolve this. Through my google searches, I found that it could be something to do with storyboards, but I've actually opted to create my UI programmatically so I'm not sure how that could be causing this.
import UIKit
import Firebase
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "chat", style: .plain, target: self, action: #selector(handleChat))
observeMessages()
}
#objc func handleChat() {
let chatController = ChatController(collectionViewLayout: UICollectionViewLayout())
navigationController?.pushViewController(chatController, animated: true)
//present(chatController, animated: true, completion: nil)
}
var messages = [Message]()
func observeMessages() {
let ref = Database.database().reference().child("FalconsVPackers").child("messages")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message()
message.setValuesForKeys(dictionary)
self.messages.append(message)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "called")
let message = messages[indexPath.row]
cell.textLabel?.text = message.text
return cell
}
}
My Message model is very simple...
import UIKit
class Message: NSObject {
var text: String?
}
Any help or advice would be greatly appreciated!
You are using NSKeyValueCoding to populate Message object so you must add #objc to text property.
message.setValuesForKeys(dictionary) // NSKeyValueCoding
.
class Message : NSObject {
#objc var text: String?
}
If you just want to populate your message object, better just use properties directly instead of setValuesForKeys. And in this case you don't have to use #objc too.
let message = Message()
message.text = dictionary["text"]
I can't get the number of children returned by snapshot. I always get Snap ((null)) (null) when I write print(self.newCars) in another position:
class NewCarsViewController: UICollectionViewController {
var firebase: FIRDatabaseReference?
var newCars = FIRDataSnapshot()
override func viewDidLoad() {
super.viewDidLoad()
self.firebase = FIRDatabase.database().reference(fromURL:"https://firebaseURL/").child("featuredCars")
firebase?.observeSingleEvent(of: .value, with: { snapshot in
self.newCars = snapshot
print("halim")
print(self.newCars)
// print(snapshot.childrenCount) // I got the expected number of items
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
// print(rest.value ?? "")
}
})
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NewCarCell", for: indexPath) as UICollectionViewCell
return cell
}
}
That code is working for me.
I would guess you are moving the print(self.newCars) outside of the Firebase block (closure).
It takes time for Firebase to return the data, and that data only becomes valid inside the closure.
If the print statement is outside of the closure, it's running before the data is returned from Firebase and it would be null.
I have been struggling with this issue for a long time being a novice in Swift iOs coding. Hope that someone can point me to the right direction.
In the following code I do a Fetchrequest to a CoreData Entity with names of persons. Once I get the results I am trying to pick (tapping on the corresponding row) one name and pass it back to the ViewController that invoked this view with a prepare for segue.
But each time I click on the row of the name I want to select, I end up with a: "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in UITableView.h if possible.'"
It seems I am invoking my indexPath in the wrong way.
Here below my code:
.....
.....
let fetchedResults =
managedObjectContext.executeFetchRequest(fetchRequest,
error: &error) as [NSManagedObject]?
if let results = fetchedResults {
names = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
// MARK: - UITableViewDataSource
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("KidCell") as UITableViewCell
let kidName = names[indexPath.row]
cell.textLabel!.text = kidName.valueForKey("kidName") as String?
if kidName != selectedKid {
cell.accessoryType = .None
} else {
cell.accessoryType = .Checkmark
selectedIndexPath = indexPath
}
return cell
}
// MARK: - UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row != selectedIndexPath.row {
if let newCell = tableView.cellForRowAtIndexPath(indexPath) {
newCell.accessoryType = .Checkmark
}
if let oldCell = tableView.cellForRowAtIndexPath(selectedIndexPath) {
oldCell.accessoryType = .None
}
selectedIndexPath = indexPath
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SelectedKid" {
let cell = sender as UITableViewCell
if let indexPath = tableView.indexPathForCell(cell) {
let kidName = names[indexPath.row]
selectedKid = kidName.valueForKey("kid") as String!
}
}
}
}
The idea is that when I tap on the name I go back with an unwind segue to the sender controller and I put the selectedName in the correct place.
Thank you fro any help!
Cristiano
your problem are with the lines below...
let cell = tableView.dequeueReusableCellWithIdentifier("KidCell") as UITableViewCell
let kidName = names[indexPath.row]
cell.textLabel!.text = kidName.valueForKey("kidName") as String?
your code should look like...
let cell = tableView.dequeueReusableCellWithIdentifier("KidCell", forIndexPath: indexPath ) as UITableViewCell
let kidName = names[indexPath.row]
cell.textLabel!.text = kidName.valueForKey(kidName)