Im retrieve data from firebese and show in tableView
In tavleViewCell I have butons : like, unLike
when I press like need to hide likeButton and show UnlikeButton
And store/remove data from firebase
Now I create metods and when I click Like -> data save to firebase and show unlikeButton BUT unlikeButton shows in another cell's(not in all cells- example if I click in 0 indexPath.row, unlikeButton shows in 0,6,12,17..) Maybe this may happen when I scroll tableView
in cell I have :
protocol TableThemeQuote {
func likeTap(_ sender: ThemesQuoteCell)
func unlikeTap(_ sender: ThemesQuoteCell)
}
class ThemesQuoteCell: UITableViewCell {
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var unlikeBtn: UIButton!
var themeDelegate: TableThemeQuote?
var index: indexPath?
#IBAction func likeBtn(_ sender: UIButton) {
sender.tag = index.row
themeDelegate?.likeTap(self)
}
#IBAction func unLikeBtn(_ sender: UIButton) {
sender.tag = index.row
themeDelegate?.unlikeTap(self)
}
}
In ViewController:
var userMarks: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
likeLike()
}
func likeLike() {
let userUID = Auth.auth().currentUser!.uid
let ref = Database.database().reference().child("users").child("\(userUID)")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if let properties = snapshot.value as? [String : AnyObject] {
if let peopleWhoLike = properties["userLikes"] as? [String : AnyObject] {
self.likeLikeL.append(peopleWhoLike)
for (id,person) in peopleWhoLike {
self.userLikeCheck.updateValue(person, forKey: id)
self.userMarks.append(person as! String)
}
}
}
})
}
In cellForRowAt indexPath:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "themesQuoteCell") as! ThemesQuoteCell
let index = indexPath.row
cell.index = index
cell.themeDelegate = self
let mark = "Темы/\(themeName)/\(quotenumber[index.row])"
if userMarks.contains(mark){
if cell.likeBtn.tag == index.row {
cell.likeBtn.isHidden = true
cell.unlikeBtn.isHidden = false
break
}
}
return cell
}
In TableThemeQuote extension:
extension ThemeQuoteVC: TableThemeQuote {
func likeTap(_ sender: ThemesQuoteCell) {
guard let tappedIndexPath = themeQuoteTableView.indexPath(for: sender) else { return }
let ref = Database.database().reference().child("Темы").child("\(self.themeName)").child("\(quotenumber[tappedIndexPath.row])")
let keyToPost = ref.childByAutoId().key!
let updateLikes: [String : Any] = ["peopleWhoLike/\(keyToPost)" : Auth.auth().currentUser!.uid]
ref.updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as? [String : AnyObject] {
if let likes = properties["peopleWhoLike"] as? [String : AnyObject] {
let count = likes.count
sender.likeLbl.text = "\(count)"
let update = ["quant" : count]
ref.updateChildValues(update)
sender.likeBtn.isHidden = true
sender.unlikeBtn.isHidden = false
sender.likeBtn.isEnabled = true
}
}
})
}
})
ref.removeAllObservers()
}
func unlikeTap(_ sender: ThemesQuoteCell) {
sender.unlikeBtn.isEnabled = false
guard let uid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("users").child("\(uid)")
guard let tappedIndexPath = themeQuoteTableView.indexPath(for: sender) else { return }
let mark = "Темы/\(themeName)/\(quotenumber[tappedIndexPath.row])"
unlikeCellBtn(index: tappedIndexPath.row, sender: sender)
if sender.unlikeBtn.tag == tappedIndexPath.row {
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if let properties = snapshot.value as? [String : AnyObject] {
if let peopleWhoLike = properties["userLikes"] as? [String : AnyObject] {
for (id,person) in peopleWhoLike {
if person as! String == mark {
ref.child("userLikes").child(id).removeValue(completionBlock: { (error, reff) in
if error == nil {
ref.observeSingleEvent(of: .value, with: { (snap) in
if let prop = snap.value as? [String : AnyObject] {
if let likes = prop["userLikes"] as? [String : AnyObject] {
let count = likes.count
ref.updateChildValues(["quantLikes" : count])
}else {
ref.updateChildValues(["quantLikes" : 0])
}
}
})
}
})
sender.likeBtn.isHidden = false
sender.unlikeBtn.isHidden = true
sender.unlikeBtn.isEnabled = true
break
}
}
}
}
})
}
ref.removeAllObservers()
}
What Im doing wrong and how can I get current tap and show/hide only that button which I press?
Cells are reused. This part of the code
if userMarks.contains(mark){
if cell.likeBtn.tag == index.row {
cell.likeBtn.isHidden = true
cell.unlikeBtn.isHidden = false
break
}
}
sets the hidden properties depending on some conditions but keeps the hidden state unchanged if the conditions are not met.
What you have to do is to add an else clause to set default values (by the way, the break statement is pointless)
if userMarks.contains(mark) && cell.likeBtn.tag == index.row {
cell.likeBtn.isHidden = true
cell.unlikeBtn.isHidden = false
} else {
cell.likeBtn.isHidden = <default value>
cell.unlikeBtn.isHidden = <default value>
}
Related
Can someone tell me which part of my code is not correct?
There two two files CompDftVC.swift and SelectDftVC.swift, when clock at tableview (CompDftVC) which segue to another tableview (SelectDftVC), which I make some selection and click 'Save' to segue back, then I got duplicate records at the initial tableview. Please see my code and screen shots as attached. Thank you for your help.
// CompDftVC.swift
import UIKit
import Firebase
class CompDftVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return compNDftLst.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = compDftTV.dequeueReusableCell(withIdentifier: "compDftTVCell", for: indexPath)
cell.textLabel?.text = compNDftLst[indexPath.row].compName
if indexPath.row % 2 == 0 {
cell.contentView.backgroundColor = .white
} else {
cell.contentView.backgroundColor = .white
}
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// pass compID to destination
tmpComponentId = compNDftLst[indexPath.row].compId!
// animate effect
compDftTV.deselectRow(at: indexPath, animated: true)
// segue triggered
performSegue(withIdentifier: "segueToSelectDft", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueToSelectDft" {
let svc = segue.destination as! SelectDftVC
svc.passInCompId = tmpComponentId
svc.passInParentEmpty = tmpParentEmpty
}
}
#IBOutlet weak var compDftTV: UITableView!
let node = currCompID+"CompDft"
let compNode = currCompID+"Comp"
let dftNode = currCompID+"Dft"
var compNDftLst = [CompNDft]()
var databaseHandle: DatabaseHandle?
var compRef: DatabaseReference!
var tmpComponentId = String()
var tmpParentEmpty = Int()
override func viewDidLoad() {
super.viewDidLoad()
compDftTV.delegate = self
compDftTV.dataSource = self
self.compNDftLst.removeAll()
// check whether the compDft table is empty
// if empty only return component with section (one row)
// if not, prepare tmp Array to display on tableview
compRef = Database.database().reference().child(node)
compRef.observe(DataEventType.value, with: { (snapshot) in
// need to consider the workshop is still empty or not?
if snapshot.childrenCount > 0 {
// CompDft table is not empty. need to retrieve comp/defect list and present them at an expandable tableview
// self.compNDftLst.removeAll()
// component already has defects. prepare the tablewview with defect list
self.tmpParentEmpty = 0
self.compRef = Database.database().reference().child(self.compNode)
self.compRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for comp in snapshot.children.allObjects as! [DataSnapshot] {
let compObj = comp.value as? [String: AnyObject]
self.tmpComponentId = comp.key
let componentName = compObj?["name"]
let component = CompNDft(compId: self.tmpComponentId as String?, compName: componentName as! String?, dftList: [""])
self.compNDftLst.append(component)
}
self.compDftTV.reloadData()
}
})
} else {
// CompDft table is empty. Only need to get componment list from Comp table
self.tmpParentEmpty = 1
self.compRef = Database.database().reference().child(self.compNode)
self.compRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
// self.compNDftLst.removeAll()
for comp in snapshot.children.allObjects as! [DataSnapshot] {
let compObj = comp.value as? [String: AnyObject]
self.tmpComponentId = comp.key
let componentName = compObj?["name"]
let component = CompNDft(compId: self.tmpComponentId as String?, compName: componentName as! String?, dftList: [""])
self.compNDftLst.append(component)
}
self.compDftTV.reloadData()
}
})
}
})
} // ViewDidLoad
} // class
// SelectDftVC.swift
import UIKit
import Firebase
class SelectDftVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tmpDisplayDefectNameList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = selectDftTV.dequeueReusableCell(withIdentifier: "dftListTVCell", for: indexPath)
cell.textLabel?.text = tmpDisplayDefectNameList[indexPath.row]
// get the componet ID and check which defect was selected and then mark them .checkmark
if tmpDefectSelected[indexPath.row] == 1 {
cell.accessoryType = .checkmark
}
if indexPath.row % 2 == 0 {
cell.contentView.backgroundColor = .white
} else {
cell.contentView.backgroundColor = .white
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.none
tmpDefectSelected[indexPath.row] = 0
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.checkmark
tmpDefectSelected[indexPath.row] = 1
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
let node = currCompID+"CompDft"
let compNode = currCompID+"Comp"
let dftNode = currCompID+"Dft"
var passInCompId = String()
var passInParentEmpty = Int()
var ref: DatabaseReference!
var ref2: DatabaseReference!
var ref3: DatabaseReference!
var ref4: DatabaseReference!
var ref5: DatabaseReference!
var ref6: DatabaseReference!
var refDft: DatabaseReference!
var selectedDft: DatabaseReference!
var selectedDftId = [String]()
var tmpDefectSelected = [Int]()
var initialTmpDefectSelected = [Int]()
var tmpDisplayDefectIdList = [String]()
var tmpDisplayDefectNameList = [String]()
var tmpSelectedDftID = [String]()
var tmpSelectedDftName = [String]()
var hasData = Int()
#IBOutlet weak var selectDftTV: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
selectDftTV.delegate = self
selectDftTV.dataSource = self
tmpDisplayDefectIdList.removeAll() // defect ID
tmpDisplayDefectNameList.removeAll() // defect Name
selectedDftId.removeAll() // Selected defect ID
tmpDefectSelected.removeAll() // Integer array for checkmark
initialTmpDefectSelected.removeAll() // Initial Integer array for checkmark (to be updated when populating tableview
if passInParentEmpty == 1 {
// component doesn't contain any defect
ref = Database.database().reference().child(dftNode)
ref.observe(DataEventType.value, with: { (snapshot) in
// need to consider the workshop is still empty or not?
if snapshot.childrenCount > 0 {
// get defect list
for defect in snapshot.children.allObjects as! [DataSnapshot] {
let defectObj = defect.value as? [String: AnyObject]
let defectId = defect.key
let defectName = defectObj?["name"]
self.tmpDisplayDefectIdList.append(defectId)
self.tmpDisplayDefectNameList.append(defectName as! String)
self.tmpDefectSelected.append(0)
self.initialTmpDefectSelected.append(0)
}
self.prepareSelectedDftList()
}
})
} else {
// the component already contain defect
ref = Database.database().reference().child(dftNode)
ref.observe(DataEventType.value, with: { (snapshot) in
// need to consider the workshop is still empty or not?
if snapshot.childrenCount > 0 {
// get defect list
for defect in snapshot.children.allObjects as! [DataSnapshot] {
let defectObj = defect.value as? [String: AnyObject]
let defectId = defect.key
let defectName = defectObj?["name"]
self.tmpDisplayDefectIdList.append(defectId)
self.tmpDisplayDefectNameList.append(defectName as! String)
self.tmpDefectSelected.append(0)
self.initialTmpDefectSelected.append(0)
}
}
self.ref2 = Database.database().reference().child(self.node)
self.ref2.queryOrderedByKey().queryEqual(toValue: self.passInCompId).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
self.hasData = 1
} else {
self.hasData = 0
}
if self.hasData == 1 {
self.ref3 = Database.database().reference().child(self.node)
self.ref3.queryOrderedByKey().queryEqual(toValue: self.passInCompId).observe(.childAdded, with: { (snapshot) in
if snapshot.childrenCount > 0 {
// selected component has defects
for child in snapshot.children {
let snap = child as! DataSnapshot
let tmpkey = snap.key as String?
self.selectedDftId.append(tmpkey!)
}
}
self.prepareSelectedDftList()
})
} else {
self.prepareSelectedDftList()
}
})
// self.ref2.removeAllObservers()
}) // first DBquery
// self.ref.removeAllObservers()
}
} // viewDidLoad
#IBAction func saveSelectedDft(_ sender: Any) {
// prepare the array of defects to be marked for component which can override the current one
if tmpDisplayDefectNameList.count > 0 {
for i in 0...tmpDisplayDefectNameList.count - 1 {
if tmpDefectSelected[i] == 1 {
// prepare selected defect ID and name array
tmpSelectedDftID.append(tmpDisplayDefectIdList[i])
tmpSelectedDftName.append(tmpDisplayDefectNameList[i])
}
}
markUsed4CompNDft()
// refDft.removeAllObservers()
}
// segue back and refresh tmp array (may be) and tableview
self.navigationController?.popViewController(animated: true)
} // saveSelectedDftBtn
func prepareSelectedDftList() {
if selectedDftId.count == 0 {
selectDftTV.reloadData()
} else if selectedDftId.count == 1 {
for i in 0...tmpDisplayDefectIdList.count - 1 {
if tmpDisplayDefectIdList[i] == selectedDftId[0] {
tmpDefectSelected[i] = 1
initialTmpDefectSelected[i] = 1
}
}
selectDftTV.reloadData()
} else if selectedDftId.count > 1 {
for i in 0...tmpDisplayDefectIdList.count - 1 {
for j in 0...selectedDftId.count - 1 {
if tmpDisplayDefectIdList[i] == selectedDftId[j] {
tmpDefectSelected[i] = 1
initialTmpDefectSelected[i] = 1
}
}
}
// self.ref.removeAllObservers()
selectDftTV.reloadData()
}
} // prepareSelectedDftList
func resetComponentUsedToZero() {
var clearCompId = String()
var clearCompName = String()
ref5 = Database.database().reference()
ref5.child(compNode).queryOrderedByKey().queryEqual(toValue: passInCompId).observe(.childAdded, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
if key == "id" {
clearCompId = snap.value as! String
}
if key == "name" {
clearCompName = snap.value as! String
}
}
let updatedComponent = [
"id" : clearCompId,
"name" : clearCompName,
"date" : utcDate(),
"time" : utcTime(),
"updated" : currUID,
"used" : 0
] as [String: Any]
self.ref5.child(self.compNode).child(self.passInCompId).setValue(updatedComponent)
})
ref5.removeAllObservers()
}
func setComponentUsedToOne() {
var clearCompId = String()
var clearCompName = String()
ref5 = Database.database().reference()
ref5.child(compNode).queryOrderedByKey().queryEqual(toValue: passInCompId).observe(.childAdded, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
if key == "id" {
clearCompId = snap.value as! String
}
if key == "name" {
clearCompName = snap.value as! String
}
}
let updatedComponent = [
"id" : clearCompId,
"name" : clearCompName,
"date" : utcDate(),
"time" : utcTime(),
"updated" : currUID,
"used" : 1
] as [String: Any]
self.ref5.child(self.compNode).child(self.passInCompId).setValue(updatedComponent)
})
ref5.removeAllObservers()
}
func markUsed4CompNDft() {
ref6 = Database.database().reference()
// check any changes made
if initialTmpDefectSelected != tmpDefectSelected {
if tmpSelectedDftID.count == 0 {
// clear all defect
ref6.child(node).child(passInCompId).setValue(nil)
// should change component 'used' to 0
resetComponentUsedToZero()
} else {
ref6.child(node).child(passInCompId).setValue(nil)
for i in 0...tmpSelectedDftID.count - 1 {
ref6.child(node).child(passInCompId).child(tmpSelectedDftID[i]).setValue(tmpSelectedDftName[i])
}
// mark component used = 1
setComponentUsedToOne()
// mark defect(s) used = 1
if tmpSelectedDftID.count > 0 {
// ref = Database.database().reference().child(dftNode)
ref4 = Database.database().reference().child(dftNode)
for i in 0...tmpSelectedDftID.count - 1 {
ref4.queryOrderedByKey().queryEqual(toValue: tmpSelectedDftID[i]).observe(.childAdded, with: { (sp) in
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["date" : utcDate()])
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["time" : utcTime()])
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["updated" : currUID])
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["used" : 1])
})
}
}
// ref4.removeAllObservers()
}
}
} // markUsed4CompNDft
} // class
I'm trying to fetch specific user messages from Firebase Real-Time database.I want to fill chatLogTableView with the user messages.I'm fetching user messages datas from firebase with fetchCurrentUserMessages() function.
In this function :
self.messagesDatas.append(message)
print(self.messagesDatas)
When i try to print the self.messagesDatas its okay.But when the tableview trying to retrieve self.messagesDatas.count for numberOfRowsInSection i'm getting 0 count.Why this is happening ?
Here is my code :
import UIKit
import Firebase
class ChatLogCustomCell: UITableViewCell {
#IBOutlet weak var leftLabel: UILabel!
#IBOutlet weak var rightLabel: UILabel!
}
class ChatLogViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var chatLogTableView: UITableView!
#IBOutlet weak var messageTextField: UITextField! // bu kismi elle yazman gerekebilir cunku xcode bu messagetextfield i sanki view in icinde oldugu icin table view icinde algilayamayabilir
#IBOutlet weak var backButton: UINavigationItem!
var usersDataFromChatScreen = [User]()
var selectedUserToIDFromChatScreen = ""
var isTeacherFromChatScreen = ""
var messagesDatas = [Message]()
var messagesDictionary = [String : Message]()
override func viewDidLoad() {
super.viewDidLoad()
chatLogTableView.delegate = self
chatLogTableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messagesDatas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = chatLogTableView.dequeueReusableCell(withIdentifier: "chatLogCell") as! ChatLogCustomCell
let message = messagesDatas[indexPath.row]
cell.leftLabel.text = message.text
cell.rightLabel.text = message.text
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.0
}
#IBAction func sendButtonTouchOnInside(_ sender: Any) {
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toID = selectedUserToIDFromChatScreen
Auth.auth().addStateDidChangeListener { (auth, user) in
let fromID = auth.currentUser?.uid
let values = ["text" : self.messageTextField.text, "toID" : toID, "fromID" : fromID] as [String : Any]
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print(error ?? "")
return
}
guard let messageID = childRef.key else { return }
let userMessagesRef = Database.database().reference().child("user-messages").child(fromID!).child(messageID)
userMessagesRef.setValue(1)
let recipientUserMessagesRef = Database.database().reference().child("user-messages").child(toID).child(messageID)
recipientUserMessagesRef.setValue(1)
}
}
}
func fetchCurrentUserMessages() {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
if self.isTeacherFromChatScreen == "no" {
let ref = Database.database().reference().child("user-messages").child(uid)
ref.observe(.childAdded) { (snapshot) in
let messageID = snapshot.key
let messagesRef = Database.database().reference().child("messages").child(messageID)
messagesRef.observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
let message = Message()
let toID = dictionary["toID"] as? String ?? "toID not found"
let messageText = dictionary["text"] as? String ?? "Text not found"
let fromID = dictionary["fromID"] as? String ?? "fromID not found"
message.toID = toID
message.text = messageText
message.fromID = fromID
self.messagesDatas.append(message)
print(self.messagesDatas.count)
}
}
}
else {
}
}
}
Reload your tableview when you get data from server
func fetchCurrentUserMessages() {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
if self.isTeacherFromChatScreen == "no" {
let ref = Database.database().reference().child("user-messages").child(uid)
ref.observe(.childAdded) { (snapshot) in
let messageID = snapshot.key
let messagesRef = Database.database().reference().child("messages").child(messageID)
messagesRef.observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
let message = Message()
let toID = dictionary["toID"] as? String ?? "toID not found"
let messageText = dictionary["text"] as? String ?? "Text not found"
let fromID = dictionary["fromID"] as? String ?? "fromID not found"
message.toID = toID
message.text = messageText
message.fromID = fromID
self.messagesDatas.append(message)
print(self.messagesDatas.count)
DispatchQueue.main.async {
chatLogTableView.reloadData()
}
}
}
}
else {
}
}
I have a tableview that is being populated with who a user is following. Problem is that I need to pass that cells data to "var otherUser: NSDictionary!" but because I am populating the cell using a data structure file called "Information" I get this error - "Cannot assign value of type 'Information' to type 'NSDictionary?'" in the prepareForSegue. I am unsure if I can repackage the information I need into a NSDictionary so I can successfully do a data pass. I just don't know if this is a easy solution or an actual problem because of my ignorance.
Following TableViewController Code
import UIKit
import Firebase
class BusinessFollowing: UITableViewController {
#IBOutlet var noDataView: UIView!
#IBOutlet var followingTableView: UITableView!
var yourFollowing = [Information]()
var listFollowing = [NSDictionary?]()
var databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
var loggedInUser = Auth.auth().currentUser
var loggedInUserData:NSDictionary?
var following = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.followingTableView.backgroundView = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.followingTableView.reloadData()
self.yourFollowing.removeAll()
self.following.removeAll()
getFollowingData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "following" {
// gotta check if we're currently searching
if let indexPath = followingTableView.indexPathForSelectedRow {
let user = self.yourFollowing[indexPath.row]
let controller = segue.destination as? ExploreBusinessProfileSwitchView
controller?.otherUser = user
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.yourFollowing.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BusinessFollowingCell
let following = yourFollowing[indexPath.row]
let businessName = following.businessName
let businessStreet = following.businessStreet
let businessCity = following.businessCity
let businessState = following.businessState
cell.businessName.text = businessName
cell.businessStreet.text = businessStreet
cell.businessCity.text = businessCity
cell.businessState.text = businessState
// cell.businessName?.text = self.listFollowing[indexPath.row]?["businessName"] as? String
// cell.businessStreet?.text = self.listFollowing[indexPath.row]?["businessStreet"] as? String
// cell.businessCity?.text = self.listFollowing[indexPath.row]?["businessCity"] as? String
// cell.businessState?.text = self.listFollowing[indexPath.row]?["businessState"] as? String
return cell
}
func getFollowingData() {
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
Database.database().reference().child("Businesses").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
databaseRef.child("Businesses").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
for (_, value) in users {
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
self.yourFollowing.append(Information(uid: uid, businessName: name, businessStreet: address, businessCity: city, businessState: state))
}
self.followingTableView.backgroundView = nil
self.followingTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
self.followingTableView.backgroundView = self.noDataView
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}
"Information" Data Structure File
import UIKit
class Information {
var uid: String
var businessName: String
var businessStreet: String
var businessCity: String
var businessState: String
init(uid: String, businessName: String, businessStreet: String, businessCity: String, businessState: String){
self.uid = uid
self.businessName = businessName
self.businessStreet = businessStreet
self.businessCity = businessCity
self.businessState = businessState
}
}
The error is pretty clear.
user in ExploreBusinessProfileSwitchView is obviously declared as NSDictionary, declare it as Information.
By the way don't use NSArray / NSDictionary in Swift. Use native types.
I am doing a call into a child "following" and am seeing if the logged-in user's UID is there and has a child of another user which the logged-in user is following.
I am printing who the logged-in user is following into a tableview. The first problem is my code, because I know it is bad practice to have two firebase calls within each other so I need someone to teach me a better method. Because of the poor code, when I go unfollow the other user and come back to the tab where the logged-in users list of who they are following is displayed it shows this (image below). When the logged-in user is following nobody it should just display the "Sorry!" text, yet still keeps who the user was following. Need someone to teach me a better method for doing this type of firebase call. Code and a firebase JSON stack image are below... In the firebase JSON stack image, the expanded UID is the logged-in user and the child in is the other user the logged-in user is following. I need a better way to call and extract this information, I am just ignorant of how-to.
func getFollowingData() {
Database.database().reference().child("following").child(uid!).observe(DataEventType.value, with: { (snapshot) in
if snapshot.exists() {
print("Got Snapshot")
Database.database().reference().child("following").child(self.uid!).observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
print(snapshot)
let snapshot = snapshot.value as? NSDictionary
self.listFollowing.append(snapshot)
self.followingTableView.insertRows(at: [IndexPath(row:self.listFollowing.count-1,section:0)], with: UITableViewRowAnimation.automatic)
self.followingTableView.backgroundView = nil
}
})
} else {
print("No Snapshot")
self.followingTableView.backgroundView = self.noDataView
}
})
}
Figured it out, just needed to do it how I did it before on other feeds.
import UIKit
import Firebase
class BusinessFollowing: UITableViewController {
#IBOutlet var noDataView: UIView!
#IBOutlet var followingTableView: UITableView!
var yourFollowing = [Information]()
var listFollowing = [NSDictionary?]()
var databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
var loggedInUser = Auth.auth().currentUser
var loggedInUserData:NSDictionary?
var following = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.followingTableView.backgroundView = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.followingTableView.reloadData()
self.yourFollowing.removeAll()
self.following.removeAll()
getFollowingData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "following" {
// gotta check if we're currently searching
if let indexPath = followingTableView.indexPathForSelectedRow {
let user = listFollowing[indexPath.row]
let controller = segue.destination as? ExploreBusinessProfileSwitchView
controller?.otherUser = user
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.yourFollowing.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BusinessFollowingCell
let following = yourFollowing[indexPath.row]
let businessName = following.businessName
let businessStreet = following.businessStreet
let businessCity = following.businessCity
let businessState = following.businessState
cell.businessName.text = businessName
cell.businessStreet.text = businessStreet
cell.businessCity.text = businessCity
cell.businessState.text = businessState
// cell.businessName?.text = self.listFollowing[indexPath.row]?["businessName"] as? String
// cell.businessStreet?.text = self.listFollowing[indexPath.row]?["businessStreet"] as? String
// cell.businessCity?.text = self.listFollowing[indexPath.row]?["businessCity"] as? String
// cell.businessState?.text = self.listFollowing[indexPath.row]?["businessState"] as? String
return cell
}
func getFollowingData() {
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
Database.database().reference().child("Businesses").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
databaseRef.child("Businesses").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
for (_, value) in users {
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
self.yourFollowing.append(Information(uid: uid, businessName: name, businessStreet: address, businessCity: city, businessState: state))
}
self.followingTableView.backgroundView = nil
self.followingTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
self.followingTableView.backgroundView = self.noDataView
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}
I am trying to get an array of strings from JSON and I'm trying to figure out how to deal with it if the returned array is empty. In some cases, the returned value is [] and for other cases, the array contains string values. It is crashing because of unexpectedly finding a nil value.
For clarification, the gyms array is passed from another class and everything here works without the code for the images.
Here is my relevant code:
var gyms = [AnyObject]()
var imageArrays = [[String]?]()
In viewDidLoad()
getGymImages()
Methods to get JSON data:
func getGymImages() {
var index = 0
for dictionary in gyms {
let id = dictionary["id"] as! String
let urlString = String("https://gyminyapp.azurewebsites.net/api/GymImage/\(id)")
let url = NSURL(string: urlString)
let data = NSData(contentsOfURL: url!)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
for imageArray in json as! [AnyObject] {
imageArrays.append((imageArray as? [String])!)
}
} catch {
print("Error")
}
index += 1
}
addImagesToGyms()
}
func addImagesToGyms() {
var index = 0;
for array in imageArrays {
var dictionary = gyms[index] as! [String:AnyObject]
dictionary["images"] = array
gyms[index] = dictionary
index += 1
}
}
In cellForRowAtIndexPath()
let gymImages = dictionary["images"] as! [String]
if gymImages.count > 0 {
let firstImageURL = gymImages[0] as String
cell.cellImageView.sd_setImageWithURL(NSURL(string: firstImageURL))
}
EDIT: I was asked to show more of the file, so here it is.
import UIKit
class GymListTableViewController: UITableViewController {
var gyms = [AnyObject]()
var gymName: String?
var gymAddress: String?
var gymPhoneNumber: String?
var gymWebsite: String?
var gymID: String?
var gymLatitude: String?
var gymLongitude: String?
var maxDistance: Double?
var myLocation: CLLocation?
var milesArray = [Double]()
var imageArrays = [[String]?]()
var segmentedControl: UISegmentedControl?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Gyms"
tableView.registerNib(UINib(nibName: "GymListTableViewCell", bundle: nil), forCellReuseIdentifier: "gymCell")
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "waypoint_map"), style: .Done, target: self, action: #selector(showMapView))
self.navigationItem.rightBarButtonItem?.tintColor = BarItems.greenTintColor
segmentedControl = UISegmentedControl(items: ["A-Z", "Z-A", "Rating", "Distance"])
segmentedControl?.sizeToFit()
segmentedControl?.selectedSegmentIndex = 0
segmentedControl!.setTitleTextAttributes([NSFontAttributeName: UIFont(name:"Helvetica-Light", size: 15)!],
forState: UIControlState.Normal)
segmentedControl?.addTarget(self, action: #selector(changeSelectedSegmentIndex), forControlEvents: .ValueChanged)
self.navigationItem.titleView = segmentedControl
sortAlphabetically()
let backgroundImage = UIImage(named: "gray_background")
let backgroundImageView = UIImageView(image: backgroundImage)
tableView.backgroundView = backgroundImageView
addDistancesToGyms()
getGymImages()
// geocodeAddresses()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func addDistancesToGyms() {
var index = 0
for distance in milesArray {
var dictionary = gyms[index] as! [String:AnyObject]
dictionary["distance"] = distance
gyms[index] = dictionary
index += 1
}
}
func getGymImages() {
var index = 0
for dictionary in gyms {
let id = dictionary["id"] as! String
let urlString = String("https://gyminyapp.azurewebsites.net/api/GymImage/\(id)")
let url = NSURL(string: urlString)
let data = NSData(contentsOfURL: url!)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
for imageArray in json as! [AnyObject] {
imageArrays.append((imageArray as? [String])!)
}
} catch {
print("Error")
}
index += 1
}
addImagesToGyms()
}
func addImagesToGyms() {
var index = 0;
for array in imageArrays {
var dictionary = gyms[index] as! [String:AnyObject]
dictionary["images"] = array
gyms[index] = dictionary
index += 1
}
}
func changeSelectedSegmentIndex() {
let segmentTouched = segmentedControl?.selectedSegmentIndex
if segmentTouched == 0 {
sortAlphabetically()
} else if segmentTouched == 1 {
sortReverseAlphabetically()
} else if segmentTouched == 2 {
sortByRatingAscending()
} else {
sortByDistanceAscending()
}
}
func sortAlphabetically() {
gyms.sortInPlace{
(($0 as! Dictionary<String, AnyObject>)["name"] as? String) < (($1 as! Dictionary<String, AnyObject>)["name"] as? String)
}
tableView.reloadData()
}
func sortReverseAlphabetically() {
gyms.sortInPlace{
(($0 as! Dictionary<String, AnyObject>)["name"] as? String) > (($1 as! Dictionary<String, AnyObject>)["name"] as? String)
}
tableView.reloadData()
}
func sortByRatingAscending() {
// TODO: Sort by rating
}
func sortByDistanceAscending() {
gyms.sortInPlace{
(($0 as! Dictionary<String, AnyObject>)["distance"] as? Double) < (($1 as! Dictionary<String, AnyObject>)["distance"] as? Double)
}
tableView.reloadData()
}
func showMapView() {
self.performSegueWithIdentifier("displayMapSegue", sender: self.navigationController)
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gyms.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 131
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("gymCell", forIndexPath: indexPath) as! GymListTableViewCell
let dictionary = gyms[indexPath.row]
let addressDictionary = dictionary["address"]
let street = addressDictionary!!["streetAddress"] as! String
let city = addressDictionary!!["city"] as! String
let state = addressDictionary!!["state"] as! String
let zipInt = addressDictionary!!["zipCode"] as! Int
let zipCode = String(zipInt)
let addressString = String("\(street) " + "\(city), " + "\(state) " + "\(zipCode)")
cell.backgroundColor = UIColor.clearColor()
cell.gymNameLabel.text = dictionary["name"] as? String
cell.gymAddressLabel.text = addressString
let miles = dictionary["distance"] as! Double
let milesString = String(format: "%.1f miles", miles)
let milesLabelString = milesString
cell.milesLabel.text = milesLabelString
let gymImages = dictionary["images"] as! [String]
if gymImages.count > 0 {
let firstImageURL = gymImages[0] as String
cell.cellImageView.sd_setImageWithURL(NSURL(string: firstImageURL))
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let dictionary = gyms[indexPath.row]
if dictionary["name"] as? String != nil {
self.gymName = dictionary["name"] as? String
}
let cell = tableView.cellForRowAtIndexPath(indexPath) as! GymListTableViewCell
self.gymAddress = cell.gymAddressLabel.text
if dictionary["phone"] as? String != nil {
self.gymPhoneNumber = dictionary["phone"] as? String
}
if dictionary["website"] as? String != nil {
self.gymWebsite = dictionary["website"] as? String
}
if dictionary["id"] as? String != nil {
self.gymID = dictionary["id"] as? String
}
if dictionary["latitude"] as? String != nil {
self.gymLatitude = dictionary["latitude"] as? String
}
if dictionary["longitude"] as? String != nil {
self.gymLongitude = dictionary["longitude"] as? String
}
self.performSegueWithIdentifier("detailFromListSegue", sender: self.navigationController)
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailFromListSegue" {
let gymDetailVC = segue.destinationViewController as! DetailTableViewController
if self.gymName != nil {
gymDetailVC.gymName = self.gymName
} else {
gymDetailVC.gymName = nil
}
if self.gymAddress != nil {
gymDetailVC.gymAddress = self.gymAddress
} else {
gymDetailVC.gymAddress = nil
}
if self.gymPhoneNumber != nil {
gymDetailVC.gymPhoneNumber = self.gymPhoneNumber
} else {
gymDetailVC.gymPhoneNumber = nil
}
if self.gymWebsite != nil {
gymDetailVC.gymWebsite = self.gymWebsite
} else {
gymDetailVC.gymWebsite = nil
}
if self.gymID != nil {
gymDetailVC.gymID = self.gymID!
} else {
// gymDetailVC.gymID = nil
}
if self.gymLatitude != nil {
gymDetailVC.gymLatitude = self.gymLatitude!
}
if self.gymLongitude != nil {
gymDetailVC.gymLongitude = self.gymLongitude!
}
}
}
}
numberOfRowsInSection should be returning gymImages.count if it isn't already.
Then as a safeguard you can always do
if indexPath.row < gymImages.count {
}
before accessing the content in cellForRowAtIndexPath