I have a firebase database to pull 50 users with the highest integer value and to display them from highest to lowest. The issue will arise when I enter the leaderboard view for the first time. The order should show jlewallen18 at the top AND THEN appledev. But on first load appledev is at the top, until I back out and open the leaderboard again (code at the bottom).
Leaderboard code:
class LeaderboardViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var leaderboardTableView: UITableView!
var userModel : [User] = [User]()
let pipeline = ImagePipeline {
//config settings for image display removed for brevity
}
override func viewDidLoad() {
super.viewDidLoad()
leaderboardTableView.delegate = self
leaderboardTableView.dataSource = self
fetchUsers()
}
func fetchUsers() {
let queryRef = Database.database().reference().child("users").queryOrdered(byChild: "ranking").queryLimited(toLast: 50)
queryRef.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String : AnyObject]{
let user = User(dictionary: dictionary)
self.userModel.append(user)
}
DispatchQueue.main.async(execute: {
self.leaderboardTableView.reloadData()
})
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userModel.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "leaderboardTableCell", for: indexPath) as! LeaderboardTableCell
if userModel.count > indexPath.row {
if let profileImageURL = userModel[indexPath.row].photoURL {
let url = URL(string: profileImageURL)!
var options = ImageLoadingOptions()
options.pipeline = pipeline
options.transition = .fadeIn(duration: 0.25)
Nuke.loadImage(
with: ImageRequest(url: url).processed(with: _ProgressiveBlurImageProcessor()),
options: options,
into: cell.userImage
)
}
}
cell.userName.text = userModel[indexPath.row].username
cell.watchTime.text = "\(String(describing: userModel[indexPath.row].watchTime!))"
cell.ranking.text = "\(indexPath.row + 1)"
cell.userImage.layer.cornerRadius = cell.userImage.frame.size.width/2
return cell
}
}
I thought it might be because I am using the same model name userModel in both my Profile page view and my Leaderboard view but when i changed the model name in my leaderboard view nothing changed. What else can I share to help? Thanks!
EDIT: here's my console output after printing out watchTime which is the integer I have rankings for:
HERES WHERE I OPEN LEADERBOARD PAGE FIRST:
Optional(28)
Optional(247)
Optional(0)
Optional(0)
Optional(0)
Optional(0)
AFTER I GO BACK AND CLICK TO VIEW LEADERBOARD AGAIN:
Optional(247)
Optional(28)
Optional(0)
Optional(0)
Optional(0)
Optional(0)
The issue here is related to this line of code...
let queryRef = Database.database().reference().child("users").queryOrdered(byChild: "ranking").queryLimited(toLast: 50)
Changing this limit to 10 makes the app work as expected, which is a temporary 'fix'.
If we figure out why that limit is causing issues I'll be sure to update this answer.
Related
I have a problem since yesterday morning but I can't figure it out how can I resolve this issue.
I'm having a table view which is using prototype cells, 2 labels and 1 photo. For the labels I used Firestore and for the picture firebase storage.
The problems is that the only way I know how to retrieve photos from my firebase storage is this code
let storage = Storage.storage()
let storageRef = storage.reference()
let ref = storageRef.child("Mancare/Mancare3.jpg")
testImage.sd_setImage(with: ref)
I want to retrieve the photos into my table view, but I do not know how can I can accomplish that.
This is what im using for retrieving the labels with Firestore . I'll paste only the necessary parts of the code :
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return labels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewTest.dequeueReusableCell(withIdentifier: "CountryTableViewCell", for: indexPath) as! CountryTableViewCell
let storage = Storage.storage()
let storageRef = storage.reference()
let ref = storageRef.child("Mancare/Mancare3.jpg")
let label = labels[indexPath.row]
cell.labelTest.text = label.firstLabel
cell.labelLaba.text = label.secondLabel
return cell
}
func getDatabaseRecords() {
let db = Firestore.firestore()
labels = [] // Empty the array
db.collection("labels").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let newEntry = Labels(
firstLabel: data["firstLabel"] as! String,
secondLabel: data["secondLabel"] as! String)
self.labels
.append(newEntry)
}
}
DispatchQueue.main.async {
self.tableViewTest.reloadData()
}
}
}
This is how I declared the labels:
struct Labels {
let firstLabel: String
let secondLabel: String
}
var labels: [Labels] = []
If someone can help me , ill be forever grateful . Thanks
First, you need to fix your model so it can help you. Add the bucket name to the model like this:
Struct Labels {
let firstLabel: String
let secondLabel: String
let photoKey: String // This will store the bucket name for this `Labels`
}
Now in your getDatabaseRecords change:
let newEntry = Labels(firstLabel: data["firstLabel"] as! String,
secondLabel: data["secondLabel"] as! String),
photoKey: data["photoKey"] as! String) // Added Line
Then in cellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewTest.dequeueReusableCell(withIdentifier: "CountryTableViewCell", for: indexPath) as! CountryTableViewCell
let label = labels[indexPath.row]
let storageRef = Storage.storage().reference()
let photoRef = storageRef.child(label.photoKey)
cell.labelTest.text = label.firstLabel
cell.labelLaba.text = label.secondLabel
cell.imageView.sd_setImage(with: photoRef) // Assuming the image view in your cell is named this
return cell
}
Last, make sure your document structure matches the new Labels Model in the firebase console, and you have images as well in the root of your storage that match with all the photoKeys. Btw, Labels is not a very good model name, I just went with it for consistency
I am trying to populate a UITableView using an array and I am unable to do so. Here is what I have so far. This code is for retrieving data and storing it in the array that I am using to populate the UITableView:
func prepareForRetrieval() {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).observe(.value, with: {
(snapshot) in
for snap in snapshot.children.allObjects {
let id = snap as! DataSnapshot
self.keyArray.append(id.key)
}
self.updateCart()
})
}
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
print(self.cartArray.count)
})
}
}
The data is properly appending into the array and when I print the count of the array, it prints the correct count. This means that the data is there. However, when I try to populate a UITableView, it doesn't detect any data. I have the following code to make sure that there is data in the array before trying to populate the UITableView:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.prepareForRetrieval()
if cartBrain.cartArray.isEmpty == false{
tableViewOutlet.dataSource = self
tableViewOutlet.reloadData()
}
else {
tableViewOutlet.isHidden = true
tableViewOutlet.isUserInteractionEnabled = false
purchaseButtonOutlet.isEnabled = false
cartEmptyLabel.text = "Your cart is empty. Please add items and check back later."
}
}
When I open the View Controller, the TableView is disabled because it doesn't detect any data. I have already set the data source to self and the thing is that when the count of the array is printed, it again prints the correct amount. I have already set the data source to self for the UITableView. Here is my code for the UITableView:
extension CartViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cartBrain.cartArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartcustomcell", for: indexPath)
cell.textLabel?.text = cartBrain.cartArray[indexPath.row].itemName
cell.detailTextLabel?.text = String(cartBrain.cartArray[indexPath.row].itemQuantity)
return cell
}
}
I don't understand why the count of the array prints the correct amount meaning that there is data stored in it but when the View Controller is loaded, it detects that the array is empty. Thanks for the help and I'm sorry if the question is a bit unclear.
After appending data to cartArray in updateCart you should reloadData(), like this:
weak var tableViewOutlet: UITableView?
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
DispatchQueue.main.async {
self.tableViewOutlet.reloadData()
}
})
}
}
The updateCart doesn't seem to have any connection to the tableViewOutlet so you need to pass in a reference to it in your viewDidLoad like this:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.tableViewOutlet = tableViewOutlet
cartBrain.prepareForRetrieval()
Note: Since you're using a for loop to trigger the async call multiple times you can use the array count to check if all the items are appended to do the reload to avoid multiple reloads.
In my Xcode Project I will like to have a similar view like Snapchat's "Send To..." screen (I have attached a screenshot). I have already made a tableview and populate it and have allowed multiple selection on. I am currently having trouble with two things:
1) Multiple Selection: I can select an cell I want, but when I tap on the search bar and start typing, all my previous selections go away. I am assuming that I need to add all of the names in a array and somehow communicate the array with the table so it shows if this username is in the array then make it selected in the tableview. But I am not sure how to do that. How can I do this?
2) Sending to Bottom Bar (blue in photo): As you may know, in Snapchat as you press on which users you want to send the snap to, their names get added to the bar at the bottom, as you fill up the bar, it because swipe able where you can horizontally scroll through the names you have added. I can append the names to an array and show the array in a label like theirs, but I do not know how to make it so a user can horizontally scroll through it.How do I implement this same feature?
Feel free to answer ANY of the questions! You do not need to do all of them, I just need them answered. Here's my code so far:
class User {
var userID:String?
var userFullName:String?
var userUsername:String?
var userProfileImage:PFFile?
var isPrivate:Bool
init(userID : String, userFullName : String, userUserName : String, userProfileImage : PFFile, isPrivate : Bool) {
self.userID = userID
self.userFullName = userFullName
self.userUsername = userUserName
self.userProfileImage = userProfileImage
self.isPrivate = isPrivate
}
}
var userArray = [User]()
func loadFriends() {
//STEP 1: Find friends
let friendsQuery = PFQuery(className: "Friends") //choosing class
friendsQuery.whereKey("friendOne", equalTo: PFUser.current()?.objectId ?? String()) //finding friends
friendsQuery.limit = self.page //number of users intitally showing
friendsQuery.findObjectsInBackground (block: { (objects, error) -> Void in
if error == nil { //if no error
//clean up
self.friendsArray.removeAll(keepingCapacity: false)
//STEP 2: Find related objects depending on query setting
for object in objects! {
self.friendsArray.append(object.value(forKey: "friendTwo") as! String) //hold array info of friend
}
//STEP 3: Find friend info
let query = PFUser.query()
query?.whereKey("objectId", containedIn: self.friendsArray)
query?.addDescendingOrder("createdAt") //how to order users
query?.findObjectsInBackground(block: { (objects, error) -> Void in
if error == nil {
for object in objects! {
var user : User
let fullname = (object.value(forKey: "fullname") as! String)
let username = (object.object(forKey: "username") as! String)
let profilePhoto = (object.object(forKey: "profilePhoto") as! PFFile)
let objectID = (object.objectId!)
let isPrivate = (object.object(forKey: "isPrivate") as! Bool)
user = User(userID: objectID, userFullName: fullname, userUserName: username, userProfileImage: profilePhoto, isPrivate: isPrivate)
self.userArray.append(user)
}
self.tableView.reloadData()
} else {
print(error!)
}
})
} else {
print(error!)
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! FriendCell
let user = userArray[indexPath.row]
//add user info to cells
cell.fullnameLabel.text = user.userFullName
cell.usernameLabel.text = user.userUsername
cell.objectID = user.userID!
cell.isPrivate = user.isPrivate
user.userProfileImage?.getDataInBackground (block: { (data, error) in
if error == nil {
cell.profilePhoto.image = UIImage(data: data!)
}
})
})
}
1) Multiple Selection:
You should have a User class (e.g User) that holds user properties instead of maintaining array for each property. Store User object in a Array. User class could be like below:
class User {
var userID:String
var userFullName:String
var userName:String
var userProfileImageUrl:String
init(userID:String,userFullName:String,userName:String,userProfileImageUrl:String) {
self.userID = userID
self.userFullName = userFullName
self.userName = userName
self.userProfileImageUrl = userProfileImageUrl
}
}
You could have a User extension to check if that user is selected or not(e.g isSelected).
import UIKit
import Foundation
private var selectedKey: UInt8 = 0
extension User {
var isSelected:Bool{
get {
return objc_getAssociatedObject(self, &selectedKey) as! Bool
}
set {
objc_setAssociatedObject(self, &selectedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Now in your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell check that user.isSelected == true/false and update your selected/deselected image accordingly.
And update the value of isSelected in func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
2) Sending to Bottom Bar:
For bottom bar add a UICollectionView as a subview in UIView. Create a class overriding UICollectionViewCell that holds a UILabel. You can add flow layout in UICollectionView.
I have given just an idea to start with.Hope it will help you.
I think, you set bool check for every cell in tableView. If cell load again, it will not show check. Because, It check is false.
I'm new to swift and having trouble with setting value to FIRDataSnapshot. I simply created a calculator. I took MealCaloryArray in didload method and display it tableviewcell (with the help of FIRDataSnapshot list) and the calory is changed in the plus function and I have to send the new value to tableviewcell again. However, I couldnt set the new value in FIRDataSnapshot array list. I tried to useself.calory[buttonRow].setValue(<value: n, forUndefinedKey:"")but I dont have proper "forUndefinedKey" value. Do you have any suggestions?
My nested Firebase DB Structure and code blocks is attached.
Firebase Child Structure:
Database Screenshot
var calory: [FIRDataSnapshot]! = []
override func viewDidLoad() {
ref = FIRDatabase.database().reference()
let CoursesRef = ref.child("CompanyMeals")
CoursesRef.observe(.childAdded, with: { snapshot in
self.calory = snapshot.childSnapshot(forPath: "MealCaloryArray").children.allObjects as! [FIRDataSnapshot]
self.calory.append(snapshot)
self.ingredientTableView.reloadData()
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ingredientTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MealCalculatorListCell
let cellcalory = self.calory[indexPath.row].value as? Double
if (cellcalory != nil) {
cell.itemTotalCalory.text = ("\(cellcalory!)")//String (describing: cellcalory)
let cellcalory1 = Int(cellcalory!)
firstCaloriesArray.append((cellcalory1 as AnyObject))
}
}
#IBAction func plusAction(sender: UIButton) {
cell.itemTotalCalory.text = String ((Int(oldcalory!) + fcCalory))
let newcalory = cell.itemTotalCalory.text
let n = String(newcalory!)
self.calory[buttonRow].setValue(value: n, forUndefinedKey: "")
}
The problem is really, that while offline the UItableview is not populating. Basically while online it will read from a php coded website in json and parse its data to NSUserdefaults and It will display data using the defaults set. This works very well when online.
I tested it like this. first I run the code while online( wifi connected ) to first populate the defaults, then exit the tableview, turn wifi off, and then go back in. Nothing shows. I put a breakpoint/print text where the code should had run, but it breakpoint never got excuted, the print text never got printed.
is there a reason why the code isnt running when offline? am i missing a setting i should add?
var messagesArray:[String] = [String]()
var dateArray:[String] = [String]()
class Singleton {
static let sharedInstance: UserDefaults = {
let instance = UserDefaults.standard
// setup code
return instance
}()
}
//let defaults = UserDefaults.standard
let defaults = Singleton.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
//removeDefaults()
if (isInternetAvailable() == true)
{
self.retrieveMessages("")
//storeLocal()
}
else {
// TODO data is available but not displayed ??
for (key, value) in defaults.dictionaryRepresentation() {
print("\(key) = \(value) \n")
}
}
//display current notification
//nRead()
self.notificationTable.dataSource = self
self.notificationTable.delegate = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView:UITableView, numberOfRowsInSection section: Int) -> Int{
return messagesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// this code does not run when offline
//test
let nCell = tableView.dequeueReusableCell(withIdentifier: "nCell") as UITableViewCell!
//let myLabelTitle = nCell?.viewWithTag(1) as! UILabel
let myLabelDate = nCell?.viewWithTag(2) as! UILabel
let myLabelDescription = nCell?.viewWithTag(3) as! UILabel
//messagesArray ["nContent":["Test1", "Test2"]]
myLabelDescription.text = defaults.string(forKey: "nDescription\(indexPath.row + 1)")
myLabelDate.text = defaults.string(forKey: "nDate\(indexPath.row + 1)")
//print(defaults.string(forKey:"nDate1"))
print("this code runs even while offline")
let readValue = defaults.string(forKey: "nRead\(indexPath.row + 1)")
if (readValue == "1" )
{
myLabelDate.textColor = UIColor.black
}
else
{
myLabelDate.textColor = UIColor.red
}
return nCell!
}
func tableView(_ tableView:UITableView, numberOfRowsInSection section: Int) -> Int{
return messagesArray.count
}
messagesArray.count prints 0, thus the code isn't running. fixed my own issue