Swift: Trying to set a ui button title using a completion handler - ios

I am trying to use data from firebase to populate buttons on the UI. Everything works as expected except the button title is not updating. Any ideas on how to fix this?
#IBAction func addNewTapped(_ sender: Any) {
readOneDay2(lastMonday(trackerDate), completion: { message in
let lastHourRead = message
print(message)
self.lastHour1.setTitle(lastHourRead, for: UIControl.State.application)
})
}
func readOneDay2 (_ mydate: Date, completion: #escaping (_ message: String) -> Void){
var db: DatabaseReference!
db = Database.database().reference()
var totalComb: Double = 0.0
let userID = Auth.auth().currentUser?.uid
db.child("TimesheetData").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let dict = snapshot.value as? NSDictionary
for (key, value) in dict! {
let myvalue = value as? [String: Any]
let compDate: String = myvalue!["timeSheetDate"]! as! String
if compDate == dateStringer(mydate) {
let sHours: String = myvalue!["hours"]! as! String
let sOverTime: String = myvalue!["overTime"]! as! String
let sDoubleTime: String = myvalue!["doubleTime"]! as! String
let dHours: Double = Double(sHours)!
let dOverTime: Double = Double(sOverTime)!
let dDoubleTime: Double = Double(sDoubleTime)!
totalComb = totalComb + dHours + dOverTime + dDoubleTime
print(key)
}
}
print("First Sum " + String(totalComb))
DispatchQueue.main.async {
completion(String(totalComb))
}
}) { (error) in
print(error.localizedDescription)
}
}

As per #Virender said,
Change UIControl.State.application to UIControl.State.normal
DispatchQueue.main.async {
self.lastHour1.setTitle(lastHourRead, for:.normal)
}

Related

What is the solution to the error "The value of type [User] 'cannot be converted to the expected argument type' User"?

In my page named service, xcode points to the user and gives an error. but it doesn't work. What do you think should I change?
my user is already optional. I think it is an index problem but I don't know how to solve it I would appreciate it if you could help.where do you think the problem
message.swift
import Firebase
struct Message {
let text: String
let toId: String
let fromId: String
var timestamp: Timestamp!
var user: User?
let isFromCurrentUser :Bool
init(dictionary: [String: Any]) {
self.text = dictionary["text"] as? String ?? ""
self.toId = dictionary["toId"] as? String ?? ""
self.fromId = dictionary["fromId"] as? String ?? ""
self.timestamp = dictionary["timestamp"] as? Timestamp ?? Timestamp(date: Date())
self.isFromCurrentUser = fromId == Auth.auth().currentUser?.uid
}
}
struct Conversation {
let user: User
let message : Message
}
Service.Swift
import Firebase
struct Service {
static func fetchUsers (completion: #escaping([User]) -> Void) {
var users = [User] ()
COLLECTION_USERS.getDocuments { (snapshot, error) in
snapshot?.documents.forEach({ (document) in
let dictionary = document.data()
let user = User(dictionary: dictionary)
users.append(user)
completion(users)
})
}
}
static func fetchUser(widhtUid uid: String, completion:#escaping([User]) ->Void) {
COLLECTION_USERS.document(uid).getDocument { (snapshot, error) in
guard let dictionary = snapshot?.data() else {return}
let user = User(dictionary: dictionary)
completion(user)
}
}
static func fetchConversations (completion: #escaping([Conversation]) ->Void) {
var conversations = [Conversation]()
guard let uid = Auth.auth().currentUser?.uid else {return}
let query = COLLECTION_MESSAGES.document(uid).collection("recent-messages").order(by: "timestamp")
query.addSnapshotListener { (snapshot, error) in
snapshot?.documentChanges.forEach({ change in
let dictionary = change.document.data()
let message = Message(dictionary: dictionary)
self.fetchUser(widhtUid: message.toId) { user in
let conversation = Conversation(user:user, message: message)
conversations.append(conversation)
completion(conversations)
}
})
}
}
static func fetchMessages (forUser user: User, completion: #escaping([Message])-> Void) {
var messages = [Message]()
guard let currentUid = Auth.auth().currentUser?.uid else {return}
let query = COLLECTION_MESSAGES.document(currentUid).collection(user.uid).order(by: "timestamp")
query.addSnapshotListener{(snapshot,error) in
snapshot?.documentChanges.forEach({ change in
if change.type == .added {
let dictionary = change.document.data ()
messages.append(Message(dictionary: dictionary))
completion(messages)
}
})
}
}
static func uploadMessage(message: String, to user: User, completion: ((Error?)->Void)?) {
guard let currentUid = Auth.auth().currentUser?.uid else {return}
let data = ["text": message,
"fromId": currentUid,
"toId": user.uid,
"timestamp" : Timestamp(date: Date())] as [String : Any]
COLLECTION_MESSAGES.document(currentUid).collection(user.uid).addDocument(data:data) { _ in
COLLECTION_MESSAGES.document(user.uid).collection(currentUid).addDocument(data:data,completion:completion)
COLLECTION_MESSAGES.document(currentUid).collection("recent- messages").document(user.uid).setData(data)
COLLECTION_MESSAGES.document(user.uid).collection("recent- messages").document(currentUid).setData(data)
}
}
}
In this method:
static func fetchUser(widhtUid uid: String, completion:#escaping ([User]) -> Void)
The completion closure's parameter should be a User, not an array of users - [User].
Xcode should point you to the line where this error happens...
Anyway, here
static func fetchUser(widhtUid uid: String, completion:#escaping([User]) ->Void) {
COLLECTION_USERS.document(uid).getDocument { (snapshot, error) in
guard let dictionary = snapshot?.data() else {return}
let user = User(dictionary: dictionary)
completion(user)
}
}
Your completion:#escaping([User]) ->Void) expects an array [User] , but you invoke it with just one User object here completion(user)

Issues retrieving notificationTypes from Firebase

I have a bit of a lengthy question, So I apologize in advance I will try to illustrate this to the best of my abilities. I am trying to establish a notifications view controller that calls different types of data from Firebase and sets different notification types.
In the image above, this is how the cells should look when a user sends a notification to firebase. The user associated with that specific notification type as called and posted onto the screen.
In the firebase structure, We see that all of the information Stored is saved under the UID of the user in the first picture and is set under that specific users notification to show who is sending them a notification which is correct. These users names and images show perfectly as well as the image on the right.
The code I use to save this information is below,
fileprivate func saveSwipeToDataBase(didLike: Any) {
let swipeDate = Int(NSDate().timeIntervalSince1970)
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let cardUID = topCardView?.cardViewModel.uid else { return }
let documentData = ["workerId": uid,
"didLike": didLike,
"checked": 0,
"Swipe Date": swipeDate,
"type": SWIPE_INT_VALUE,
"posterId" : cardUID] as [String : Any]
self.postJobNotificationsIntoDatabseWithUID(uid: cardUID, values: documentData as [String : AnyObject])
}
private func postJobNotificationsIntoDatabseWithUID(uid: String, values: [String: AnyObject]) {
let ref = Database.database().reference(fromURL: "https://oddjobs-b131f.firebaseio.com/")
let usersReference = ref.child("notifications").child(uid).childByAutoId()
usersReference.setValue(values, withCompletionBlock: { (err, ref) in
if err != nil {
print("error saving data into firebase")
return
}
})
}
And below is how I retrieve this information and store it onto the Notifications View controller.
func fetchNotifications() {
guard let currentUID = Auth.auth().currentUser?.uid else { return }
NOTIFICATIONS_REF.child(currentUID).observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? Dictionary<String, AnyObject> else { return }
print(dictionary)
for (_, postingRawData) in dictionary {
guard let postingDictionary = postingRawData as? Dictionary<String, AnyObject> else { continue }
guard let uid = postingDictionary["workerId"] as? String else { continue }
Database.fetchUser(with: uid, completion: { (user) in
if let postId = postingDictionary["posterId"] as? String {
Database.fetchPoster(with: postId, completion: {(poster) in
let notification = userNotifications(user: user, poster: poster, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
})
} else {
let notification = userNotifications(user: user, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
}
})
}
}
}
Now that I got the correct way to setup up and show out of the way, I will show my enum and how I am distinguishing the different types of calls from firebase.
class userNotifications {
// MARK: - establish notificationTypes
enum NotificationType: Int, Printable {
case swipe
case accepted
case confirmed
case completed
case pay
var description: String {
switch self {
case .swipe: return " swiped on your Job "
case .accepted: return " accepted you to complete the job, "
case .confirmed: return " confirmed the job"
case .completed: return " completed the job"
case .pay: return " pay for completed"
}
}
init(index: Int) {
switch index {
case 0: self = .swipe
case 1: self = .accepted
case 2: self = .confirmed
case 3: self = .completed
case 4: self = .pay
default: self = .swipe
}
}
}
// MARK: - access firebaseData
var creationDate: Date!
var timeDate: Date!
var uid: String!
var fromId: String?
var workerId: String?
var user: User!
var poster: Poster!
var type: Int?
var notificationType: NotificationType!
var didCheck = false
init(user: User? = nil, poster: Poster? = nil, dictionary: Dictionary<String, AnyObject>) {
self.user = user
if let poster = poster {
self.poster = poster
}
if let swipeDate = dictionary["Swipe Date"] as? Double {
self.creationDate = Date(timeIntervalSince1970: swipeDate)
}
if let createDate = dictionary["creationDate"] as? Double {
self.creationDate = Date(timeIntervalSince1970: createDate)
}
if let swipeDate = dictionary["time&date"] as? Double {
self.timeDate = Date(timeIntervalSince1970: swipeDate)
}
if let type = dictionary["type"] as? Int {
self.notificationType = NotificationType(index: type)
}
if let uid = dictionary["uid"] as? String {
self.uid = uid
}
if let fromId = dictionary["fromId"] as? String {
self.fromId = fromId
}
if let workerId = dictionary["workerUID"] as? String {
self.workerId = workerId
}
if let checked = dictionary["checked"] as? Int {
if checked == 0 {
self.didCheck = false
} else {
self.didCheck = true
}
}
}
}
Above is the different types of notifications to be set.
Now, My issue is If I call a different notification type, such as .accepted, the information calls in a very different way.
The image above seems correct, However, the name and image are incorrect. it should be from the user ZacheryWilcox instead of Cjbwjdhbe. the user Cjbwjdhbe is the current user and the user who should be receing a notification from Zacherywilcox. not from itself.
In firebase, the information is saved as
the code I use to save this information is below
var workerUser: User? {
didSet {
let name = workerUser?.name
workerNameLabel.text = name
let workersUID = workerUser?.uid
workerNameLabel.text = name
guard let profileImage = workerUser?.profileImageUrl else { return }
workerImageView.loadImageUsingCacheWithUrlString(profileImage)
}
}
func saveUserData() {
let workUser = self.workerUser
guard let uid = Auth.auth().currentUser?.uid else { return }
let workerId = workUser?.uid
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else { return }
let user = User(dictionary: dictionary as [String : AnyObject])
workUser?.uid = snapshot.key
self.datePicker.datePickerMode = UIDatePicker.Mode.date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM dd yyyy/ hh:mm a"
let selectedDate = dateFormatter.string(from: self.datePicker.date)
let creationDate = Int(NSDate().timeIntervalSince1970)
print(selectedDate)
let docData: [String: Any] = [
"workerId": workerId!,
"time&date": selectedDate,
"posterId" : uid,
"creationDate": creationDate,
"location": user.address!,
"type": 1,
"jobPost": "someUIDString",
"checked": 0,
]
self.postJobNotificationsIntoDatabseWithUID(uid: workerId!, values: docData as [String : AnyObject])
}, withCancel: { (err) in
print("attempting to load information")
})
print("Finished saving user info")
self.dismiss(animated: true, completion: {
print("Dismissal complete")
})
}
private func postJobNotificationsIntoDatabseWithUID(uid: String, values: [String: AnyObject]) {
let ref = Database.database().reference(fromURL: "https://oddjobs-b131f.firebaseio.com/")
let usersReference = ref.child("notifications").child(uid).childByAutoId()
usersReference.setValue(values, withCompletionBlock: { (err, ref) in
if err != nil {
print("error saving data into firebase")
return
}
})
}
When the type .accepted is being used to differentiate what notificationType is being called, the user who sent the notification is not being set correctly and I have no idea what is the reasoning behind this. The correct user that is sending this information over is Zacherywilcox, and that users image and name should be set to the user's notification screen. not the user Cjbe... I was wondering if anyone could help me fix these issues. Thank you in advance. I'm starting to think that the way I am saving the users information when accepting the user is incorrect.
When I am fetchingNotifications(), is it possible that since calling
guard let uid = postingDictionary["workerId"] as? String else { continue }
Database.fetchUser(with: uid, completion: { (user) in
if let postId = postingDictionary["posterId"] as? String {
has an effect on whats going on? if so, Is there a way to differentiate between what notificationType is being called and fetch what notifications has been called with their respective users?
Just update your code to:
func fetchNotifications() {
guard let currentUID = Auth.auth().currentUser?.uid else { return }
NOTIFICATIONS_REF.child(currentUID).observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? Dictionary<String, AnyObject> else { return }
print(dictionary)
let notificationId = snapshot.key
for (_, postingRawData) in dictionary {
guard let postingDictionary = postingRawData as? Dictionary<String, AnyObject> else { continue }
guard let type = postingDictionary["type"] as? Int else { continue }
guard let uid = (type == userNotifications.NotificationType.accepted.rawValue) ? postingDictionary["fromId"] as? String : postingDictionary["workerId"] as? String else { continue }
Database.fetchUser(with: uid, completion: { (user) in
if let postId = postingDictionary["fromId"] as? String {
Database.fetchPoster(with: postId, completion: {(poster) in
let notification = userNotifications(user: user, poster: poster, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
})
} else {
let notification = userNotifications(user: user, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
}
// NOTIFICATIONS_REF.child(currentUID).child(notificationId).child("checked").setValue(1)
})
}
}
}
This will solve your problem.

Firebase & Swift: Performing segue after fetching data

When people log in to my app, I take some data from the database like this:
func DownloadButikker() {
self.ref?.child("Stores").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let store = Store()
store.Latitude = dictionary["Latitude"]?.doubleValue
store.Longitude = dictionary["Longitude"]?.doubleValue
store.Store = dictionary["Store"] as? String
store.Status = dictionary["Status"] as? String
stores.append(store)
}
})
self.performSegue(withIdentifier: "LogInToMain", sender: nil)
}
I perform the segue before all data has finished loading. Are there any way to get a completion to the observer or check if all data is loaded before making the segue?
The quick solution is that you need to perform your segue after finishing your async call.
func DownloadButikker() {
self.ref?.child("Stores").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let store = Store()
store.Latitude = dictionary["Latitude"]?.doubleValue
store.Longitude = dictionary["Longitude"]?.doubleValue
store.Store = dictionary["Store"] as? String
store.Status = dictionary["Status"] as? String
stores.append(store)
}
DispatchQueue.main.async {
self.performSegue(withIdentifier: "LogInToMain", sender: nil)
}
})
}
func DownloadButikker(completion: Bool = false) {
self.ref?.child("Stores").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let store = Store()
store.Latitude = dictionary["Latitude"]?.doubleValue
store.Longitude = dictionary["Longitude"]?.doubleValue
store.Store = dictionary["Store"] as? String
store.Status = dictionary["Status"] as? String
stores.append(store)
completion(true)
}
})
}
// In your ViewController
func myFunction() {
DownloadButikker { (hasFinished) in
if hasFinished {
self.performSegue(withIdentifier: "LogInToMain", sender: nil)
}
}
}

How to use the variable of FirebaseReference.observeSingleEvent into another function

I want to use variable_a in another function. Actually, I want to load these data into tableviewcell.
func readFIRData() {
var credentials:[String]
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference().child("usr").child(userID!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let Name = value?["firstName"] as? String ?? ""
let PhoneNo = value?["mobile"] as? String ?? ""
var variable_a = [Name,PhoneNo]
self.tableView.reloadData()
}) { (error) in
}
}
You should have a callback (completion handler) in your readFIRData function, and pass variable_a as a parameter in that callback. Parse it into object which you use in table view, and reload tableView in the callback.
Function should look like this:
func readFIRData(_ completion: ([Name,PhoneNo]) -> ()) {
var credentials:[String]
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference().child("usr").child(userID!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let Name = value?["firstName"] as? String ?? ""
let PhoneNo = value?["mobile"] as? String ?? ""
var variable_a = [Name,PhoneNo]
completion(variable_a)
}) { (error) in
}
}
and then have another function which is going to call readFIRData function:
func requestData() {
readFIRData() { [weak self] data in
guard let `self` = self else { return }
self.data = data
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Mention how you get back to main thread to reload tableView. By self.data I assumed data which you will use in table view to instantiate cells.
Solved this way
func GetData(completion: #escaping(_ credentials: [String]) -> Void) {
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference().child("usr").child(userID!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let Name = value?["firstName"] as? String ?? ""
let PhoneNo = value?["mobile"] as? String ?? ""
var variable_a = [Name,PhoneNo]
completion(variable_a)
self.tableView.reloadData()
}) { (error) in
}
}
Now, assign StringArray to some variable. Use below code to assign. Likewise variable StringArrayVariable can be used to populate tableview cells.
GetData { (StringArray) in
self.StringArrayVariable= StringArray
}

Firebase Realtime Array count mismatch

I have an iOS swift app using Firebase realtime database. If I use the app normally so far I cannot find any issue. However, I want to anticipate edge cases.
I am trying to stress test my app before I push the update, and one way I am doing it is quickly going back and forth from a VC with a tableView to the next VC which is a detail VC. If I do it several times eventually the tableview will show lots of duplicate data.
I have tested my app by having a tableview open on my simulator and going into my Firebase Console and manually changing a value and instantly on the device the string changes.
So I am confused as to why my tableview would show an incorrect amount of children if it is constantly checking what the value should be.
// MARK: Firebase Methods
func checkIfDataExits() {
DispatchQueue.main.async {
self.cardArray.removeAll()
self.ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.hasChild("cards") {
self.pullAllUsersCards()
} else {
self.tableView.reloadData()
}
})
}
}
func pullAllUsersCards() {
cardArray.removeAll()
let userRef = ref.child("users").child((user?.uid)!).child("cards")
userRef.observe(DataEventType.value, with: { (snapshot) in
for userscard in snapshot.children {
let cardID = (userscard as AnyObject).key as String
let cardRef = self.ref.child("cards").child(cardID)
cardRef.observe(DataEventType.value, with: { (cardSnapShot) in
let cardSnap = cardSnapShot as DataSnapshot
let cardDict = cardSnap.value as! [String: AnyObject]
let cardNickname = cardDict["nickname"]
let cardType = cardDict["type"]
let cardStatus = cardDict["cardStatus"]
self.cardNicknameToTransfer = cardNickname as! String
self.cardtypeToTransfer = cardType as! String
let aCard = CardClass()
aCard.cardID = cardID
aCard.nickname = cardNickname as! String
aCard.type = cardType as! String
aCard.cStatus = cardStatus as! Bool
self.cardArray.append(aCard)
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
})
}
I got help and changed my code drastically, so now it works
func checkIfDataExits() {
self.ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.hasChild("services") {
self.pullCardData()
} else {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
func pullCardData() {
let cardRef = self.ref.child("cards")
cardRef.observe(DataEventType.value, with: { (snapshot) in
for cards in snapshot.children {
let allCardIDs = (cards as AnyObject).key as String
if allCardIDs == self.cardID {
if let childId = self.cardID {
let thisCardLocation = cardRef.child(childId)
thisCardLocation.observe(DataEventType.value, with: { (snapshot) in
let thisCardDetails = snapshot as DataSnapshot
if let cardDict = thisCardDetails.value as? [String: AnyObject] {
self.selectedCard?.cardID = thisCardDetails.key
self.selectedCard?.nickname = cardDict["nickname"] as? String ?? ""
self.selectedCard?.type = cardDict["type"] as? String ?? ""
self.pullServicesForCard()
}
})
}
}
}
})
}
func pullServicesForCard() {
if let theId = self.cardID {
let thisCardServices = self.ref.child("cards").child(theId).child("services")
thisCardServices.observe(DataEventType.value, with: { (serviceSnap) in
if self.serviceArray.count != Int(serviceSnap.childrenCount) {
self.serviceArray.removeAll()
self.fetchAndAddAllServices(serviceSnap: serviceSnap, index: 0, completion: { (success) in
if success {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
})
}
}
func fetchAndAddAllServices(serviceSnap: DataSnapshot, index: Int, completion: #escaping (_ success: Bool) -> Void) {
if serviceSnap.hasChildren() {
if index < serviceSnap.children.allObjects.count {
let serviceChild = serviceSnap.children.allObjects[index]
let serviceID = (serviceChild as AnyObject).key as String
let thisServiceLocationInServiceNode = self.ref.child("services").child(serviceID)
thisServiceLocationInServiceNode.observeSingleEvent(of: DataEventType.value, with: { (thisSnap) in
let serv = thisSnap as DataSnapshot
if let serviceDict = serv.value as? [String: AnyObject] {
let aService = ServiceClass(serviceDict: serviceDict)
self.serviceCurrent = serviceDict["serviceStatus"] as? Bool
self.serviceName = serviceDict["serviceName"] as? String ?? ""
self.serviceURL = serviceDict["serviceURL"] as? String ?? ""
self.serviceFixedBool = serviceDict["serviceFixed"] as? Bool
self.serviceFixedAmount = serviceDict["serviceAmount"] as? String ?? ""
self.attentionInt = serviceDict["attentionInt"] as? Int
self.totalArr.append((serviceDict["serviceAmount"] as? String)!)
// self.doubleArray = self.totalArr.flatMap{ Double($0) }
// let arraySum = self.doubleArray.reduce(0, +)
// self.title = self.selectedCard?.nickname ?? ""
// if let titleName = self.selectedCard?.nickname {
// self.title = "\(titleName): \(arraySum)"
// }
aService.serviceID = serviceID
if serviceDict["serviceStatus"] as? Bool == true {
self.selectedCard?.cStatus = true
} else {
self.selectedCard?.cStatus = false
}
if !self.serviceArray.contains(where: { (service) -> Bool in
return service.serviceID == aService.serviceID
}) {
self.serviceArray.append(aService)
self.serviceArray.sort {$1.serviceAttention < $0.serviceAttention}
}
}
self.fetchAndAddAllServices(serviceSnap: serviceSnap, index: index + 1, completion: completion)
})
}
else {
completion(true)
}
}
else {
completion(false)
}
}

Resources