Okay, I have been dealing with something I really need some help with, so I am here. I have a simple function where it loops through firebase and adds data to arrays. Here is the problem, I need to know where the function has finished looping. I honestly need some help figuring this out, so here is an example.
func getUsers() {
let ref = Database.database.reference()
if let myAget = self.myAgel {
ref.child("newUsers").observeSingleEvent(of: .value, with: {(snapshoter) in
if let valuer = snapshoter.value as? [String : AnyObject] {
for (one,_) in valuer {
checkCount.append("one")
ref.child("users").child(one).child("age").observeSingleEvent(of: .value, with: {(snap) in
if let agerti = snap.value as? Int {
if myAget - agerti <= 1 && myAget - agerti >= -1 {
print("Add this user")
}
}
})
}
}
print("now do something with those users")
}
})
}
}
**Console Prints :
now do something with those users
Add this user
Add this user
Add this user
Add this user
AS you can see, the problem is I want to call a function after I simply check if the users age is close to yours : +- 1 yr. How can I have it print "now do something..." after "add the user". Thank you so much!, Please respond if you have anything helpful at all, I am in need of anything.
You can use a DispatchGroup for this. You need to enter the dispatch group before you start each unit of asynchronous work and leave when that unit is complete. You can establish as closure to be executed when all work is complete using notify.
func getUsers() {
let ref = Database.database.reference()
if let myAget = self.myAgel {
ref.child("newUsers").observeSingleEvent(of: .value, with: {(snapshoter) in
if let valuer = snapshoter.value as? [String : AnyObject] {
let dispatchGroup = DispatchGroup()
for (one,_) in valuer {
dispatchGroup.enter()
checkCount.append("one")
ref.child("users").child(one).child("age").observeSingleEvent(of: .value, with: {(snap) in
if let agerti = snap.value as? Int {
if myAget - agerti <= 1 && myAget - agerti >= -1 {
print("Add this user")
}
}
dispatchGroup.leave()
})
}
dispatchGroup.notify {
print("now do something with those users")
}
}
})
}
}
Related
I have a problem that only occurs when page A has to run a massive amount of code and the user goes to page B before all the A page code is finished. In these instances, sometimes cells get duplicated(ie, say page B must be : User H in top, user F below him. Instead there are two Hs followed by two Fs below them).
Below is the relevant code of page B, but I am fairly certain the problem does not lie there. Why?: Because I changed the array that gets displayed [H,F] to a set, so according to the code, there should never be an instance like [H,H,F,F]
///Part1: code that gets called from viewdidLoad
var peppi = [Usery]()
func printPersonInfo(uid: String) {
self.handleA = thisUser.observe(DataEventType.value, with: { snapshot in
...
myPeopleRef44.queryLimited(toLast: 30).observeSingleEvent(of: .value, with: { [self] snapshot in
let uniqueArray = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray))
for person in peopleArray where uid == person.value as? String {
...
func decode(autoId: String) -> TimeInterval {
}
return TimeInterval(exactly: timestamp)!
}
...
if Calendar.current.isDateInToday(date){
let p = Usery(...)
peppi.append(p)
}
}
DispatchQueue.main.async {
self.peppi.sort { ($0.self.time1 ?? 0) > ($1.self.time1 ?? 0)
}
print(self.peppi, "lo")
self.table.reloadData()
}
})
})
}
/// Part: 2 In viewDidLoad, code that calls the function printPersonInfo
handle = myPeopleRef.queryLimited(toLast: 30).observe(DataEventType.value, with: { snapshot in
func decode(autoId: String) -> TimeInterval {
..
return …
}
let uniqueArray1 = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray1))
for person4 in peopleArray where uid == Auth.auth().currentUser?.uid {
self.dg.enter()
self.dg.leave()
self.dg.notify(queue: .main) {
let date = Date(timeIntervalSince1970: TimeInterval(time11)/1000.0)
print(date,"pdate")
if Calendar.current.isDateInToday(date){
self.printPersonInfo(uid: personUid)
}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
let ref = Database.database().reference().child("people")
ref.removeObserver(withHandle: handle)
ref.removeObserver(withHandle: self.handleA)
}
})
i need to check when i iterated through the last item. I cannot just put the line after my for loop because then i receive always an empty list. I tried the following but this one doesnt work:
.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
for rest in snapshot.children.allObjects.count as! [DataSnapshot] {
let refi = Database.database().reference().child("Users")
refi.observeSingleEvent(of: .value, with: { (snapshoti) in
if snapshoti.value as! String == "active"{
let userid = rest.key
self.someProtocol[rest.key] = self.checksum
if self.checksum == self.someProtocol.count-1 {
self.sortUsers()
}else{
}
self.checksum = self.checksum+1
}
})
}
The answer of dr_barto will work but needs the following adaptation:
for (idx, element) in array.enumerated() {
if idx == array.endIndex-1 {
// handling the last element
}
}
From the Apple documentation:
endIndex is the array’s “past the end” position—that is, the position one greater than the last valid subscript argument
If you don't want to use index, you can check element like this:
for element in array {
if element == array.first {
print("first")
} else if element == array.last {
print("last")
}
}
EDIT my answer won't work since (as pointed out in the comments) endIndex is never going to match any index value returned from enumerated because it denotes the index after the last element. See https://stackoverflow.com/a/53341276/5471218 for how it's done correctly :)
As pointed out in the comments, you should use enumerated; given an array, you'd use it like this:
for (idx, element) in array.enumerated() {
if idx == array.endIndex {
// handling the last element
}
}
Could be made into an extension also:
extension Array {
func lastIndex(index: Int) -> Bool {
index == endIndex-1
}
}
or
extension Array where Element : Equatable {
func isLast(element: Element) -> Bool {
last == element
}
}
for the newbies in swift like me..
I attached the full code.
ref.child("BookShelf").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
if let datasnap = snapshot.value as? Dictionary<String, Any> {
for (index,data) in datasnap.enumerated() {
let book = Book(userid: data.key , dataSnap: data.value as! Dictionary<String, Any>)
bookShelf.append(book)
print("index",index)
print("datasnap.count-1",datasnap.count-1)
if(index == datasnap.count-1){
print("1 bookshelf count getFirebaseData()",bookShelf.count)
self.showDashBoard(bookShelf: bookShelf)
}
}
//if last data then trigger show
}
}) { (error) in
print(error.localizedDescription)
}
Try this solution
.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
for (index,rest) in snapshot.children.allObjects {
if index == (snapshot.children.allObjects.count - 1){
//yeah finally you are getting the last object :)
}
}
})
}
P.S: I Assume snapshot.children.allObjects is an array of [DataSnapshot]
I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}
The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().
Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}
func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}
I'm trying to find out the best way to handle a completion on a function.
The function calls for data from firebase and adds them to an array of dictionaries. Because this is for maps and adding annotations the loop is adding lots of data before coming to the final appended version so its throwing loads of annotations dow in the same place. i want to know if i can call a completion on the loop when its finished and then call the function ShowSightings().
func getDatafromFB() {
DataService.ds.REF_POSTS.child("postCodes").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let postsIds = value?.allKeys as! [String]
for postId in postsIds {
let refToPost = Database.database().reference(withPath: "posts/" + "postCodes/" + postId)
refToPost.observe(.value, with: { snapshot in
if snapshot.exists() {
let postDict = snapshot.value as? [String: AnyObject]
print("Tony: before append post \(self.posts)")
self.posts.append(postDict!)
print("Tony: post \(self.posts)")
}else {
print("Tony: Couldn't get the data")
}
})
}
print("Tony: The compleetion result \(self.posts)")
})
}
You can try this:
func doAsyncTask(completionHandler:#escaping (Bool) -> ()){
//do async tasks
completionHandler(true) //<- call this when the data is retrieved
//so in your case, see below
}
override func viewDidLoad{
doAsyncTask(){ succes in
//succes gives true or false
}
}
//your case
}else {
print("Tony: Couldn't get the data")
}
completionHandler(true) //<- right there
This is for 1 async task. I see you want to use multiple async task. This is a job for dispatch groups. I change some of my function to take parameters. Check this out:
func doAsyncTask(postID: String, completionHandler:#escaping (Bool) -> ()){
//do async tasks
completionHandler(true)
}
override func viewDidLoad{
var arrPostIDs = [String]()
//append to arrPostIDs here
let postIDDispatchGroup = DispatchGroup()
for postID in arrPostIDs{
postIDDispatchGroup.enter()
doAsyncTask(postID: postID){ succes in
//succes gives true or false
postIDDispatchGroup.leave()
}
}
postIDDispatchGroup.notify(queue: .main) {
//everything completed :), do whatever you want
}
}
I am wanting to update the child values after editing inside the textfields.
At the moment I have this action:
#IBAction func updateAction(_ sender: Any) {
guard let itemNameText = itemName.text, let itemDateText = itemDate.text else { return }
guard itemNameText.characters.count > 0, itemDateText.characters.count > 0 else {
print("Complete all fields")
return
}
let uid = FIRAuth.auth()?.currentUser?.uid
let key = item.ref!.key
let itemList = Item(itemName: itemNameText, itemDate: itemDateText, uid: uid!)
let editItemRef = databaseRef.child("/usersList/\(key)")
editItemRef.updateChildValues(itemList.toAnyObject())
print("edited")
}
I was following this tutorial but he seems to use the username, and as I only have the email or uid (userID) as authentication I thought I'd use the uid.
This is my toAnyObject function inside my class:
func toAnyObject() -> [String: AnyObject] {
return ["itemName": itemName as AnyObject, "itemDate": itemDate as AnyObject, "userID": userID as AnyObject]
}
When I run the breakpoint it does show the edited value of the item however the update doesn't appear to be performing.
Just to be extra safe, try dropping the leading slash from your path:
databaseRef.child("usersList/\(key)")
…and try printing the Error returned by Firebase, if any:
editItemRef.updateChildValues(itemList.toAnyObject()) {
(error, _) in
if let error = error {
print("ERROR: \(error)")
} else {
print("SUCCESS")
}
Edit. We found out he was using the wrong database path. The right one is:
databaseRef.child("users").child(uid!).child("usersList/\(key)")