I am currently getting index out of range but this only happens after the first 3 items have been displayed successfully. I looked through the code and I still don't know where im going wrong. Index out of range happens only when trying to load text in the cells.
Log when method is called from viewdidload:
Description of indexPath: 0
Description of indexPath: 1
Description of indexPath: 2
Log when I press a button to load the rest of images:
Description of indexPath: 0
Description of indexPath: 1
Description of indexPath: 2
Description of indexPath: 3
Datasource method where indexpath is out of range:
//MARK: - tableview datasource methods
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if arrayOfImgObj.count > 0 {
cell.imageView?.image = arrayOfImgObj[indexPath.row].imageTempt
print("Description of indexPath: ",indexPath.row)
cell.textLabel?.text = arrayOfUIDs[indexPath.row] // index out of range
} else {
print("\(arrayOfImgObj.count) not over 0 yet")
}
return cell
}
Method that is called when viewDidload:
//testing array
var arrayOfUIDs = User().users
func fetchAllUserFristImage() {
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if (snapshot.value as? [String: AnyObject]) != nil {
let user = User()
user.id = snapshot.key
//testing array
self.arrayOfUIDs.append(snapshot.key)
print("\(String(describing: user.id)) <-- SHUD BE THE USERUID")
self.databaseRef = Database.database().reference()
let usersPostRef2 = self.databaseRef.child("Posts").child(user.id!)
usersPostRef2.observe(.value, with: {(postXSnapshots) in
if let postDictionary2 = postXSnapshots.value as? NSDictionary {
for (p) in postDictionary2 {
let posts = p.value as! NSDictionary
//to get back to where i was delete the below for i
for (i) in posts {
let imageUrlString = i.value as! NSDictionary
guard let postUrl = imageUrlString.value(forKey: "image1") else {return}
//below is ting from below
if postUrl != nil {
self.feedArray.append(Post(fetchedImageURL: postUrl as! String))
let imageUrlString = "\(postUrl)"
let imageUrl = URL(string: imageUrlString)!
print("\(imageUrl) this shud be img url's of posts")
let imageDataL = try! Data(contentsOf: imageUrl)
self.imageObject.img2 = UIImage(data: imageDataL)
let image1ToDisplay: UIImage = self.imageObject.img2!
self.arrayOfImgObj.append(forTable(imageTempt: image1ToDisplay))
} else {print("this user had no posts, was nil")}
}
}
}
})
}
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
Edit:
I have changed teh code to only use one array of objects:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if arrayOfImgObj.count > 0 {
cell.imageView?.image = arrayOfImgObj[indexPath.row].imageTempt
print("Description of indexPath: ",indexPath.row)
cell.textLabel?.text = arrayOfImgObj[indexPath.row].users[indexPath.row] // index out of range
} else {
print("\(arrayOfImgObj.count) not over 0 yet")
}
return cell
}
changing one line in the function to this:
self.arrayOfImgObj.append(forTable(imageTempt: image1ToDisplay, users: snapshot.key))
Thanks in advance!
Your problem is that you have two arrays:
arrayOfUIDs which has one entry for each user
arrayOfImgObj which has 0-n entries for each user (So, it is likely that the number of objects in this array will be greater than the number of objects in arrayOfUIDs.
You are basing your row count on the arrayOfImgObj, but then using the row number to index into arrayOfUIDs, which results in an array bounds exception.
It may well be more elegant to have table view section per userid, but if you want to put all of the rows in a single section I would suggest you use a single array of structs as your data model.
Use something like:
struct UserImage {
var userID: String
var image: UIImage
}
var tableData = [UserImage]()
Then as you fetch each image, create a new struct and put it in your array:
func fetchAllUserFristImage() {
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if snapshot.value as? [String: AnyObject] != nil {
let user = snapshot.key
self.databaseRef = Database.database().reference()
let usersPostRef2 = self.databaseRef.child("Posts").child(user)
usersPostRef2.observe(.value, with: {(postXSnapshots) in
if let postDictionary2 = postXSnapshots.value as? [String:AnyObject] {
for (p) in postDictionary2 {
if let posts = p.value as? [String:AnyObject] {
//to get back to where i was delete the below for i
for (i) in posts {
if let imageUrlString = i.value as? [String:AnyObject], let postUrl = imageUrlString.["image1"] as? String {
self.feedArray.append(Post(fetchedImageURL: postUrl))
if let imageUrl = URL(string: postUrl), let imageDataL = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageDataL) {
self.tableData.append(UserImage(userID: user, image: image))
} else {print("this user had no posts, was nil")}
}
}
}
}
}
})
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
Now, your cellForRow can be:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.imageView?.image = tableData[indexPath.row].image
cell.textLabel?.text = tableData[indexPath.row].userID
return cell
}
Related
I am working on a group chat app and trying to fetch the last message from a node to display the last message under the group name. Everything is working fine. When the group receives a new message, the last message is showing in the correct group and some other random groups as well. If I open the correct group and come back, all the other groups are showing the correct last message. Kindly help me with the below code.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:GroupsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell") as! GroupsTableViewCell
cell.selectionStyle = .none
var user: Groupslist
if groupList.count > 0 {
user = groupList[indexPath.row]
cell.lbl_name_group.text = user.name;
cell.view_count.isHidden = true
GetLastMsg(groupID: user.group_id ?? "", cell: cell)
cell.img_group.image = UIImage.init(named:"final_grp")
GetUnReadMsgCount(groupID: user.group_id ?? "", cell: cell)
}
return cell
}
groupList contains the list of groups which is fetched from firebase on viewWillAppear.
func GetLastMsg(groupID : String, cell : GroupsTableViewCell){
let userRef2 = rootRef2.child("message").child(groupID).queryLimited(toLast: 1)
userRef2.observe( .value, with: { snapshot in
guard let _ = snapshot.value as? [String:AnyObject] else {
print("Error")
return
}
print("children count: \(snapshot.children.allObjects.count)")
for snap in snapshot.children.allObjects as! [DataSnapshot] {
let message = Message()
let value = snap.value as? [String: Any] ?? [:]
let text = value["text"] as? String ?? "Name not found"
let mimType = value["mimType"] as? String
let name_doc = value["name_doc"] as? String ?? "file.file"
message.text = text
message.mimType = mimType
message.name_doc = name_doc
message.timestamp_group = (value["timestamp"] as! NSNumber)
print(value["idSender"] as? String)
print(message.name_doc)
var msg_last = ""
// cell.lbl_lastmsg.text = msg_last
if message.mimType == "image/jpeg" {
msg_last = "Image"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "im")
}else if message.mimType == "audio/3gpp" {
msg_last = "Audio"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "mi")
}else if message.mimType == "video/mp4" {
msg_last = "Video"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "vi")
}else if message.mimType == "application/pdf" {
let name_docs = message.name_doc!.split{$0 == "."}.map(String.init)
msg_last = name_docs.last!
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "doc")
}else if mimType == nil{
msg_last = message.text!
cell.img_small_width.constant=0
}
DispatchQueue.main.async {
cell.lbl_lastmsg.text = msg_last
}
}
}
})
}
New method:
func GetUnReadMsgCount(groupID : String, cell : GroupsTableViewCell) {
rootRef2.child("group_message_unread").child(current_user!).child(groupID).observe(.childAdded, with: { (snapshot) -> Void in
print(snapshot.childrenCount)
print(snapshot.children.description)
if snapshot.childrenCount > 0 {
DispatchQueue.main.async {
cell.view_count.isHidden = false
cell.lbl_count.text = String(snapshot.childrenCount)
// self.tbl_groups.reloadRows(at: [IndexPath(row: 3, section: 0)], with: .fade)
// self.tbl_groups.reloadData()
}
}
})
}
That's the expected behavior. In a .observe( .value listener, the snapshot is always the complete data at the path. So when you add a child node, the snapshot fires with the entire new data at userRef2.
You have two main options to not get duplicates in your UI:
Clear all messages when there is an update, before adding the data to the table again. So that'd be right before for snap in snapshot.children in your code. This is by far the simplest way to solve the problem if duplicate rows, but may lead to some flashing in your UI.
Listen for child events, instead of observing the entire value. With this you'll get a single .childAdded event for the new child, you can get rid of the for snap in snapshot.children.allObjects and just add the singular new child node to the table.
My UITableView will only show all of the fully loaded contents in the cells if I follow the advice of this post and call reloadData() in the main thread with DispatchQueue. I'm doing this in tableView cellForRowAt because it wasn't fully loading the values from my array into all of the cells otherwise. Now it's constantly reloading and is obviously very energy intensive. This makes it harder to scroll in the Table View, which makes sense.
How can I prevent this ridiculous use of energy while still loading the data into the cells as I need?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell? =
charitiesSupportedTable?.dequeueReusableCell(withIdentifier: "SupportedCell")
if (cell == nil)
{
cell = UITableViewCell(style: UITableViewCellStyle.subtitle,
reuseIdentifier: "abc")
}
if(indexPath.row <= moneyRaisedOfPrevPostsArray.count-1){
cell?.textLabel?.text = charOfPrevPostsArray[indexPath.row]
cell?.detailTextLabel?.text = moneyRaisedOfPrevPostsArray[indexPath.row]
}
DispatchQueue.main.async {
self.charitiesSupportedTable.reloadData()
}
return cell!
}
Here's my viewDidLoad where I create the array
override func viewDidLoad() {
super.viewDidLoad()
let userEmail = Auth.auth().currentUser!.email
var email1 = userEmail?.replacingOccurrences(of: "#", with: "", options: NSString.CompareOptions.literal, range:nil)
email1 = email1?.replacingOccurrences(of: ".", with: "", options: NSString.CompareOptions.literal, range:nil)
charitiesSupportedTable.delegate = self
charitiesSupportedTable.dataSource = self
charitiesSupportedTable.isScrollEnabled = false
reff = Database.database().reference().child("Posts")
let userRef1 = reff?.queryOrdered(byChild: "user").queryEqual(toValue : email1)
userRef1?.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
let value = snap.value
let valueAsDict = value as! NSDictionary
charityOfPrevPosts = valueAsDict.value(forKey: "charity") as! String
viewCountOfPrevPosts = valueAsDict.value(forKey: "viewCount") as! String // times .006
moneyRaisedPrevPosts = Double(viewCountOfPrevPosts)! * 0.006
charOfPrevPostsArray.append(charityOfPrevPosts)
moneyRaisedOfPrevPostsArray.append(String(moneyRaisedPrevPosts))
}
charOfPrevPostsArray.reverse()
moneyRaisedOfPrevPostsArray.reverse()
}
)
moneyRaisedOfPrevPostsArray.removeAll()
charOfPrevPostsArray.removeAll()
reff = Database.database().reference().child("UsersViews").child(email1!).child("totalMoneyRaised")
reff?.observe(DataEventType.value, with: { (snapshot) in
var totalMoney = snapshot.value as! Double
self.totalMoneyLabel.text = String(format: "$%.02f", totalMoney)
})
// don't know if this part is relevant but I do return charitiesCount + 2 in numberOfRowsInSection
reff = Database.database().reference()
databaseHandle = reff?.child("UsersViews").child(email1!).child("charitiesSupported").observe(.value, with: { (snapshot) in
let myDictionary11 = snapshot.value as? NSDictionary
// Adds brands and charities to myData array
for value in (myDictionary11?.allValues)! {
self.myCharities.append(value as! String)
}
charitiesCount = self.myCharities.count
})
}
I click once and it place a tick , though on the second time clicking it will not remove the tick , same goes for when there is a tick, the first time click the tick will be removed but then once removed and I click again it will not replace the tick?
I can only change the tick to no tick or no tick to tick when i go out of the vc and back in ,
Is there an action/function to call that will reload the ability to action a click with out having to go out of the vc and back in?
Here is my code.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
let key = ref.child("Users").childByAutoId().key
checkIfFollowing(indexPath: indexPath)
var isFollower = false
ref.child("Users").child(uid!).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (ke, value) in following {
if value as? String == self.users[indexPath.row].userID {
isFollower = true
ref.child("Users").child(uid!).child("following/\(ke)").removeValue()
ref.child("Users").child(self.users[indexPath.row].userID!).child("followers/\(ke)").removeValue()
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = .none
//self.checkIfFollowing(indexPath: indexPath)
}
}
}
// if !isFoller - means isFollower is equal to false
if !isFollower {
let following = ["following/\(key)":self.users[indexPath.row].userID]
let followers = ["followers/\(key)" : uid]
ref.child("Users").child(uid!).updateChildValues(following)
ref.child("Users").child(self.users[indexPath.row].userID!).updateChildValues(followers)
//self.checkIfFollowing(indexPath: indexPath)
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = .checkmark
}
})
ref.removeAllObservers()
}
func checkIfFollowing(indexPath: IndexPath){
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
ref.child("Users").child(uid!).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (_, value) in following {
if value as? String == self.users[indexPath.row].userID {
self.tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
}
}
})
ref.removeAllObservers()
}
I'd like to populate a tableView with results from a Firebase query. I successfully get the results and store them into an array but I'm struggling with how to put those values into the table.
I'm getting back a "userID" and a "title". I'm storing those in an array [[String]]. An example of an array after getting back a value would be [["OYa7U5skUfTqaGBeOOplRLMvvFp1", "manager"],["JQ44skOblqaGBe98ll5FvXzZad", "employee"]]
How would I access an individual value, such as the userID? This is what I have so far in my cellForRowAtIndexPath function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let invite = inviteArray[indexPath.row]
print(inviteArray)
print(invite)
return cell
}
Thanks!
EDIT: added function to store snapshot in an array
var dictArray: [Dictionary<String, String>] = []
func getAlerts(){
let invitesRef = self.rootRef.child("invites")
let query = invitesRef.queryOrderedByChild("invitee").queryEqualToValue(currentUser?.uid)
query.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
guard let invitee = child.value["invitee"] as? String else{
return
}
guard let role = child.value["role"] as? String else{
return
}
self.dictArray.append(child as! Dictionary<String, String>)
print(self.dictArray)
}
})
}
I'm trying to implemented a Collection View controller and am massively failing... How do I get rid of this unwrapping error ? It occurs when I create my collectionView cell in my cellForRowAtIndexPath function.
Here is the code.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = picturesCollectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath) as? PhotoBrowserCollectionViewCell
if let photoObject = pictureObjects.objectAtIndex(indexPath.row) as? PictureElement {
let title = photoObject.title ?? "" // if photoObject.title == nil, then we return an empty string
let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(photoObject.timeStamp))
let timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject)
let author = photoObject.author ?? ""
let issueNumber = photoObject.issueNumber ?? ""
let volumeNumber = photoObject.volumeNumber ?? ""
let nodeID = photoObject.nodeID ?? 0
let imageURL = photoObject.imageURL ?? ""
cell!.request?.cancel()
// Using image cache system to make sure the table view works even when rapidly scrolling down the screen.
if let image = self.imageCache.objectForKey(imageURL) as? UIImage {
cell!.imageView.image = image
} else {
cell!.imageView.image = nil
cell!.request = Alamofire.request(.GET, imageURL).responseImage { response in
if let image = response.result.value {
self.imageCache.setObject(response.result.value!, forKey: imageURL)
cell!.imageView.image = image
} else {
}
}
}
} else {
}
return cell!
}
Also, I already tried to use a guard-let statement and it didn't work. Here it is:
guard let cell = picturesCollectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath) as? PhotoBrowserCollectionViewCell else {
print("cell could not be initialized as PhotoBrowserCollectionViewCell, hence it will be casted to another default UICollectionViewCell type")
return collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath)
}
Assuming you are using a storyboard...
Make sure you set the reuse identifier for the cell in the storyboard to match the value of PhotoBrowserCellIdentifier.
Make sure you set the class for the cell in the storyboard to PhotoBrowserCollectionViewCell.